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 if(!actualDpiLoadAttempted) { 1867 // FIXME: do the actual monitor we are on 1868 // and on X this is a good chance to load the monitor map. 1869 version(Windows) { 1870 if(GetDpiForWindow) 1871 actualDpi_ = GetDpiForWindow(impl.hwnd); 1872 } else version(X11) { 1873 if(!xRandrInfoLoadAttemped) { 1874 xRandrInfoLoadAttemped = true; 1875 if(!XRandrLibrary.attempted) { 1876 XRandrLibrary.loadDynamicLibrary(); 1877 } 1878 1879 if(XRandrLibrary.loadSuccessful) { 1880 auto display = XDisplayConnection.get; 1881 int scratch; 1882 int major, minor; 1883 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 1884 goto fallback; 1885 1886 XRRQueryVersion(display, &major, &minor); 1887 if(major <= 1 && minor < 5) 1888 goto fallback; 1889 1890 int count; 1891 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 1892 if(monitors is null) 1893 goto fallback; 1894 scope(exit) XRRFreeMonitors(monitors); 1895 1896 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 1897 MonitorInfo.info.assumeSafeAppend(); 1898 foreach(idx, monitor; monitors[0 .. count]) { 1899 MonitorInfo.info ~= MonitorInfo( 1900 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1901 Size(monitor.mwidth, monitor.mheight), 1902 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 1903 ); 1904 1905 /+ 1906 if(monitor.mwidth == 0 || monitor.mheight == 0) 1907 // unknown physical size, just guess 96 to avoid divide by zero 1908 MonitorInfo.info ~= MonitorInfo( 1909 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1910 Size(monitor.mwidth, monitor.mheight), 1911 96 1912 ); 1913 else 1914 // and actual thing 1915 MonitorInfo.info ~= MonitorInfo( 1916 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1917 Size(monitor.mwidth, monitor.mheight), 1918 minInternal( 1919 // millimeter to int then rounding up. 1920 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 1921 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 1922 ) 1923 ); 1924 +/ 1925 } 1926 // writeln("Here", MonitorInfo.info); 1927 } 1928 } 1929 1930 if(XRandrLibrary.loadSuccessful) { 1931 updateActualDpi(true); 1932 // writeln("updated"); 1933 1934 if(!requestedInput) { 1935 // this is what requests live updates should the configuration change 1936 // each time you select input, it sends an initial event, so very important 1937 // to not get into a loop of selecting input, getting event, updating data, 1938 // and reselecting input... 1939 requestedInput = true; 1940 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 1941 // writeln("requested input"); 1942 } 1943 } else { 1944 fallback: 1945 // make sure we disable events that aren't coming 1946 xrrEventBase = -1; 1947 // best guess... respect the custom scaling user command to some extent at least though 1948 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 1949 } 1950 } 1951 actualDpiLoadAttempted = true; 1952 } 1953 return actualDpi_; 1954 } 1955 1956 private int actualDpi_; 1957 private bool actualDpiLoadAttempted; 1958 1959 version(X11) private { 1960 bool requestedInput; 1961 static bool xRandrInfoLoadAttemped; 1962 struct MonitorInfo { 1963 Rectangle position; 1964 Size size; 1965 int dpi; 1966 1967 static MonitorInfo[] info; 1968 } 1969 bool screenPositionKnown; 1970 int screenPositionX; 1971 int screenPositionY; 1972 void updateActualDpi(bool loadingNow = false) { 1973 if(!loadingNow && !actualDpiLoadAttempted) 1974 actualDpi(); // just to make it do the load 1975 foreach(idx, m; MonitorInfo.info) { 1976 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 1977 bool changed = actualDpi_ && actualDpi_ != m.dpi; 1978 actualDpi_ = m.dpi; 1979 // writeln("monitor ", idx); 1980 if(changed && onDpiChanged) 1981 onDpiChanged(); 1982 break; 1983 } 1984 } 1985 } 1986 } 1987 1988 /++ 1989 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 1990 or if the window is moved to a new remote connection or a monitor is hot-swapped. 1991 1992 History: 1993 Added November 26, 2021 (dub v10.4) 1994 1995 See_Also: 1996 [actualDpi] 1997 +/ 1998 void delegate() onDpiChanged; 1999 2000 version(X11) { 2001 void recreateAfterDisconnect() { 2002 if(!stateDiscarded) return; 2003 2004 if(_parent !is null && _parent.stateDiscarded) 2005 _parent.recreateAfterDisconnect(); 2006 2007 bool wasHidden = hidden; 2008 2009 activeScreenPainter = null; // should already be done but just to confirm 2010 2011 actualDpi_ = 0; 2012 actualDpiLoadAttempted = false; 2013 xRandrInfoLoadAttemped = false; 2014 2015 impl.createWindow(_width, _height, _title, openglMode, _parent); 2016 2017 if(auto dh = dropHandler) { 2018 dropHandler = null; 2019 enableDragAndDrop(this, dh); 2020 } 2021 2022 if(recreateAdditionalConnectionState) 2023 recreateAdditionalConnectionState(); 2024 2025 hidden = wasHidden; 2026 stateDiscarded = false; 2027 } 2028 2029 bool stateDiscarded; 2030 void discardConnectionState() { 2031 if(XDisplayConnection.display) 2032 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2033 if(discardAdditionalConnectionState) 2034 discardAdditionalConnectionState(); 2035 stateDiscarded = true; 2036 } 2037 2038 void delegate() discardAdditionalConnectionState; 2039 void delegate() recreateAdditionalConnectionState; 2040 2041 } 2042 2043 private DropHandler dropHandler; 2044 2045 SimpleWindow _parent; 2046 bool beingOpenKeepsAppOpen = true; 2047 /++ 2048 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. 2049 2050 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2051 2052 Params: 2053 2054 width = the width of the window's client area, in pixels 2055 height = the height of the window's client area, in pixels 2056 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2057 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2058 resizable = [Resizability] has three options: 2059 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2060 $(P `fixedSize` will not allow the user to resize the window.) 2061 $(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.) 2062 windowType = The type of window you want to make. 2063 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. 2064 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". 2065 +/ 2066 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) { 2067 claimGuiThread(); 2068 version(sdpy_thread_checks) assert(thisIsGuiThread); 2069 this._width = this._virtualWidth = width; 2070 this._height = this._virtualHeight = height; 2071 this.openglMode = opengl; 2072 version(X11) { 2073 // auto scale not implemented except with opengl and even there it is kinda weird 2074 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2075 resizable = Resizability.fixedSize; 2076 } 2077 this.resizability = resizable; 2078 this.windowType = windowType; 2079 this.customizationFlags = customizationFlags; 2080 this._title = (title is null ? "D Application" : title); 2081 this._parent = parent; 2082 impl.createWindow(width, height, this._title, opengl, parent); 2083 2084 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2085 beingOpenKeepsAppOpen = false; 2086 } 2087 2088 /// ditto 2089 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2090 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2091 } 2092 2093 /// Same as above, except using the `Size` struct instead of separate width and height. 2094 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2095 this(size.width, size.height, title, opengl, resizable); 2096 } 2097 2098 /// ditto 2099 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2100 this(size, title, opengl, resizable); 2101 } 2102 2103 2104 /++ 2105 Creates a window based on the given [Image]. It's client area 2106 width and height is equal to the image. (A window's client area 2107 is the drawable space inside; it excludes the title bar, etc.) 2108 2109 Windows based on images will not be resizable and do not use OpenGL. 2110 2111 It will draw the image in upon creation, but this will be overwritten 2112 upon any draws, including the initial window visible event. 2113 2114 You probably do not want to use this and it may be removed from 2115 the library eventually, or I might change it to be a "permanent" 2116 background image; one that is automatically drawn on it before any 2117 other drawing event. idk. 2118 +/ 2119 this(Image image, string title = null) { 2120 this(image.width, image.height, title); 2121 this.image = image; 2122 } 2123 2124 /++ 2125 Wraps a native window handle with very little additional processing - notably no destruction 2126 this is incomplete so don't use it for much right now. The purpose of this is to make native 2127 windows created through the low level API (so you can use platform-specific options and 2128 other details SimpleWindow does not expose) available to the event loop wrappers. 2129 +/ 2130 this(NativeWindowHandle nativeWindow) { 2131 windowType = WindowTypes.minimallyWrapped; 2132 version(Windows) 2133 impl.hwnd = nativeWindow; 2134 else version(X11) { 2135 impl.window = nativeWindow; 2136 if(nativeWindow) 2137 display = XDisplayConnection.get(); // get initial display to not segfault 2138 } else version(OSXCocoa) 2139 throw new NotYetImplementedException(); 2140 else featureNotImplemented(); 2141 // FIXME: set the size correctly 2142 _width = 1; 2143 _height = 1; 2144 if(nativeWindow) 2145 nativeMapping[nativeWindow] = this; 2146 2147 beingOpenKeepsAppOpen = false; 2148 2149 if(nativeWindow) 2150 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2151 _suppressDestruction = true; // so it doesn't try to close 2152 } 2153 2154 /++ 2155 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2156 The delegate will be called when the window manager asks you to take focus. 2157 2158 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2159 2160 History: 2161 Added April 1, 2022 (dub v10.8) 2162 +/ 2163 SimpleWindow delegate() setRequestedInputFocus; 2164 2165 /// Experimental, do not use yet 2166 /++ 2167 Grabs exclusive input from the user until you release it with 2168 [releaseInputGrab]. 2169 2170 2171 Note: it is extremely rude to do this without good reason. 2172 Reasons may include doing some kind of mouse drag operation 2173 or popping up a temporary menu that should get events and will 2174 be dismissed at ease by the user clicking away. 2175 2176 Params: 2177 keyboard = do you want to grab keyboard input? 2178 mouse = grab mouse input? 2179 confine = confine the mouse cursor to inside this window? 2180 2181 History: 2182 Prior to March 11, 2021, grabbing the keyboard would always also 2183 set the X input focus. Now, it only focuses if it is a non-transient 2184 window and otherwise manages the input direction internally. 2185 2186 This means spurious focus/blur events will no longer be sent and the 2187 application will not steal focus from other applications (which the 2188 window manager may have rejected anyway). 2189 +/ 2190 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2191 static if(UsingSimpledisplayX11) { 2192 XSync(XDisplayConnection.get, 0); 2193 if(keyboard) { 2194 if(isTransient && _parent) { 2195 /* 2196 FIXME: 2197 setting the keyboard focus is not actually that helpful, what I more likely want 2198 is the events from the parent window to be sent over here if we're transient. 2199 */ 2200 2201 _parent.inputProxy = this; 2202 } else { 2203 2204 SimpleWindow setTo; 2205 if(setRequestedInputFocus !is null) 2206 setTo = setRequestedInputFocus(); 2207 if(setTo is null) 2208 setTo = this; 2209 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2210 } 2211 } 2212 if(mouse) { 2213 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2214 EventMask.PointerMotionMask // FIXME: not efficient 2215 | EventMask.ButtonPressMask 2216 | EventMask.ButtonReleaseMask 2217 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2218 ) 2219 { 2220 XSync(XDisplayConnection.get, 0); 2221 import core.stdc.stdio; 2222 printf("Grab input failed %d\n", res); 2223 //throw new Exception("Grab input failed"); 2224 } else { 2225 // cool 2226 } 2227 } 2228 2229 } else version(Windows) { 2230 // FIXME: keyboard? 2231 SetCapture(impl.hwnd); 2232 if(confine) { 2233 RECT rcClip; 2234 //RECT rcOldClip; 2235 //GetClipCursor(&rcOldClip); 2236 GetWindowRect(hwnd, &rcClip); 2237 ClipCursor(&rcClip); 2238 } 2239 } else version(OSXCocoa) { 2240 throw new NotYetImplementedException(); 2241 } else static assert(0); 2242 } 2243 2244 private Point imePopupLocation = Point(0, 0); 2245 2246 /++ 2247 Sets the location for the IME (input method editor) to pop up when the user activates it. 2248 2249 Bugs: 2250 Not implemented outside X11. 2251 +/ 2252 void setIMEPopupLocation(Point location) { 2253 static if(UsingSimpledisplayX11) { 2254 imePopupLocation = location; 2255 updateIMEPopupLocation(); 2256 } else { 2257 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2258 // throw new NotYetImplementedException(); 2259 } 2260 } 2261 2262 /// ditto 2263 void setIMEPopupLocation(int x, int y) { 2264 return setIMEPopupLocation(Point(x, y)); 2265 } 2266 2267 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2268 // so this function gets called in setIMEPopupLocation as well as whenever the window 2269 // receives a ConfigureNotify event 2270 private void updateIMEPopupLocation() { 2271 static if(UsingSimpledisplayX11) { 2272 if (xic is null) { 2273 return; 2274 } 2275 2276 XPoint nspot; 2277 nspot.x = cast(short) imePopupLocation.x; 2278 nspot.y = cast(short) imePopupLocation.y; 2279 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2280 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2281 XFree(preeditAttr); 2282 } 2283 } 2284 2285 private bool imeFocused = true; 2286 2287 /++ 2288 Tells the IME whether or not an input field is currently focused in the window. 2289 2290 Bugs: 2291 Not implemented outside X11. 2292 +/ 2293 void setIMEFocused(bool value) { 2294 imeFocused = value; 2295 updateIMEFocused(); 2296 } 2297 2298 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2299 private void updateIMEFocused() { 2300 static if(UsingSimpledisplayX11) { 2301 if (xic is null) { 2302 return; 2303 } 2304 2305 if (focused && imeFocused) { 2306 XSetICFocus(xic); 2307 } else { 2308 XUnsetICFocus(xic); 2309 } 2310 } 2311 } 2312 2313 /++ 2314 Returns the native window. 2315 2316 History: 2317 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2318 to access it through the `impl` member (which is semi-supported 2319 but platform specific and here it is simple enough to offer an accessor). 2320 2321 Bugs: 2322 Not implemented outside Windows or X11. 2323 +/ 2324 NativeWindowHandle nativeWindowHandle() { 2325 version(X11) 2326 return impl.window; 2327 else version(Windows) 2328 return impl.hwnd; 2329 else 2330 throw new NotYetImplementedException(); 2331 } 2332 2333 private bool isTransient() { 2334 with(WindowTypes) 2335 final switch(windowType) { 2336 case normal, undecorated, eventOnly: 2337 case nestedChild, minimallyWrapped: 2338 return (customizationFlags & WindowFlags.transient) ? true : false; 2339 case dropdownMenu, popupMenu, notification: 2340 return true; 2341 } 2342 } 2343 2344 private SimpleWindow inputProxy; 2345 2346 /++ 2347 Releases the grab acquired by [grabInput]. 2348 +/ 2349 void releaseInputGrab() { 2350 static if(UsingSimpledisplayX11) { 2351 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2352 if(_parent) 2353 _parent.inputProxy = null; 2354 } else version(Windows) { 2355 ReleaseCapture(); 2356 ClipCursor(null); 2357 } else version(OSXCocoa) { 2358 throw new NotYetImplementedException(); 2359 } else static assert(0); 2360 } 2361 2362 /++ 2363 Sets the input focus to this window. 2364 2365 You shouldn't call this very often - please let the user control the input focus. 2366 +/ 2367 void focus() { 2368 static if(UsingSimpledisplayX11) { 2369 SimpleWindow setTo; 2370 if(setRequestedInputFocus !is null) 2371 setTo = setRequestedInputFocus(); 2372 if(setTo is null) 2373 setTo = this; 2374 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2375 } else version(Windows) { 2376 SetFocus(this.impl.hwnd); 2377 } else version(OSXCocoa) { 2378 throw new NotYetImplementedException(); 2379 } else static assert(0); 2380 } 2381 2382 /++ 2383 Requests attention from the user for this window. 2384 2385 2386 The typical result of this function is to change the color 2387 of the taskbar icon, though it may be tweaked on specific 2388 platforms. 2389 2390 It is meant to unobtrusively tell the user that something 2391 relevant to them happened in the background and they should 2392 check the window when they get a chance. Upon receiving the 2393 keyboard focus, the window will automatically return to its 2394 natural state. 2395 2396 If the window already has the keyboard focus, this function 2397 may do nothing, because the user is presumed to already be 2398 giving the window attention. 2399 2400 Implementation_note: 2401 2402 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2403 atom on X11 and the FlashWindow function on Windows. 2404 +/ 2405 void requestAttention() { 2406 if(_focused) 2407 return; 2408 2409 version(Windows) { 2410 FLASHWINFO info; 2411 info.cbSize = info.sizeof; 2412 info.hwnd = impl.hwnd; 2413 info.dwFlags = FLASHW_TRAY; 2414 info.uCount = 1; 2415 2416 FlashWindowEx(&info); 2417 2418 } else version(X11) { 2419 demandingAttention = true; 2420 demandAttention(this, true); 2421 } else version(OSXCocoa) { 2422 throw new NotYetImplementedException(); 2423 } else static assert(0); 2424 } 2425 2426 private bool _focused; 2427 2428 version(X11) private bool demandingAttention; 2429 2430 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2431 /// You'll have to call `close()` manually if you set this delegate. 2432 void delegate () closeQuery; 2433 2434 /// This will be called when window visibility was changed. 2435 void delegate (bool becomesVisible) visibilityChanged; 2436 2437 /// This will be called when window becomes visible for the first time. 2438 /// You can do OpenGL initialization here. Note that in X11 you can't call 2439 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2440 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2441 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2442 private bool _visibleForTheFirstTimeCalled; 2443 void delegate () visibleForTheFirstTime; 2444 2445 /// Returns true if the window has been closed. 2446 final @property bool closed() { return _closed; } 2447 2448 private final @property bool notClosed() { return !_closed; } 2449 2450 /// Returns true if the window is focused. 2451 final @property bool focused() { return _focused; } 2452 2453 private bool _visible; 2454 /// Returns true if the window is visible (mapped). 2455 final @property bool visible() { return _visible; } 2456 2457 /// Closes the window. If there are no more open windows, the event loop will terminate. 2458 void close() { 2459 if (!_closed) { 2460 runInGuiThread( { 2461 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2462 if (onClosing !is null) onClosing(); 2463 impl.closeWindow(); 2464 _closed = true; 2465 } ); 2466 } 2467 } 2468 2469 /++ 2470 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2471 2472 History: 2473 Overload added on March 7, 2021. 2474 +/ 2475 void close() shared { 2476 (cast() this).close(); 2477 } 2478 2479 /++ 2480 2481 +/ 2482 void maximize() { 2483 version(Windows) 2484 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2485 else version(X11) { 2486 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2487 2488 // also note _NET_WM_STATE_FULLSCREEN 2489 } 2490 2491 } 2492 2493 private bool _fullscreen; 2494 version(Windows) 2495 private WINDOWPLACEMENT g_wpPrev; 2496 2497 /// not fully implemented but planned for a future release 2498 void fullscreen(bool yes) { 2499 version(Windows) { 2500 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2501 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2502 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2503 MONITORINFO mi; 2504 mi.cbSize = MONITORINFO.sizeof; 2505 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2506 GetMonitorInfo(MonitorFromWindow(hwnd, 2507 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2508 SetWindowLong(hwnd, GWL_STYLE, 2509 dwStyle & ~WS_OVERLAPPEDWINDOW); 2510 SetWindowPos(hwnd, HWND_TOP, 2511 mi.rcMonitor.left, mi.rcMonitor.top, 2512 mi.rcMonitor.right - mi.rcMonitor.left, 2513 mi.rcMonitor.bottom - mi.rcMonitor.top, 2514 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2515 } 2516 } else { 2517 SetWindowLong(hwnd, GWL_STYLE, 2518 dwStyle | WS_OVERLAPPEDWINDOW); 2519 SetWindowPlacement(hwnd, &g_wpPrev); 2520 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2521 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2522 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2523 } 2524 2525 } else version(X11) { 2526 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2527 } 2528 2529 _fullscreen = yes; 2530 2531 } 2532 2533 bool fullscreen() { 2534 return _fullscreen; 2535 } 2536 2537 /++ 2538 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2539 2540 +/ 2541 void minimize() { 2542 version(Windows) 2543 ShowWindow(impl.hwnd, SW_MINIMIZE); 2544 //else version(X11) 2545 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2546 } 2547 2548 /// Alias for `hidden = false` 2549 void show() { 2550 hidden = false; 2551 } 2552 2553 /// Alias for `hidden = true` 2554 void hide() { 2555 hidden = true; 2556 } 2557 2558 /// Hide cursor when it enters the window. 2559 void hideCursor() { 2560 version(OSXCocoa) throw new NotYetImplementedException(); else 2561 if (!_closed) impl.hideCursor(); 2562 } 2563 2564 /// Don't hide cursor when it enters the window. 2565 void showCursor() { 2566 version(OSXCocoa) throw new NotYetImplementedException(); else 2567 if (!_closed) impl.showCursor(); 2568 } 2569 2570 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2571 * 2572 * Please remember that the cursor is a shared resource that should usually be left to the user's 2573 * control. Try to think for other approaches before using this function. 2574 * 2575 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2576 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2577 * receive "mouse moved here" event. 2578 */ 2579 bool warpMouse (int x, int y) { 2580 version(X11) { 2581 if (!_closed) { impl.warpMouse(x, y); return true; } 2582 } else version(Windows) { 2583 if (!_closed) { 2584 POINT point; 2585 point.x = x; 2586 point.y = y; 2587 if(ClientToScreen(impl.hwnd, &point)) { 2588 SetCursorPos(point.x, point.y); 2589 return true; 2590 } 2591 } 2592 } 2593 return false; 2594 } 2595 2596 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2597 void sendDummyEvent () { 2598 version(X11) { 2599 if (!_closed) { impl.sendDummyEvent(); } 2600 } 2601 } 2602 2603 /// Set window minimal size. 2604 void setMinSize (int minwidth, int minheight) { 2605 version(OSXCocoa) throw new NotYetImplementedException(); else 2606 if (!_closed) impl.setMinSize(minwidth, minheight); 2607 } 2608 2609 /// Set window maximal size. 2610 void setMaxSize (int maxwidth, int maxheight) { 2611 version(OSXCocoa) throw new NotYetImplementedException(); else 2612 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2613 } 2614 2615 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2616 /// Currently only supported on X11. 2617 void setResizeGranularity (int granx, int grany) { 2618 version(OSXCocoa) throw new NotYetImplementedException(); else 2619 if (!_closed) impl.setResizeGranularity(granx, grany); 2620 } 2621 2622 /// Move window. 2623 void move(int x, int y) { 2624 version(OSXCocoa) throw new NotYetImplementedException(); else 2625 if (!_closed) impl.move(x, y); 2626 } 2627 2628 /// ditto 2629 void move(Point p) { 2630 version(OSXCocoa) throw new NotYetImplementedException(); else 2631 if (!_closed) impl.move(p.x, p.y); 2632 } 2633 2634 /++ 2635 Resize window. 2636 2637 Note that the width and height of the window are NOT instantly 2638 updated - it waits for the window manager to approve the resize 2639 request, which means you must return to the event loop before the 2640 width and height are actually changed. 2641 +/ 2642 void resize(int w, int h) { 2643 if(!_closed && _fullscreen) fullscreen = false; 2644 version(OSXCocoa) throw new NotYetImplementedException(); else 2645 if (!_closed) impl.resize(w, h); 2646 } 2647 2648 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2649 void moveResize (int x, int y, int w, int h) { 2650 if(!_closed && _fullscreen) fullscreen = false; 2651 version(OSXCocoa) throw new NotYetImplementedException(); else 2652 if (!_closed) impl.moveResize(x, y, w, h); 2653 } 2654 2655 private bool _hidden; 2656 2657 /// Returns true if the window is hidden. 2658 final @property bool hidden() { 2659 return _hidden; 2660 } 2661 2662 /// Shows or hides the window based on the bool argument. 2663 final @property void hidden(bool b) { 2664 _hidden = b; 2665 version(Windows) { 2666 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2667 } else version(X11) { 2668 if(b) 2669 //XUnmapWindow(impl.display, impl.window); 2670 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2671 else 2672 XMapWindow(impl.display, impl.window); 2673 } else version(OSXCocoa) { 2674 throw new NotYetImplementedException(); 2675 } else static assert(0); 2676 } 2677 2678 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2679 void opacity(double opacity) @property 2680 in { 2681 assert(opacity >= 0 && opacity <= 1); 2682 } do { 2683 version (Windows) { 2684 impl.setOpacity(cast(ubyte)(255 * opacity)); 2685 } else version (X11) { 2686 impl.setOpacity(cast(uint)(uint.max * opacity)); 2687 } else throw new NotYetImplementedException(); 2688 } 2689 2690 /++ 2691 Sets your event handlers, without entering the event loop. Useful if you 2692 have multiple windows - set the handlers on each window, then only do 2693 [eventLoop] on your main window or call `EventLoop.get.run();`. 2694 2695 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2696 [handlePulse], and [handleMouseEvent] automatically based on the provide 2697 delegate signatures. 2698 +/ 2699 void setEventHandlers(T...)(T eventHandlers) { 2700 // FIXME: add more events 2701 foreach(handler; eventHandlers) { 2702 static if(__traits(compiles, handleKeyEvent = handler)) { 2703 handleKeyEvent = handler; 2704 } else static if(__traits(compiles, handleCharEvent = handler)) { 2705 handleCharEvent = handler; 2706 } else static if(__traits(compiles, handlePulse = handler)) { 2707 handlePulse = handler; 2708 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2709 handleMouseEvent = handler; 2710 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2711 } 2712 } 2713 2714 /++ 2715 The event loop automatically returns when the window is closed 2716 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2717 pulse timer is created. The event loop will block until an event 2718 arrives or the pulse timer goes off. 2719 2720 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2721 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2722 [handleMouseEvent], based on the signature of delegates you provide. 2723 2724 Give one with no parameters to set a timer pulse handler. Give one that 2725 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2726 and one that takes `dchar` for a char event handler. You can use as many 2727 or as few handlers as you need for your application. 2728 2729 Bugs: 2730 2731 $(PITFALL 2732 You should always have one event loop live for your application. 2733 If you make two windows in sequence, the second call to eventLoop 2734 might fail: 2735 2736 --- 2737 // don't do this! 2738 auto window = new SimpleWindow(); 2739 window.eventLoop(0); 2740 2741 auto window2 = new SimpleWindow(); 2742 window2.eventLoop(0); // problematic! might crash 2743 --- 2744 2745 simpledisplay's current implementation assumes that final cleanup is 2746 done when the event loop refcount reaches zero. So after the first 2747 eventLoop returns, when there isn't already another one active, it assumes 2748 the program will exit soon and cleans up. 2749 2750 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2751 it eventually, but in the mean time, there's an easy solution: 2752 2753 --- 2754 // do this 2755 EventLoop mainEventLoop = EventLoop.get; // just add this line 2756 2757 auto window = new SimpleWindow(); 2758 window.eventLoop(0); 2759 2760 auto window2 = new SimpleWindow(); 2761 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2762 --- 2763 2764 By adding a top-level reference to the event loop, it ensures the final cleanup 2765 is not performed until it goes out of scope too, letting the individual window loops 2766 work without trouble despite the bug. 2767 ) 2768 2769 History: 2770 The overload without `pulseTimeout` was added on December 8, 2021. 2771 2772 On December 9, 2021, the default blocking mode (which is now configurable 2773 because [eventLoopWithBlockingMode] was added) switched from 2774 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2775 should almost never be noticeable to you since the typical simpledisplay 2776 paradigm has been (and I still recommend) to have one `eventLoop` call. 2777 2778 See_Also: 2779 [eventLoopWithBlockingMode] 2780 +/ 2781 final int eventLoop(T...)( 2782 long pulseTimeout, /// set to zero if you don't want a pulse. 2783 T eventHandlers) /// delegate list like std.concurrency.receive 2784 { 2785 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2786 } 2787 2788 /// ditto 2789 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2790 { 2791 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 2792 } 2793 2794 /++ 2795 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 2796 2797 History: 2798 Added December 8, 2021 (dub v10.5) 2799 2800 Previously, this implementation was right inside [eventLoop], but when I wanted 2801 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 2802 just renamed it instead of adding as an overload. Besides, the new name makes it 2803 easier to remember the order and avoids ambiguity between two int-like params anyway. 2804 2805 See_Also: 2806 [SimpleWindow.eventLoop], [EventLoop] 2807 2808 Bugs: 2809 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 2810 +/ 2811 final int eventLoopWithBlockingMode(T...)( 2812 BlockingMode blockingMode, /// when you want this function to block until 2813 long pulseTimeout, /// set to zero if you don't want a pulse. 2814 T eventHandlers) /// delegate list like std.concurrency.receive 2815 { 2816 setEventHandlers(eventHandlers); 2817 2818 version(with_eventloop) { 2819 // delegates event loop to my other module 2820 version(X11) 2821 XFlush(display); 2822 2823 import arsd.eventloop; 2824 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2825 scope(exit) clearInterval(handle); 2826 2827 loop(); 2828 return 0; 2829 } else version(OSXCocoa) { 2830 // FIXME 2831 if (handlePulse !is null && pulseTimeout != 0) { 2832 timer = scheduledTimer(pulseTimeout*1e-3, 2833 view, sel_registerName("simpledisplay_pulse"), 2834 null, true); 2835 } 2836 2837 setNeedsDisplay(view, true); 2838 run(NSApp); 2839 return 0; 2840 } else { 2841 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2842 2843 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 2844 return 0; 2845 2846 return el.run( 2847 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 2848 null : 2849 &this.notClosed 2850 ); 2851 } 2852 } 2853 2854 /++ 2855 This lets you draw on the window (or its backing buffer) using basic 2856 2D primitives. 2857 2858 Be sure to call this in a limited scope because your changes will not 2859 actually appear on the window until ScreenPainter's destructor runs. 2860 2861 Returns: an instance of [ScreenPainter], which has the drawing methods 2862 on it to draw on this window. 2863 2864 Params: 2865 manualInvalidations = if you set this to true, you will need to 2866 set the invalid rectangle on the painter yourself. If false, it 2867 assumes the whole window has been redrawn each time you draw. 2868 2869 Only invalidated rectangles are blitted back to the window when 2870 the destructor runs. Doing this yourself can reduce flickering 2871 of child windows. 2872 2873 History: 2874 The `manualInvalidations` parameter overload was added on 2875 December 30, 2021 (dub v10.5) 2876 +/ 2877 ScreenPainter draw() { 2878 return draw(false); 2879 } 2880 /// ditto 2881 ScreenPainter draw(bool manualInvalidations) { 2882 return impl.getPainter(manualInvalidations); 2883 } 2884 2885 // This is here to implement the interface we use for various native handlers. 2886 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2887 2888 // maps native window handles to SimpleWindow instances, if there are any 2889 // you shouldn't need this, but it is public in case you do in a native event handler or something 2890 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2891 2892 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 2893 private int _virtualWidth; 2894 private int _virtualHeight; 2895 2896 /// Width of the window's drawable client area, in pixels. 2897 @scriptable 2898 final @property int width() const pure nothrow @safe @nogc { 2899 if(resizability == Resizability.automaticallyScaleIfPossible) 2900 return _virtualWidth; 2901 else 2902 return _width; 2903 } 2904 2905 /// Height of the window's drawable client area, in pixels. 2906 @scriptable 2907 final @property int height() const pure nothrow @safe @nogc { 2908 if(resizability == Resizability.automaticallyScaleIfPossible) 2909 return _virtualHeight; 2910 else 2911 return _height; 2912 } 2913 2914 /++ 2915 Returns the actual size of the window, bypassing the logical 2916 illusions of [Resizability.automaticallyScaleIfPossible]. 2917 2918 History: 2919 Added November 11, 2022 (dub v10.10) 2920 +/ 2921 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 2922 return Size(_width, _height); 2923 } 2924 2925 2926 private int _width; 2927 private int _height; 2928 2929 // HACK: making the best of some copy constructor woes with refcounting 2930 private ScreenPainterImplementation* activeScreenPainter_; 2931 2932 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2933 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2934 2935 private OpenGlOptions openglMode; 2936 private Resizability resizability; 2937 private WindowTypes windowType; 2938 private int customizationFlags; 2939 2940 /// `true` if OpenGL was initialized for this window. 2941 @property bool isOpenGL () const pure nothrow @safe @nogc { 2942 version(without_opengl) 2943 return false; 2944 else 2945 return (openglMode == OpenGlOptions.yes); 2946 } 2947 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2948 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2949 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2950 2951 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2952 /// to call this, as it's not recommended to share window between threads. 2953 void mtLock () { 2954 version(X11) { 2955 XLockDisplay(this.display); 2956 } 2957 } 2958 2959 /// "Unlock" 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 mtUnlock () { 2962 version(X11) { 2963 XUnlockDisplay(this.display); 2964 } 2965 } 2966 2967 /// Emit a beep to get user's attention. 2968 void beep () { 2969 version(X11) { 2970 XBell(this.display, 100); 2971 } else version(Windows) { 2972 MessageBeep(0xFFFFFFFF); 2973 } 2974 } 2975 2976 2977 2978 version(without_opengl) {} else { 2979 2980 /// 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`. 2981 void delegate() redrawOpenGlScene; 2982 2983 /// This will allow you to change OpenGL vsync state. 2984 final @property void vsync (bool wait) { 2985 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2986 version(X11) { 2987 setAsCurrentOpenGlContext(); 2988 glxSetVSync(display, impl.window, wait); 2989 } else version(Windows) { 2990 setAsCurrentOpenGlContext(); 2991 wglSetVSync(wait); 2992 } 2993 } 2994 2995 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 2996 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 2997 /// enough without waiting 'em to finish their frame business. 2998 bool useGLFinish = true; 2999 3000 // FIXME: it should schedule it for the end of the current iteration of the event loop... 3001 /// 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. 3002 void redrawOpenGlSceneNow() { 3003 version(X11) if (!this._visible) return; // no need to do this if window is invisible 3004 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3005 if(redrawOpenGlScene is null) 3006 return; 3007 3008 this.mtLock(); 3009 scope(exit) this.mtUnlock(); 3010 3011 this.setAsCurrentOpenGlContext(); 3012 3013 redrawOpenGlScene(); 3014 3015 this.swapOpenGlBuffers(); 3016 // 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. 3017 if (useGLFinish) glFinish(); 3018 } 3019 3020 private bool redrawOpenGlSceneSoonSet = false; 3021 private static class RedrawOpenGlSceneEvent { 3022 SimpleWindow w; 3023 this(SimpleWindow w) { this.w = w; } 3024 } 3025 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 3026 /++ 3027 Queues an opengl redraw as soon as the other pending events are cleared. 3028 +/ 3029 void redrawOpenGlSceneSoon() { 3030 if(redrawOpenGlScene is null) 3031 return; 3032 3033 if(!redrawOpenGlSceneSoonSet) { 3034 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3035 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3036 redrawOpenGlSceneSoonSet = true; 3037 } 3038 this.postEvent(redrawOpenGlSceneEvent, true); 3039 } 3040 3041 3042 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3043 void setAsCurrentOpenGlContext() { 3044 assert(openglMode == OpenGlOptions.yes); 3045 version(X11) { 3046 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3047 throw new Exception("glXMakeCurrent"); 3048 } else version(Windows) { 3049 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3050 if (!wglMakeCurrent(ghDC, ghRC)) 3051 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3052 } 3053 } 3054 3055 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3056 /// This doesn't throw, returning success flag instead. 3057 bool setAsCurrentOpenGlContextNT() nothrow { 3058 assert(openglMode == OpenGlOptions.yes); 3059 version(X11) { 3060 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3061 } else version(Windows) { 3062 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3063 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3064 } 3065 } 3066 3067 /// 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. 3068 /// This doesn't throw, returning success flag instead. 3069 bool releaseCurrentOpenGlContext() nothrow { 3070 assert(openglMode == OpenGlOptions.yes); 3071 version(X11) { 3072 return (glXMakeCurrent(display, 0, null) != 0); 3073 } else version(Windows) { 3074 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3075 return wglMakeCurrent(ghDC, null) ? true : false; 3076 } 3077 } 3078 3079 /++ 3080 simpledisplay always uses double buffering, usually automatically. This 3081 manually swaps the OpenGL buffers. 3082 3083 3084 You should not need to call this yourself because simpledisplay will do it 3085 for you after calling your `redrawOpenGlScene`. 3086 3087 Remember that this may throw an exception, which you can catch in a multithreaded 3088 application to keep your thread from dying from an unhandled exception. 3089 +/ 3090 void swapOpenGlBuffers() { 3091 assert(openglMode == OpenGlOptions.yes); 3092 version(X11) { 3093 if (!this._visible) return; // no need to do this if window is invisible 3094 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3095 glXSwapBuffers(display, impl.window); 3096 } else version(Windows) { 3097 SwapBuffers(ghDC); 3098 } 3099 } 3100 } 3101 3102 /++ 3103 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3104 3105 3106 --- 3107 auto window = new SimpleWindow(100, 100, "First title"); 3108 window.title = "A new title"; 3109 --- 3110 3111 You may call this function at any time. 3112 +/ 3113 @property void title(string title) { 3114 _title = title; 3115 version(OSXCocoa) throw new NotYetImplementedException(); else 3116 impl.setTitle(title); 3117 } 3118 3119 private string _title; 3120 3121 /// Gets the title 3122 @property string title() { 3123 if(_title is null) 3124 _title = getRealTitle(); 3125 return _title; 3126 } 3127 3128 /++ 3129 Get the title as set by the window manager. 3130 May not match what you attempted to set. 3131 +/ 3132 string getRealTitle() { 3133 static if(is(typeof(impl.getTitle()))) 3134 return impl.getTitle(); 3135 else 3136 return null; 3137 } 3138 3139 // don't use this generally it is not yet really released 3140 version(X11) 3141 @property Image secret_icon() { 3142 return secret_icon_inner; 3143 } 3144 private Image secret_icon_inner; 3145 3146 3147 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3148 @property void icon(MemoryImage icon) { 3149 if(icon is null) 3150 return; 3151 auto tci = icon.getAsTrueColorImage(); 3152 version(Windows) { 3153 winIcon = new WindowsIcon(icon); 3154 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3155 } else version(X11) { 3156 secret_icon_inner = Image.fromMemoryImage(icon); 3157 // FIXME: ensure this is correct 3158 auto display = XDisplayConnection.get; 3159 arch_ulong[] buffer; 3160 buffer ~= icon.width; 3161 buffer ~= icon.height; 3162 foreach(c; tci.imageData.colors) { 3163 arch_ulong b; 3164 b |= c.a << 24; 3165 b |= c.r << 16; 3166 b |= c.g << 8; 3167 b |= c.b; 3168 buffer ~= b; 3169 } 3170 3171 XChangeProperty( 3172 display, 3173 impl.window, 3174 GetAtom!("_NET_WM_ICON", true)(display), 3175 GetAtom!"CARDINAL"(display), 3176 32 /* bits */, 3177 0 /*PropModeReplace*/, 3178 buffer.ptr, 3179 cast(int) buffer.length); 3180 } else version(OSXCocoa) { 3181 throw new NotYetImplementedException(); 3182 } else static assert(0); 3183 } 3184 3185 version(Windows) 3186 private WindowsIcon winIcon; 3187 3188 bool _suppressDestruction; 3189 3190 ~this() { 3191 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3192 if(_suppressDestruction) 3193 return; 3194 impl.dispose(); 3195 } 3196 3197 private bool _closed; 3198 3199 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3200 /* 3201 ScreenPainter drawTransiently() { 3202 return impl.getPainter(); 3203 } 3204 */ 3205 3206 /// Draws an image on the window. This is meant to provide quick look 3207 /// of a static image generated elsewhere. 3208 @property void image(Image i) { 3209 /+ 3210 version(Windows) { 3211 BITMAP bm; 3212 HDC hdc = GetDC(hwnd); 3213 HDC hdcMem = CreateCompatibleDC(hdc); 3214 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3215 3216 GetObject(i.handle, bm.sizeof, &bm); 3217 3218 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3219 3220 SelectObject(hdcMem, hbmOld); 3221 DeleteDC(hdcMem); 3222 ReleaseDC(hwnd, hdc); 3223 3224 /* 3225 RECT r; 3226 r.right = i.width; 3227 r.bottom = i.height; 3228 InvalidateRect(hwnd, &r, false); 3229 */ 3230 } else 3231 version(X11) { 3232 if(!destroyed) { 3233 if(i.usingXshm) 3234 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3235 else 3236 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3237 } 3238 } else 3239 version(OSXCocoa) { 3240 draw().drawImage(Point(0, 0), i); 3241 setNeedsDisplay(view, true); 3242 } else static assert(0); 3243 +/ 3244 auto painter = this.draw; 3245 painter.drawImage(Point(0, 0), i); 3246 } 3247 3248 /++ 3249 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3250 3251 --- 3252 window.cursor = GenericCursor.Help; 3253 // now the window mouse cursor is set to a generic help 3254 --- 3255 3256 +/ 3257 @property void cursor(MouseCursor cursor) { 3258 version(OSXCocoa) 3259 featureNotImplemented(); 3260 else 3261 if(this.impl.curHidden <= 0) { 3262 static if(UsingSimpledisplayX11) { 3263 auto ch = cursor.cursorHandle; 3264 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3265 } else version(Windows) { 3266 auto ch = cursor.cursorHandle; 3267 impl.currentCursor = ch; 3268 SetCursor(ch); // redraw without waiting for mouse movement to update 3269 } else featureNotImplemented(); 3270 } 3271 3272 } 3273 3274 /// What follows are the event handlers. These are set automatically 3275 /// by the eventLoop function, but are still public so you can change 3276 /// them later. wasPressed == true means key down. false == key up. 3277 3278 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3279 void delegate(KeyEvent ke) handleKeyEvent; 3280 3281 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3282 void delegate(dchar c) handleCharEvent; 3283 3284 /// Handles a timer pulse. Settable through setEventHandlers. 3285 void delegate() handlePulse; 3286 3287 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3288 void delegate(bool) onFocusChange; 3289 3290 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3291 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3292 void delegate() onClosing; 3293 3294 /** Called when we received destroy notification. At this stage we cannot do much with our window 3295 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3296 * last minute cleanup. */ 3297 void delegate() onDestroyed; 3298 3299 static if (UsingSimpledisplayX11) 3300 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3301 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3302 * You will probably never need to setup this handler, it is for very low-level stuff. 3303 * 3304 * WARNING! Xlib is multithread-locked when this handles is called! */ 3305 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3306 3307 //version(Windows) 3308 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3309 3310 private { 3311 int lastMouseX = int.min; 3312 int lastMouseY = int.min; 3313 void mdx(ref MouseEvent ev) { 3314 if(lastMouseX == int.min || lastMouseY == int.min) { 3315 ev.dx = 0; 3316 ev.dy = 0; 3317 } else { 3318 ev.dx = ev.x - lastMouseX; 3319 ev.dy = ev.y - lastMouseY; 3320 } 3321 3322 lastMouseX = ev.x; 3323 lastMouseY = ev.y; 3324 } 3325 } 3326 3327 /// Mouse event handler. Settable through setEventHandlers. 3328 void delegate(MouseEvent) handleMouseEvent; 3329 3330 /// use to redraw child widgets if you use system apis to add stuff 3331 void delegate() paintingFinished; 3332 3333 void delegate() paintingFinishedDg() { 3334 return paintingFinished; 3335 } 3336 3337 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3338 /// for this to ever happen. 3339 void delegate(int width, int height) windowResized; 3340 3341 /++ 3342 Platform specific - handle any native message this window gets. 3343 3344 Note: this is called *in addition to* other event handlers, unless you either: 3345 3346 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3347 3348 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. 3349 3350 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3351 3352 On X, it takes the form of `int delegate(XEvent)`. 3353 3354 History: 3355 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3356 3357 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. 3358 +/ 3359 NativeEventHandler handleNativeEvent_; 3360 3361 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3362 return handleNativeEvent_; 3363 } 3364 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3365 handleNativeEvent_ = neh; 3366 } 3367 3368 version(Windows) 3369 // compatibility shim with the old deprecated way 3370 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3371 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) { 3372 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3373 auto ret = dg(h, m, w, l); 3374 if(ret == 0) 3375 r = 1; 3376 return ret; 3377 }; 3378 } 3379 3380 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3381 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3382 /// this instead and it will work the same way. 3383 __gshared NativeEventHandler handleNativeGlobalEvent; 3384 3385 // private: 3386 /// The native implementation is available, but you shouldn't use it unless you are 3387 /// familiar with the underlying operating system, don't mind depending on it, and 3388 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3389 /// do what you need to do with handleNativeEvent instead. 3390 /// 3391 /// This is likely to eventually change to be just a struct holding platform-specific 3392 /// handles instead of a template mixin at some point because I'm not happy with the 3393 /// code duplication here (ironically). 3394 mixin NativeSimpleWindowImplementation!() impl; 3395 3396 /** 3397 This is in-process one-way (from anything to window) event sending mechanics. 3398 It is thread-safe, so it can be used in multi-threaded applications to send, 3399 for example, "wake up and repaint" events when thread completed some operation. 3400 This will allow to avoid using timer pulse to check events with synchronization, 3401 'cause event handler will be called in UI thread. You can stop guessing which 3402 pulse frequency will be enough for your app. 3403 Note that events handlers may be called in arbitrary order, i.e. last registered 3404 handler can be called first, and vice versa. 3405 */ 3406 public: 3407 /** Is our custom event queue empty? Can be used in simple cases to prevent 3408 * "spamming" window with events it can't cope with. 3409 * It is safe to call this from non-UI threads. 3410 */ 3411 @property bool eventQueueEmpty() () { 3412 synchronized(this) { 3413 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3414 } 3415 return true; 3416 } 3417 3418 /** Does our custom event queue contains at least one with the given type? 3419 * Can be used in simple cases to prevent "spamming" window with events 3420 * it can't cope with. 3421 * It is safe to call this from non-UI threads. 3422 */ 3423 @property bool eventQueued(ET:Object) () { 3424 synchronized(this) { 3425 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3426 if (!o.doProcess) { 3427 if (cast(ET)(o.evt)) return true; 3428 } 3429 } 3430 } 3431 return false; 3432 } 3433 3434 /++ 3435 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3436 3437 History: 3438 Added May 12, 2021 3439 +/ 3440 void delegate(Exception e) nothrow eventUncaughtException; 3441 3442 /** Add listener for custom event. Can be used like this: 3443 * 3444 * --------------------- 3445 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3446 * ... 3447 * win.removeEventListener(eid); 3448 * --------------------- 3449 * 3450 * Returns: 0 on failure (should never happen, so ignore it) 3451 * 3452 * $(WARNING Don't use this method in object destructors!) 3453 * 3454 * $(WARNING It is better to register all event handlers and don't remove 'em, 3455 * 'cause if event handler id counter will overflow, you won't be able 3456 * to register any more events.) 3457 */ 3458 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3459 if (dg is null) return 0; // ignore empty handlers 3460 synchronized(this) { 3461 //FIXME: abort on overflow? 3462 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3463 EventHandlerEntry e; 3464 e.dg = delegate (Object o) { 3465 if (auto co = cast(ET)o) { 3466 try { 3467 dg(co); 3468 } catch (Exception e) { 3469 // sorry! 3470 if(eventUncaughtException) 3471 eventUncaughtException(e); 3472 } 3473 return true; 3474 } 3475 return false; 3476 }; 3477 e.id = lastUsedHandlerId; 3478 auto optr = eventHandlers.ptr; 3479 eventHandlers ~= e; 3480 if (eventHandlers.ptr !is optr) { 3481 import core.memory : GC; 3482 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3483 } 3484 return lastUsedHandlerId; 3485 } 3486 } 3487 3488 /// Remove event listener. It is safe to pass invalid event id here. 3489 /// $(WARNING Don't use this method in object destructors!) 3490 void removeEventListener() (uint id) { 3491 if (id == 0 || id > lastUsedHandlerId) return; 3492 synchronized(this) { 3493 foreach (immutable idx; 0..eventHandlers.length) { 3494 if (eventHandlers[idx].id == id) { 3495 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3496 eventHandlers[$-1].dg = null; 3497 eventHandlers.length -= 1; 3498 eventHandlers.assumeSafeAppend; 3499 return; 3500 } 3501 } 3502 } 3503 } 3504 3505 /// Post event to queue. It is safe to call this from non-UI threads. 3506 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3507 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3508 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3509 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3510 if (this.closed) return false; // closed windows can't handle events 3511 3512 // remove all events of type `ET` 3513 void removeAllET () { 3514 uint eidx = 0, ec = eventQueueUsed; 3515 auto eptr = eventQueue.ptr; 3516 while (eidx < ec) { 3517 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3518 if (cast(ET)eptr.evt !is null) { 3519 // i found her! 3520 if (inCustomEventProcessor) { 3521 // if we're in custom event processing loop, processor will clear it for us 3522 eptr.evt = null; 3523 ++eidx; 3524 ++eptr; 3525 } else { 3526 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3527 ec = --eventQueueUsed; 3528 // clear last event (it is already copied) 3529 eventQueue.ptr[ec].evt = null; 3530 } 3531 } else { 3532 ++eidx; 3533 ++eptr; 3534 } 3535 } 3536 } 3537 3538 if (evt is null) { 3539 if (replace) { synchronized(this) removeAllET(); } 3540 // ignore empty events, they can't be handled anyway 3541 return false; 3542 } 3543 3544 // add events even if no event FD/event object created yet 3545 synchronized(this) { 3546 if (replace) removeAllET(); 3547 if (eventQueueUsed == uint.max) return false; // just in case 3548 if (eventQueueUsed < eventQueue.length) { 3549 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3550 } else { 3551 if (eventQueue.capacity == eventQueue.length) { 3552 // need to reallocate; do a trick to ensure that old array is cleared 3553 auto oarr = eventQueue; 3554 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3555 // just in case, do yet another check 3556 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3557 import core.memory : GC; 3558 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3559 } else { 3560 auto optr = eventQueue.ptr; 3561 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3562 assert(eventQueue.ptr is optr); 3563 } 3564 ++eventQueueUsed; 3565 assert(eventQueueUsed == eventQueue.length); 3566 } 3567 if (!eventWakeUp()) { 3568 // can't wake up event processor, so there is no reason to keep the event 3569 assert(eventQueueUsed > 0); 3570 eventQueue[--eventQueueUsed].evt = null; 3571 return false; 3572 } 3573 return true; 3574 } 3575 } 3576 3577 /// Post event to queue. It is safe to call this from non-UI threads. 3578 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3579 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3580 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3581 return postTimeout!ET(evt, 0, replace); 3582 } 3583 3584 private: 3585 private import core.time : MonoTime; 3586 3587 version(Posix) { 3588 __gshared int customEventFDRead = -1; 3589 __gshared int customEventFDWrite = -1; 3590 __gshared int customSignalFD = -1; 3591 } else version(Windows) { 3592 __gshared HANDLE customEventH = null; 3593 } 3594 3595 // wake up event processor 3596 static bool eventWakeUp () { 3597 version(X11) { 3598 import core.sys.posix.unistd : write; 3599 ulong n = 1; 3600 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3601 return true; 3602 } else version(Windows) { 3603 if (customEventH !is null) SetEvent(customEventH); 3604 return true; 3605 } else { 3606 // not implemented for other OSes 3607 return false; 3608 } 3609 } 3610 3611 static struct QueuedEvent { 3612 Object evt; 3613 bool timed = false; 3614 MonoTime hittime = MonoTime.zero; 3615 bool doProcess = false; // process event at the current iteration (internal flag) 3616 3617 this (Object aevt, uint toutmsecs) { 3618 evt = aevt; 3619 if (toutmsecs > 0) { 3620 import core.time : msecs; 3621 timed = true; 3622 hittime = MonoTime.currTime+toutmsecs.msecs; 3623 } 3624 } 3625 } 3626 3627 alias CustomEventHandler = bool delegate (Object o) nothrow; 3628 static struct EventHandlerEntry { 3629 CustomEventHandler dg; 3630 uint id; 3631 } 3632 3633 uint lastUsedHandlerId; 3634 EventHandlerEntry[] eventHandlers; 3635 QueuedEvent[] eventQueue = null; 3636 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3637 bool inCustomEventProcessor = false; // required to properly remove events 3638 3639 // process queued events and call custom event handlers 3640 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3641 void processCustomEvents () { 3642 bool hasSomethingToDo = false; 3643 uint ecount; 3644 bool ocep; 3645 synchronized(this) { 3646 ocep = inCustomEventProcessor; 3647 inCustomEventProcessor = true; 3648 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3649 auto ctt = MonoTime.currTime; 3650 bool hasEmpty = false; 3651 // mark events to process (this is required for `eventQueued()`) 3652 foreach (ref qe; eventQueue[0..ecount]) { 3653 if (qe.evt is null) { hasEmpty = true; continue; } 3654 if (qe.timed) { 3655 qe.doProcess = (qe.hittime <= ctt); 3656 } else { 3657 qe.doProcess = true; 3658 } 3659 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3660 } 3661 if (!hasSomethingToDo) { 3662 // remove empty events 3663 if (hasEmpty) { 3664 uint eidx = 0, ec = eventQueueUsed; 3665 auto eptr = eventQueue.ptr; 3666 while (eidx < ec) { 3667 if (eptr.evt is null) { 3668 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3669 ec = --eventQueueUsed; 3670 eventQueue.ptr[ec].evt = null; // make GC life easier 3671 } else { 3672 ++eidx; 3673 ++eptr; 3674 } 3675 } 3676 } 3677 inCustomEventProcessor = ocep; 3678 return; 3679 } 3680 } 3681 // process marked events 3682 uint efree = 0; // non-processed events will be put at this index 3683 EventHandlerEntry[] eh; 3684 Object evt; 3685 foreach (immutable eidx; 0..ecount) { 3686 synchronized(this) { 3687 if (!eventQueue[eidx].doProcess) { 3688 // skip this event 3689 assert(efree <= eidx); 3690 if (efree != eidx) { 3691 // copy this event to queue start 3692 eventQueue[efree] = eventQueue[eidx]; 3693 eventQueue[eidx].evt = null; // just in case 3694 } 3695 ++efree; 3696 continue; 3697 } 3698 evt = eventQueue[eidx].evt; 3699 eventQueue[eidx].evt = null; // in case event handler will hit GC 3700 if (evt is null) continue; // just in case 3701 // try all handlers; this can be slow, but meh... 3702 eh = eventHandlers; 3703 } 3704 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3705 evt = null; 3706 eh = null; 3707 } 3708 synchronized(this) { 3709 // move all unprocessed events to queue top; efree holds first "free index" 3710 foreach (immutable eidx; ecount..eventQueueUsed) { 3711 assert(efree <= eidx); 3712 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3713 ++efree; 3714 } 3715 eventQueueUsed = efree; 3716 // wake up event processor on next event loop iteration if we have more queued events 3717 // also, remove empty events 3718 bool awaken = false; 3719 uint eidx = 0, ec = eventQueueUsed; 3720 auto eptr = eventQueue.ptr; 3721 while (eidx < ec) { 3722 if (eptr.evt is null) { 3723 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3724 ec = --eventQueueUsed; 3725 eventQueue.ptr[ec].evt = null; // make GC life easier 3726 } else { 3727 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3728 ++eidx; 3729 ++eptr; 3730 } 3731 } 3732 inCustomEventProcessor = ocep; 3733 } 3734 } 3735 3736 // for all windows in nativeMapping 3737 package static void processAllCustomEvents () { 3738 3739 cleanupQueue.process(); 3740 3741 justCommunication.processCustomEvents(); 3742 3743 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3744 if (sw is null || sw.closed) continue; 3745 sw.processCustomEvents(); 3746 } 3747 3748 runPendingRunInGuiThreadDelegates(); 3749 } 3750 3751 // 0: infinite (i.e. no scheduled events in queue) 3752 uint eventQueueTimeoutMSecs () { 3753 synchronized(this) { 3754 if (eventQueueUsed == 0) return 0; 3755 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3756 uint res = int.max; 3757 auto ctt = MonoTime.currTime; 3758 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3759 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3760 if (qe.doProcess) continue; // just in case 3761 if (!qe.timed) return 1; // minimal 3762 if (qe.hittime <= ctt) return 1; // minimal 3763 auto tms = (qe.hittime-ctt).total!"msecs"; 3764 if (tms < 1) tms = 1; // safety net 3765 if (tms >= int.max) tms = int.max-1; // and another safety net 3766 if (res > tms) res = cast(uint)tms; 3767 } 3768 return (res >= int.max ? 0 : res); 3769 } 3770 } 3771 3772 // for all windows in nativeMapping 3773 static uint eventAllQueueTimeoutMSecs () { 3774 uint res = uint.max; 3775 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3776 if (sw is null || sw.closed) continue; 3777 uint to = sw.eventQueueTimeoutMSecs(); 3778 if (to && to < res) { 3779 res = to; 3780 if (to == 1) break; // can't have less than this 3781 } 3782 } 3783 return (res >= int.max ? 0 : res); 3784 } 3785 3786 version(X11) { 3787 ResizeEvent pendingResizeEvent; 3788 } 3789 3790 /++ 3791 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 3792 3793 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 3794 worth so you can disable it by setting this to `true`. 3795 3796 History: 3797 Added November 13, 2022. 3798 +/ 3799 public bool suppressAutoOpenglViewport = false; 3800 private void updateOpenglViewportIfNeeded(int width, int height) { 3801 if(suppressAutoOpenglViewport) return; 3802 3803 version(without_opengl) {} else 3804 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 3805 // writeln(width, " ", height); 3806 setAsCurrentOpenGlContextNT(); 3807 glViewport(0, 0, width, height); 3808 } 3809 } 3810 } 3811 3812 /++ 3813 Magic pseudo-window for just posting events to a global queue. 3814 3815 Not entirely supported, I might delete it at any time. 3816 3817 Added Nov 5, 2021. 3818 +/ 3819 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init); 3820 3821 /* Drag and drop support { */ 3822 version(X11) { 3823 3824 } else version(Windows) { 3825 import core.sys.windows.uuid; 3826 import core.sys.windows.ole2; 3827 import core.sys.windows.oleidl; 3828 import core.sys.windows.objidl; 3829 import core.sys.windows.wtypes; 3830 3831 pragma(lib, "ole32"); 3832 void initDnd() { 3833 auto err = OleInitialize(null); 3834 if(err != S_OK && err != S_FALSE) 3835 throw new Exception("init");//err); 3836 } 3837 } 3838 /* } End drag and drop support */ 3839 3840 3841 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 3842 /// See [GenericCursor]. 3843 class MouseCursor { 3844 int osId; 3845 bool isStockCursor; 3846 private this(int osId) { 3847 this.osId = osId; 3848 this.isStockCursor = true; 3849 } 3850 3851 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 3852 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 3853 3854 version(Windows) { 3855 HCURSOR cursor_; 3856 HCURSOR cursorHandle() { 3857 if(cursor_ is null) 3858 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 3859 return cursor_; 3860 } 3861 3862 } else static if(UsingSimpledisplayX11) { 3863 Cursor cursor_ = None; 3864 int xDisplaySequence; 3865 3866 Cursor cursorHandle() { 3867 if(this.osId == None) 3868 return None; 3869 3870 // we need to reload if we on a new X connection 3871 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 3872 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 3873 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 3874 } 3875 return cursor_; 3876 } 3877 } 3878 } 3879 3880 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 3881 // https://tronche.com/gui/x/xlib/appendix/b/ 3882 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 3883 /// 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. 3884 enum GenericCursorType { 3885 Default, /// The default arrow pointer. 3886 Wait, /// A cursor indicating something is loading and the user must wait. 3887 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3888 Help, /// A cursor indicating the user can get help about the pointer location. 3889 Cross, /// A crosshair. 3890 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3891 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 3892 UpArrow, /// An arrow pointing straight up. 3893 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3894 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3895 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 3896 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 3897 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 3898 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 3899 3900 } 3901 3902 /* 3903 X_plus == css cell == Windows ? 3904 */ 3905 3906 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3907 static struct GenericCursor { 3908 static: 3909 /// 3910 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3911 static MouseCursor mc; 3912 3913 auto type = __traits(getMember, GenericCursorType, str); 3914 3915 if(mc is null) { 3916 3917 version(Windows) { 3918 int osId; 3919 final switch(type) { 3920 case GenericCursorType.Default: osId = IDC_ARROW; break; 3921 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3922 case GenericCursorType.Hand: osId = IDC_HAND; break; 3923 case GenericCursorType.Help: osId = IDC_HELP; break; 3924 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3925 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3926 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3927 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3928 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3929 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3930 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3931 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3932 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3933 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3934 } 3935 } else static if(UsingSimpledisplayX11) { 3936 int osId; 3937 final switch(type) { 3938 case GenericCursorType.Default: osId = None; break; 3939 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3940 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3941 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3942 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3943 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3944 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3945 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3946 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3947 3948 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3949 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3950 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3951 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3952 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3953 } 3954 3955 } else featureNotImplemented(); 3956 3957 mc = new MouseCursor(osId); 3958 } 3959 return mc; 3960 } 3961 } 3962 3963 3964 /++ 3965 If you want to get more control over the event loop, you can use this. 3966 3967 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 3968 to `EventLoop.get.run`. 3969 +/ 3970 struct EventLoop { 3971 @disable this(); 3972 3973 /// Gets a reference to an existing event loop 3974 static EventLoop get() { 3975 return EventLoop(0, null); 3976 } 3977 3978 static void quitApplication() { 3979 EventLoop.get().exit(); 3980 } 3981 3982 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 3983 3984 /// Construct an application-global event loop for yourself 3985 /// See_Also: [SimpleWindow.setEventHandlers] 3986 this(long pulseTimeout, void delegate() handlePulse) { 3987 synchronized(monitor) { 3988 if(impl is null) { 3989 claimGuiThread(); 3990 version(sdpy_thread_checks) assert(thisIsGuiThread); 3991 impl = new EventLoopImpl(pulseTimeout, handlePulse); 3992 } else { 3993 if(pulseTimeout) { 3994 impl.pulseTimeout = pulseTimeout; 3995 impl.handlePulse = handlePulse; 3996 } 3997 } 3998 impl.refcount++; 3999 } 4000 } 4001 4002 ~this() { 4003 if(impl is null) 4004 return; 4005 impl.refcount--; 4006 if(impl.refcount == 0) { 4007 impl.dispose(); 4008 if(thisIsGuiThread) 4009 guiThreadFinalize(); 4010 } 4011 4012 } 4013 4014 this(this) { 4015 if(impl is null) 4016 return; 4017 impl.refcount++; 4018 } 4019 4020 /// Runs the event loop until the whileCondition, if present, returns false 4021 int run(bool delegate() whileCondition = null) { 4022 assert(impl !is null); 4023 impl.notExited = true; 4024 return impl.run(whileCondition); 4025 } 4026 4027 /// Exits the event loop 4028 void exit() { 4029 assert(impl !is null); 4030 impl.notExited = false; 4031 } 4032 4033 version(linux) 4034 ref void delegate(int) signalHandler() { 4035 assert(impl !is null); 4036 return impl.signalHandler; 4037 } 4038 4039 __gshared static EventLoopImpl* impl; 4040 } 4041 4042 version(linux) 4043 void delegate(int, int) globalHupHandler; 4044 4045 version(Posix) 4046 void makeNonBlocking(int fd) { 4047 import fcntl = core.sys.posix.fcntl; 4048 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4049 if(flags == -1) 4050 throw new Exception("fcntl get"); 4051 flags |= fcntl.O_NONBLOCK; 4052 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4053 if(s == -1) 4054 throw new Exception("fcntl set"); 4055 } 4056 4057 struct EventLoopImpl { 4058 int refcount; 4059 4060 bool notExited = true; 4061 4062 version(linux) { 4063 static import ep = core.sys.linux.epoll; 4064 static import unix = core.sys.posix.unistd; 4065 static import err = core.stdc.errno; 4066 import core.sys.linux.timerfd; 4067 4068 void delegate(int) signalHandler; 4069 } 4070 4071 version(X11) { 4072 int pulseFd = -1; 4073 version(linux) ep.epoll_event[16] events = void; 4074 } else version(Windows) { 4075 Timer pulser; 4076 HANDLE[] handles; 4077 } 4078 4079 4080 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4081 /// to call this, as it's not recommended to share window between threads. 4082 void mtLock () { 4083 version(X11) { 4084 XLockDisplay(this.display); 4085 } 4086 } 4087 4088 version(X11) 4089 auto display() { return XDisplayConnection.get; } 4090 4091 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4092 /// to call this, as it's not recommended to share window between threads. 4093 void mtUnlock () { 4094 version(X11) { 4095 XUnlockDisplay(this.display); 4096 } 4097 } 4098 4099 version(with_eventloop) 4100 void initialize(long pulseTimeout) {} 4101 else 4102 void initialize(long pulseTimeout) { 4103 version(Windows) { 4104 if(pulseTimeout && handlePulse !is null) 4105 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4106 4107 if (customEventH is null) { 4108 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4109 if (customEventH !is null) { 4110 handles ~= customEventH; 4111 } else { 4112 // this is something that should not be; better be safe than sorry 4113 throw new Exception("can't create eventfd for custom event processing"); 4114 } 4115 } 4116 4117 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4118 } 4119 4120 version(linux) { 4121 prepareEventLoop(); 4122 { 4123 auto display = XDisplayConnection.get; 4124 // adding Xlib file 4125 ep.epoll_event ev = void; 4126 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4127 ev.events = ep.EPOLLIN; 4128 ev.data.fd = display.fd; 4129 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4130 throw new Exception("add x fd");// ~ to!string(epollFd)); 4131 displayFd = display.fd; 4132 } 4133 4134 if(pulseTimeout && handlePulse !is null) { 4135 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4136 if(pulseFd == -1) 4137 throw new Exception("pulse timer create failed"); 4138 4139 itimerspec value; 4140 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4141 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4142 4143 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4144 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4145 4146 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4147 throw new Exception("couldn't make pulse timer"); 4148 4149 ep.epoll_event ev = void; 4150 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4151 ev.events = ep.EPOLLIN; 4152 ev.data.fd = pulseFd; 4153 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4154 } 4155 4156 // eventfd for custom events 4157 if (customEventFDWrite == -1) { 4158 customEventFDWrite = eventfd(0, 0); 4159 customEventFDRead = customEventFDWrite; 4160 if (customEventFDRead >= 0) { 4161 ep.epoll_event ev = void; 4162 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4163 ev.events = ep.EPOLLIN; 4164 ev.data.fd = customEventFDRead; 4165 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4166 } else { 4167 // this is something that should not be; better be safe than sorry 4168 throw new Exception("can't create eventfd for custom event processing"); 4169 } 4170 } 4171 4172 if (customSignalFD == -1) { 4173 import core.sys.linux.sys.signalfd; 4174 4175 sigset_t sigset; 4176 auto err = sigemptyset(&sigset); 4177 assert(!err); 4178 err = sigaddset(&sigset, SIGINT); 4179 assert(!err); 4180 err = sigaddset(&sigset, SIGHUP); 4181 assert(!err); 4182 err = sigprocmask(SIG_BLOCK, &sigset, null); 4183 assert(!err); 4184 4185 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4186 assert(customSignalFD != -1); 4187 4188 ep.epoll_event ev = void; 4189 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4190 ev.events = ep.EPOLLIN; 4191 ev.data.fd = customSignalFD; 4192 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4193 } 4194 } else version(Posix) { 4195 prepareEventLoop(); 4196 if (customEventFDRead == -1) { 4197 int[2] bfr; 4198 import core.sys.posix.unistd; 4199 auto ret = pipe(bfr); 4200 if(ret == -1) throw new Exception("pipe"); 4201 customEventFDRead = bfr[0]; 4202 customEventFDWrite = bfr[1]; 4203 } 4204 4205 } 4206 4207 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4208 4209 version(linux) { 4210 this.mtLock(); 4211 scope(exit) this.mtUnlock(); 4212 XPending(display); // no, really 4213 } 4214 4215 disposed = false; 4216 } 4217 4218 bool disposed = true; 4219 version(X11) 4220 int displayFd = -1; 4221 4222 version(with_eventloop) 4223 void dispose() {} 4224 else 4225 void dispose() { 4226 disposed = true; 4227 version(X11) { 4228 if(pulseFd != -1) { 4229 import unix = core.sys.posix.unistd; 4230 unix.close(pulseFd); 4231 pulseFd = -1; 4232 } 4233 4234 version(linux) 4235 if(displayFd != -1) { 4236 // 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 4237 ep.epoll_event ev = void; 4238 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4239 ev.events = ep.EPOLLIN; 4240 ev.data.fd = displayFd; 4241 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4242 displayFd = -1; 4243 } 4244 4245 } else version(Windows) { 4246 if(pulser !is null) { 4247 pulser.destroy(); 4248 pulser = null; 4249 } 4250 if (customEventH !is null) { 4251 CloseHandle(customEventH); 4252 customEventH = null; 4253 } 4254 } 4255 } 4256 4257 this(long pulseTimeout, void delegate() handlePulse) { 4258 this.pulseTimeout = pulseTimeout; 4259 this.handlePulse = handlePulse; 4260 initialize(pulseTimeout); 4261 } 4262 4263 private long pulseTimeout; 4264 void delegate() handlePulse; 4265 4266 ~this() { 4267 dispose(); 4268 } 4269 4270 version(Posix) 4271 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4272 version(Posix) 4273 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4274 version(linux) 4275 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4276 version(Windows) 4277 ref auto customEventH() { return SimpleWindow.customEventH; } 4278 4279 version(with_eventloop) { 4280 int loopHelper(bool delegate() whileCondition) { 4281 // FIXME: whileCondition 4282 import arsd.eventloop; 4283 loop(); 4284 return 0; 4285 } 4286 } else 4287 int loopHelper(bool delegate() whileCondition) { 4288 version(X11) { 4289 bool done = false; 4290 4291 XFlush(display); 4292 insideXEventLoop = true; 4293 scope(exit) insideXEventLoop = false; 4294 4295 version(linux) { 4296 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4297 bool forceXPending = false; 4298 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4299 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4300 { 4301 this.mtLock(); 4302 scope(exit) this.mtUnlock(); 4303 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 4304 } 4305 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4306 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4307 if(nfds == -1) { 4308 if(err.errno == err.EINTR) { 4309 //if(forceXPending) goto xpending; 4310 continue; // interrupted by signal, just try again 4311 } 4312 throw new Exception("epoll wait failure"); 4313 } 4314 // writeln(nfds, " ", events[0].data.fd); 4315 4316 SimpleWindow.processAllCustomEvents(); // anyway 4317 //version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4318 foreach(idx; 0 .. nfds) { 4319 if(done) break; 4320 auto fd = events[idx].data.fd; 4321 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4322 auto flags = events[idx].events; 4323 if(flags & ep.EPOLLIN) { 4324 if (fd == customSignalFD) { 4325 version(linux) { 4326 import core.sys.linux.sys.signalfd; 4327 import core.sys.posix.unistd : read; 4328 signalfd_siginfo info; 4329 read(customSignalFD, &info, info.sizeof); 4330 4331 auto sig = info.ssi_signo; 4332 4333 if(EventLoop.get.signalHandler !is null) { 4334 EventLoop.get.signalHandler()(sig); 4335 } else { 4336 EventLoop.get.exit(); 4337 } 4338 } 4339 } else if(fd == display.fd) { 4340 version(sdddd) { writeln("X EVENT PENDING!"); } 4341 this.mtLock(); 4342 scope(exit) this.mtUnlock(); 4343 while(!done && XPending(display)) { 4344 done = doXNextEvent(this.display); 4345 } 4346 forceXPending = false; 4347 } else if(fd == pulseFd) { 4348 long expirationCount; 4349 // 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... 4350 4351 handlePulse(); 4352 4353 // read just to clear the buffer so poll doesn't trigger again 4354 // BTW I read AFTER the pulse because if the pulse handler takes 4355 // a lot of time to execute, we don't want the app to get stuck 4356 // in a loop of timer hits without a chance to do anything else 4357 // 4358 // IOW handlePulse happens at most once per pulse interval. 4359 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4360 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 4361 } else if (fd == customEventFDRead) { 4362 // we have some custom events; process 'em 4363 import core.sys.posix.unistd : read; 4364 ulong n; 4365 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4366 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4367 //SimpleWindow.processAllCustomEvents(); 4368 4369 forceXPending = true; 4370 } else { 4371 // some other timer 4372 version(sdddd) { writeln("unknown fd: ", fd); } 4373 4374 if(Timer* t = fd in Timer.mapping) 4375 (*t).trigger(); 4376 4377 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4378 (*pfr).ready(flags); 4379 4380 // or i might add support for other FDs too 4381 // but for now it is just timer 4382 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4383 } 4384 } 4385 if(flags & ep.EPOLLHUP) { 4386 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4387 (*pfr).hup(flags); 4388 if(globalHupHandler) 4389 globalHupHandler(fd, flags); 4390 } 4391 /+ 4392 } else { 4393 // not interested in OUT, we are just reading here. 4394 // 4395 // error or hup might also be reported 4396 // but it shouldn't here since we are only 4397 // using a few types of FD and Xlib will report 4398 // if it dies. 4399 // so instead of thoughtfully handling it, I'll 4400 // just throw. for now at least 4401 4402 throw new Exception("epoll did something else"); 4403 } 4404 +/ 4405 } 4406 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4407 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4408 xpending: 4409 if (!done && forceXPending) { 4410 this.mtLock(); 4411 scope(exit) this.mtUnlock(); 4412 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4413 while(!done && XPending(display)) { 4414 done = doXNextEvent(this.display); 4415 } 4416 } 4417 } 4418 } else { 4419 // Generic fallback: yes to simple pulse support, 4420 // but NO timer support! 4421 4422 // FIXME: we could probably support the POSIX timer_create 4423 // signal-based option, but I'm in no rush to write it since 4424 // I prefer the fd-based functions. 4425 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4426 4427 import core.sys.posix.poll; 4428 4429 pollfd[] pfds; 4430 pollfd[32] pfdsBuffer; 4431 auto len = PosixFdReader.mapping.length + 2; 4432 // FIXME: i should just reuse the buffer 4433 if(len < pfdsBuffer.length) 4434 pfds = pfdsBuffer[0 .. len]; 4435 else 4436 pfds = new pollfd[](len); 4437 4438 pfds[0].fd = display.fd; 4439 pfds[0].events = POLLIN; 4440 pfds[0].revents = 0; 4441 4442 int slot = 1; 4443 4444 if(customEventFDRead != -1) { 4445 pfds[slot].fd = customEventFDRead; 4446 pfds[slot].events = POLLIN; 4447 pfds[slot].revents = 0; 4448 4449 slot++; 4450 } 4451 4452 foreach(fd, obj; PosixFdReader.mapping) { 4453 if(!obj.enabled) continue; 4454 pfds[slot].fd = fd; 4455 pfds[slot].events = POLLIN; 4456 pfds[slot].revents = 0; 4457 4458 slot++; 4459 } 4460 4461 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4462 if(ret == -1) throw new Exception("poll"); 4463 4464 if(ret == 0) { 4465 // FIXME it may not necessarily time out if events keep coming 4466 if(handlePulse !is null) 4467 handlePulse(); 4468 } else { 4469 foreach(s; 0 .. slot) { 4470 if(pfds[s].revents == 0) continue; 4471 4472 if(pfds[s].fd == display.fd) { 4473 while(!done && XPending(display)) { 4474 this.mtLock(); 4475 scope(exit) this.mtUnlock(); 4476 done = doXNextEvent(this.display); 4477 } 4478 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4479 4480 import core.sys.posix.unistd : read; 4481 ulong n; 4482 read(customEventFDRead, &n, n.sizeof); 4483 SimpleWindow.processAllCustomEvents(); 4484 } else { 4485 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4486 if(pfds[s].revents & POLLNVAL) { 4487 obj.dispose(); 4488 } else { 4489 obj.ready(pfds[s].revents); 4490 } 4491 } 4492 4493 ret--; 4494 if(ret == 0) break; 4495 } 4496 } 4497 } 4498 } 4499 } 4500 4501 version(Windows) { 4502 int ret = -1; 4503 MSG message; 4504 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4505 eventLoopRound++; 4506 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4507 auto waitResult = MsgWaitForMultipleObjectsEx( 4508 cast(int) handles.length, handles.ptr, 4509 (wto == 0 ? INFINITE : wto), /* timeout */ 4510 0x04FF, /* QS_ALLINPUT */ 4511 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4512 4513 SimpleWindow.processAllCustomEvents(); // anyway 4514 enum WAIT_OBJECT_0 = 0; 4515 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4516 auto h = handles[waitResult - WAIT_OBJECT_0]; 4517 if(auto e = h in WindowsHandleReader.mapping) { 4518 (*e).ready(); 4519 } 4520 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4521 // message ready 4522 int count; 4523 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 4524 ret = GetMessage(&message, null, 0, 0); 4525 if(ret == -1) 4526 throw new WindowsApiException("GetMessage", GetLastError()); 4527 TranslateMessage(&message); 4528 DispatchMessage(&message); 4529 4530 count++; 4531 if(count > 10) 4532 break; // take the opportunity to catch up on other events 4533 4534 if(ret == 0) { // WM_QUIT 4535 EventLoop.quitApplication(); 4536 break; 4537 } 4538 } 4539 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4540 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4541 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4542 // timeout, should never happen since we aren't using it 4543 } else if(waitResult == 0xFFFFFFFF) { 4544 // failed 4545 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 4546 } else { 4547 // idk.... 4548 } 4549 } 4550 4551 // return message.wParam; 4552 return 0; 4553 } else { 4554 return 0; 4555 } 4556 } 4557 4558 int run(bool delegate() whileCondition = null) { 4559 if(disposed) 4560 initialize(this.pulseTimeout); 4561 4562 version(X11) { 4563 try { 4564 return loopHelper(whileCondition); 4565 } catch(XDisconnectException e) { 4566 if(e.userRequested) { 4567 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4568 item.discardConnectionState(); 4569 XCloseDisplay(XDisplayConnection.display); 4570 } 4571 4572 XDisplayConnection.display = null; 4573 4574 this.dispose(); 4575 4576 throw e; 4577 } 4578 } else { 4579 return loopHelper(whileCondition); 4580 } 4581 } 4582 } 4583 4584 4585 /++ 4586 Provides an icon on the system notification area (also known as the system tray). 4587 4588 4589 If a notification area is not available with the NotificationIcon object is created, 4590 it will silently succeed and simply attempt to create one when an area becomes available. 4591 4592 4593 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. 4594 If this is wrong, pass -version=WindowsXP to dmd when compiling and it will 4595 use the older version. 4596 +/ 4597 version(OSXCocoa) {} else // NotYetImplementedException 4598 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4599 4600 version(X11) { 4601 void recreateAfterDisconnect() { 4602 stateDiscarded = false; 4603 clippixmap = None; 4604 throw new Exception("NOT IMPLEMENTED"); 4605 } 4606 4607 bool stateDiscarded; 4608 void discardConnectionState() { 4609 stateDiscarded = true; 4610 } 4611 } 4612 4613 4614 version(X11) { 4615 Image img; 4616 4617 NativeEventHandler getNativeEventHandler() { 4618 return delegate int(XEvent e) { 4619 switch(e.type) { 4620 case EventType.Expose: 4621 //case EventType.VisibilityNotify: 4622 redraw(); 4623 break; 4624 case EventType.ClientMessage: 4625 version(sddddd) { 4626 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4627 writeln("\t", e.xclient.format); 4628 writeln("\t", e.xclient.data.l); 4629 } 4630 break; 4631 case EventType.ButtonPress: 4632 auto event = e.xbutton; 4633 if (onClick !is null || onClickEx !is null) { 4634 MouseButton mb = cast(MouseButton)0; 4635 switch (event.button) { 4636 case 1: mb = MouseButton.left; break; // left 4637 case 2: mb = MouseButton.middle; break; // middle 4638 case 3: mb = MouseButton.right; break; // right 4639 case 4: mb = MouseButton.wheelUp; break; // scroll up 4640 case 5: mb = MouseButton.wheelDown; break; // scroll down 4641 case 6: break; // scroll left... 4642 case 7: break; // scroll right... 4643 case 8: mb = MouseButton.backButton; break; 4644 case 9: mb = MouseButton.forwardButton; break; 4645 default: 4646 } 4647 if (mb) { 4648 try { onClick()(mb); } catch (Exception) {} 4649 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 4650 } 4651 } 4652 break; 4653 case EventType.EnterNotify: 4654 if (onEnter !is null) { 4655 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 4656 } 4657 break; 4658 case EventType.LeaveNotify: 4659 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 4660 break; 4661 case EventType.DestroyNotify: 4662 active = false; 4663 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 4664 break; 4665 case EventType.ConfigureNotify: 4666 auto event = e.xconfigure; 4667 this.width = event.width; 4668 this.height = event.height; 4669 // writeln(width, " x " , height, " @ ", event.x, " ", event.y); 4670 redraw(); 4671 break; 4672 default: return 1; 4673 } 4674 return 1; 4675 }; 4676 } 4677 4678 /* private */ void hideBalloon() { 4679 balloon.close(); 4680 version(with_timer) 4681 timer.destroy(); 4682 balloon = null; 4683 version(with_timer) 4684 timer = null; 4685 } 4686 4687 void redraw() { 4688 if (!active) return; 4689 4690 auto display = XDisplayConnection.get; 4691 auto gc = DefaultGC(display, DefaultScreen(display)); 4692 XClearWindow(display, nativeHandle); 4693 4694 XSetClipMask(display, gc, clippixmap); 4695 4696 XSetForeground(display, gc, 4697 cast(uint) 0 << 16 | 4698 cast(uint) 0 << 8 | 4699 cast(uint) 0); 4700 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 4701 4702 if (img is null) { 4703 XSetForeground(display, gc, 4704 cast(uint) 0 << 16 | 4705 cast(uint) 127 << 8 | 4706 cast(uint) 0); 4707 XFillArc(display, nativeHandle, 4708 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 4709 } else { 4710 int dx = 0; 4711 int dy = 0; 4712 if(width > img.width) 4713 dx = (width - img.width) / 2; 4714 if(height > img.height) 4715 dy = (height - img.height) / 2; 4716 XSetClipOrigin(display, gc, dx, dy); 4717 4718 if (img.usingXshm) 4719 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false); 4720 else 4721 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height); 4722 } 4723 XSetClipMask(display, gc, None); 4724 flushGui(); 4725 } 4726 4727 static Window getTrayOwner() { 4728 auto display = XDisplayConnection.get; 4729 auto i = cast(int) DefaultScreen(display); 4730 if(i < 10 && i >= 0) { 4731 static Atom atom; 4732 if(atom == None) 4733 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 4734 return XGetSelectionOwner(display, atom); 4735 } 4736 return None; 4737 } 4738 4739 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 4740 auto to = getTrayOwner(); 4741 auto display = XDisplayConnection.get; 4742 XEvent ev; 4743 ev.xclient.type = EventType.ClientMessage; 4744 ev.xclient.window = to; 4745 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 4746 ev.xclient.format = 32; 4747 ev.xclient.data.l[0] = CurrentTime; 4748 ev.xclient.data.l[1] = message; 4749 ev.xclient.data.l[2] = d1; 4750 ev.xclient.data.l[3] = d2; 4751 ev.xclient.data.l[4] = d3; 4752 4753 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 4754 } 4755 4756 private static NotificationAreaIcon[] activeIcons; 4757 4758 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 4759 private void newManager() { 4760 close(); 4761 createXWin(); 4762 4763 if(this.clippixmap) 4764 XFreePixmap(XDisplayConnection.get, clippixmap); 4765 if(this.originalMemoryImage) 4766 this.icon = this.originalMemoryImage; 4767 else if(this.img) 4768 this.icon = this.img; 4769 } 4770 4771 private void createXWin () { 4772 // create window 4773 auto display = XDisplayConnection.get; 4774 4775 // to check for MANAGER on root window to catch new/changed tray owners 4776 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 4777 // so if a thing does appear, we can handle it 4778 foreach(ai; activeIcons) 4779 if(ai is this) 4780 goto alreadythere; 4781 activeIcons ~= this; 4782 alreadythere: 4783 4784 // and check for an existing tray 4785 auto trayOwner = getTrayOwner(); 4786 if(trayOwner == None) 4787 return; 4788 //throw new Exception("No notification area found"); 4789 4790 Visual* v = cast(Visual*) CopyFromParent; 4791 /+ 4792 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 4793 if(visualProp !is null) { 4794 c_ulong[] info = cast(c_ulong[]) visualProp; 4795 if(info.length == 1) { 4796 auto vid = info[0]; 4797 int returned; 4798 XVisualInfo t; 4799 t.visualid = vid; 4800 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 4801 if(got !is null) { 4802 if(returned == 1) { 4803 v = got.visual; 4804 writeln("using special visual ", *got); 4805 } 4806 XFree(got); 4807 } 4808 } 4809 } 4810 +/ 4811 4812 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null); 4813 assert(nativeWindow); 4814 4815 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4816 4817 nativeHandle = nativeWindow; 4818 4819 ///+ 4820 arch_ulong[2] info; 4821 info[0] = 0; 4822 info[1] = 1; 4823 4824 string title = this.name is null ? "simpledisplay.d program" : this.name; 4825 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4826 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4827 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4828 4829 XChangeProperty( 4830 display, 4831 nativeWindow, 4832 GetAtom!("_XEMBED_INFO", true)(display), 4833 GetAtom!("_XEMBED_INFO", true)(display), 4834 32 /* bits */, 4835 0 /*PropModeReplace*/, 4836 info.ptr, 4837 2); 4838 4839 import core.sys.posix.unistd; 4840 arch_ulong pid = getpid(); 4841 4842 XChangeProperty( 4843 display, 4844 nativeWindow, 4845 GetAtom!("_NET_WM_PID", true)(display), 4846 XA_CARDINAL, 4847 32 /* bits */, 4848 0 /*PropModeReplace*/, 4849 &pid, 4850 1); 4851 4852 updateNetWmIcon(); 4853 4854 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4855 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4856 XClassHint klass; 4857 XWMHints wh; 4858 XSizeHints size; 4859 klass.res_name = sdpyWindowClassStr; 4860 klass.res_class = sdpyWindowClassStr; 4861 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4862 } 4863 4864 // believe it or not, THIS is what xfce needed for the 9999 issue 4865 XSizeHints sh; 4866 c_long spr; 4867 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4868 sh.flags |= PMaxSize | PMinSize; 4869 // FIXME maybe nicer resizing 4870 sh.min_width = 16; 4871 sh.min_height = 16; 4872 sh.max_width = 16; 4873 sh.max_height = 16; 4874 XSetWMNormalHints(display, nativeWindow, &sh); 4875 4876 4877 //+/ 4878 4879 4880 XSelectInput(display, nativeWindow, 4881 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 4882 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 4883 4884 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 4885 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 4886 active = true; 4887 } 4888 4889 void updateNetWmIcon() { 4890 if(img is null) return; 4891 auto display = XDisplayConnection.get; 4892 // FIXME: ensure this is correct 4893 arch_ulong[] buffer; 4894 auto imgMi = img.toTrueColorImage; 4895 buffer ~= imgMi.width; 4896 buffer ~= imgMi.height; 4897 foreach(c; imgMi.imageData.colors) { 4898 arch_ulong b; 4899 b |= c.a << 24; 4900 b |= c.r << 16; 4901 b |= c.g << 8; 4902 b |= c.b; 4903 buffer ~= b; 4904 } 4905 4906 XChangeProperty( 4907 display, 4908 nativeHandle, 4909 GetAtom!"_NET_WM_ICON"(display), 4910 GetAtom!"CARDINAL"(display), 4911 32 /* bits */, 4912 0 /*PropModeReplace*/, 4913 buffer.ptr, 4914 cast(int) buffer.length); 4915 } 4916 4917 4918 4919 private SimpleWindow balloon; 4920 version(with_timer) 4921 private Timer timer; 4922 4923 private Window nativeHandle; 4924 private Pixmap clippixmap = None; 4925 private int width = 16; 4926 private int height = 16; 4927 private bool active = false; 4928 4929 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 4930 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 4931 void delegate () onLeave; /// X11 only. 4932 4933 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 4934 4935 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 4936 void getWindowRect (out int x, out int y, out int width, out int height) { 4937 if (!active) { width = 1; height = 1; return; } // 1: just in case 4938 Window dummyw; 4939 auto dpy = XDisplayConnection.get; 4940 //XWindowAttributes xwa; 4941 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 4942 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 4943 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 4944 width = this.width; 4945 height = this.height; 4946 } 4947 } 4948 4949 /+ 4950 What I actually want from this: 4951 4952 * set / change: icon, tooltip 4953 * handle: mouse click, right click 4954 * show: notification bubble. 4955 +/ 4956 4957 version(Windows) { 4958 WindowsIcon win32Icon; 4959 HWND hwnd; 4960 4961 NOTIFYICONDATAW data; 4962 4963 NativeEventHandler getNativeEventHandler() { 4964 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 4965 if(msg == WM_USER) { 4966 auto event = LOWORD(lParam); 4967 auto iconId = HIWORD(lParam); 4968 //auto x = GET_X_LPARAM(wParam); 4969 //auto y = GET_Y_LPARAM(wParam); 4970 switch(event) { 4971 case WM_LBUTTONDOWN: 4972 onClick()(MouseButton.left); 4973 break; 4974 case WM_RBUTTONDOWN: 4975 onClick()(MouseButton.right); 4976 break; 4977 case WM_MBUTTONDOWN: 4978 onClick()(MouseButton.middle); 4979 break; 4980 case WM_MOUSEMOVE: 4981 // sent, we could use it. 4982 break; 4983 case WM_MOUSEWHEEL: 4984 // NOT SENT 4985 break; 4986 //case NIN_KEYSELECT: 4987 //case NIN_SELECT: 4988 //break; 4989 default: {} 4990 } 4991 } 4992 return 0; 4993 }; 4994 } 4995 4996 enum NIF_SHOWTIP = 0x00000080; 4997 4998 private static struct NOTIFYICONDATAW { 4999 DWORD cbSize; 5000 HWND hWnd; 5001 UINT uID; 5002 UINT uFlags; 5003 UINT uCallbackMessage; 5004 HICON hIcon; 5005 WCHAR[128] szTip; 5006 DWORD dwState; 5007 DWORD dwStateMask; 5008 WCHAR[256] szInfo; 5009 union { 5010 UINT uTimeout; 5011 UINT uVersion; 5012 } 5013 WCHAR[64] szInfoTitle; 5014 DWORD dwInfoFlags; 5015 GUID guidItem; 5016 HICON hBalloonIcon; 5017 } 5018 5019 } 5020 5021 /++ 5022 Note that on Windows, only left, right, and middle buttons are sent. 5023 Mouse wheel buttons are NOT set, so don't rely on those events if your 5024 program is meant to be used on Windows too. 5025 +/ 5026 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 5027 // The canonical constructor for Windows needs the MemoryImage, so it is here, 5028 // but on X, we need an Image, so its canonical ctor is there. They should 5029 // forward to each other though. 5030 version(X11) { 5031 this.name = name; 5032 this.onClick = onClick; 5033 createXWin(); 5034 this.icon = icon; 5035 } else version(Windows) { 5036 this.onClick = onClick; 5037 this.win32Icon = new WindowsIcon(icon); 5038 5039 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5040 5041 static bool registered = false; 5042 if(!registered) { 5043 WNDCLASSEX wc; 5044 wc.cbSize = wc.sizeof; 5045 wc.hInstance = hInstance; 5046 wc.lpfnWndProc = &WndProc; 5047 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5048 if(!RegisterClassExW(&wc)) 5049 throw new WindowsApiException("RegisterClass", GetLastError()); 5050 registered = true; 5051 } 5052 5053 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5054 if(hwnd is null) 5055 throw new WindowsApiException("CreateWindow", GetLastError()); 5056 5057 data.cbSize = data.sizeof; 5058 data.hWnd = hwnd; 5059 data.uID = cast(uint) cast(void*) this; 5060 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5061 // NIF_INFO means show balloon 5062 data.uCallbackMessage = WM_USER; 5063 data.hIcon = this.win32Icon.hIcon; 5064 data.szTip = ""; // FIXME 5065 data.dwState = 0; // NIS_HIDDEN; // windows vista 5066 data.dwStateMask = NIS_HIDDEN; // windows vista 5067 5068 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5069 5070 5071 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5072 5073 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5074 } else version(OSXCocoa) { 5075 throw new NotYetImplementedException(); 5076 } else static assert(0); 5077 } 5078 5079 /// ditto 5080 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5081 version(X11) { 5082 this.onClick = onClick; 5083 this.name = name; 5084 createXWin(); 5085 this.icon = icon; 5086 } else version(Windows) { 5087 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5088 } else version(OSXCocoa) { 5089 throw new NotYetImplementedException(); 5090 } else static assert(0); 5091 } 5092 5093 version(X11) { 5094 /++ 5095 X-specific extension (for now at least) 5096 +/ 5097 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5098 this.onClickEx = onClickEx; 5099 createXWin(); 5100 if (icon !is null) this.icon = icon; 5101 } 5102 5103 /// ditto 5104 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5105 this.onClickEx = onClickEx; 5106 createXWin(); 5107 this.icon = icon; 5108 } 5109 } 5110 5111 private void delegate (MouseButton button) onClick_; 5112 5113 /// 5114 @property final void delegate(MouseButton) onClick() { 5115 if(onClick_ is null) 5116 onClick_ = delegate void(MouseButton) {}; 5117 return onClick_; 5118 } 5119 5120 /// ditto 5121 @property final void onClick(void delegate(MouseButton) handler) { 5122 // I made this a property setter so we can wrap smaller arg 5123 // delegates and just forward all to onClickEx or something. 5124 onClick_ = handler; 5125 } 5126 5127 5128 string name_; 5129 @property void name(string n) { 5130 name_ = n; 5131 } 5132 5133 @property string name() { 5134 return name_; 5135 } 5136 5137 private MemoryImage originalMemoryImage; 5138 5139 /// 5140 @property void icon(MemoryImage i) { 5141 version(X11) { 5142 this.originalMemoryImage = i; 5143 if (!active) return; 5144 if (i !is null) { 5145 this.img = Image.fromMemoryImage(i); 5146 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5147 // writeln("using pixmap ", clippixmap); 5148 updateNetWmIcon(); 5149 redraw(); 5150 } else { 5151 if (this.img !is null) { 5152 this.img = null; 5153 redraw(); 5154 } 5155 } 5156 } else version(Windows) { 5157 this.win32Icon = new WindowsIcon(i); 5158 5159 data.uFlags = NIF_ICON; 5160 data.hIcon = this.win32Icon.hIcon; 5161 5162 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5163 } else version(OSXCocoa) { 5164 throw new NotYetImplementedException(); 5165 } else static assert(0); 5166 } 5167 5168 /// ditto 5169 @property void icon (Image i) { 5170 version(X11) { 5171 if (!active) return; 5172 if (i !is img) { 5173 originalMemoryImage = null; 5174 img = i; 5175 redraw(); 5176 } 5177 } else version(Windows) { 5178 this.icon(i is null ? null : i.toTrueColorImage()); 5179 } else version(OSXCocoa) { 5180 throw new NotYetImplementedException(); 5181 } else static assert(0); 5182 } 5183 5184 /++ 5185 Shows a balloon notification. You can only show one balloon at a time, if you call 5186 it twice while one is already up, the first balloon will be replaced. 5187 5188 5189 The user is free to block notifications and they will automatically disappear after 5190 a timeout period. 5191 5192 Params: 5193 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5194 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5195 icon = the icon to display with the notification. If null, it uses your existing icon. 5196 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5197 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5198 +/ 5199 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5200 bool useCustom = true; 5201 version(libnotify) { 5202 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5203 try { 5204 if(!active) return; 5205 5206 if(libnotify is null) { 5207 libnotify = new C_DynamicLibrary("libnotify.so"); 5208 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5209 } 5210 5211 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5212 5213 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5214 5215 if(onclick) { 5216 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5217 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); 5218 libnotify_action_delegates_count++; 5219 } 5220 5221 // FIXME icon 5222 5223 // set hint image-data 5224 // set default action for onclick 5225 5226 void* error; 5227 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5228 5229 useCustom = false; 5230 } catch(Exception e) { 5231 5232 } 5233 } 5234 5235 version(X11) { 5236 if(useCustom) { 5237 if(!active) return; 5238 if(balloon) { 5239 hideBalloon(); 5240 } 5241 // I know there are two specs for this, but one is never 5242 // implemented by any window manager I have ever seen, and 5243 // the other is a bloated mess and too complicated for simpledisplay... 5244 // so doing my own little window instead. 5245 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5246 5247 int x, y, width, height; 5248 getWindowRect(x, y, width, height); 5249 5250 int bx = x - balloon.width; 5251 int by = y - balloon.height; 5252 if(bx < 0) 5253 bx = x + width + balloon.width; 5254 if(by < 0) 5255 by = y + height; 5256 5257 // just in case, make sure it is actually on scren 5258 if(bx < 0) 5259 bx = 0; 5260 if(by < 0) 5261 by = 0; 5262 5263 balloon.move(bx, by); 5264 auto painter = balloon.draw(); 5265 painter.fillColor = Color(220, 220, 220); 5266 painter.outlineColor = Color.black; 5267 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5268 auto iconWidth = icon is null ? 0 : icon.width; 5269 if(icon) 5270 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5271 iconWidth += 6; // margin around the icon 5272 5273 // draw a close button 5274 painter.outlineColor = Color(44, 44, 44); 5275 painter.fillColor = Color(255, 255, 255); 5276 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5277 painter.pen = Pen(Color.black, 3); 5278 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5279 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5280 painter.pen = Pen(Color.black, 1); 5281 painter.fillColor = Color(220, 220, 220); 5282 5283 // Draw the title and message 5284 painter.drawText(Point(4 + iconWidth, 4), title); 5285 painter.drawLine( 5286 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5287 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5288 ); 5289 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5290 5291 balloon.setEventHandlers( 5292 (MouseEvent ev) { 5293 if(ev.type == MouseEventType.buttonPressed) { 5294 if(ev.x > balloon.width - 16 && ev.y < 16) 5295 hideBalloon(); 5296 else if(onclick) 5297 onclick(); 5298 } 5299 } 5300 ); 5301 balloon.show(); 5302 5303 version(with_timer) 5304 timer = new Timer(timeout, &hideBalloon); 5305 else {} // FIXME 5306 } 5307 } else version(Windows) { 5308 enum NIF_INFO = 0x00000010; 5309 5310 data.uFlags = NIF_INFO; 5311 5312 // FIXME: go back to the last valid unicode code point 5313 if(title.length > 40) 5314 title = title[0 .. 40]; 5315 if(message.length > 220) 5316 message = message[0 .. 220]; 5317 5318 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5319 enum NIIF_LARGE_ICON = 0x00000020; 5320 enum NIIF_NOSOUND = 0x00000010; 5321 enum NIIF_USER = 0x00000004; 5322 enum NIIF_ERROR = 0x00000003; 5323 enum NIIF_WARNING = 0x00000002; 5324 enum NIIF_INFO = 0x00000001; 5325 enum NIIF_NONE = 0; 5326 5327 WCharzBuffer t = WCharzBuffer(title); 5328 WCharzBuffer m = WCharzBuffer(message); 5329 5330 t.copyInto(data.szInfoTitle); 5331 m.copyInto(data.szInfo); 5332 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5333 5334 if(icon !is null) { 5335 auto i = new WindowsIcon(icon); 5336 data.hBalloonIcon = i.hIcon; 5337 data.dwInfoFlags |= NIIF_USER; 5338 } 5339 5340 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5341 } else version(OSXCocoa) { 5342 throw new NotYetImplementedException(); 5343 } else static assert(0); 5344 } 5345 5346 /// 5347 //version(Windows) 5348 void show() { 5349 version(X11) { 5350 if(!hidden) 5351 return; 5352 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5353 hidden = false; 5354 } else version(Windows) { 5355 data.uFlags = NIF_STATE; 5356 data.dwState = 0; // NIS_HIDDEN; // windows vista 5357 data.dwStateMask = NIS_HIDDEN; // windows vista 5358 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5359 } else version(OSXCocoa) { 5360 throw new NotYetImplementedException(); 5361 } else static assert(0); 5362 } 5363 5364 version(X11) 5365 bool hidden = false; 5366 5367 /// 5368 //version(Windows) 5369 void hide() { 5370 version(X11) { 5371 if(hidden) 5372 return; 5373 hidden = true; 5374 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5375 } else version(Windows) { 5376 data.uFlags = NIF_STATE; 5377 data.dwState = NIS_HIDDEN; // windows vista 5378 data.dwStateMask = NIS_HIDDEN; // windows vista 5379 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5380 } else version(OSXCocoa) { 5381 throw new NotYetImplementedException(); 5382 } else static assert(0); 5383 } 5384 5385 /// 5386 void close () { 5387 version(X11) { 5388 if (active) { 5389 active = false; // event handler will set this too, but meh 5390 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5391 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5392 flushGui(); 5393 } 5394 } else version(Windows) { 5395 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5396 } else version(OSXCocoa) { 5397 throw new NotYetImplementedException(); 5398 } else static assert(0); 5399 } 5400 5401 ~this() { 5402 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5403 version(X11) 5404 if(clippixmap != None) 5405 XFreePixmap(XDisplayConnection.get, clippixmap); 5406 close(); 5407 } 5408 } 5409 5410 version(X11) 5411 /// Call `XFreePixmap` on the return value. 5412 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5413 char[] data = new char[](i.width * i.height / 8 + 2); 5414 data[] = 0; 5415 5416 int bitOffset = 0; 5417 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5418 ubyte v = c.a > 128 ? 1 : 0; 5419 data[bitOffset / 8] |= v << (bitOffset%8); 5420 bitOffset++; 5421 } 5422 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5423 return handle; 5424 } 5425 5426 5427 // basic functions to make timers 5428 /** 5429 A timer that will trigger your function on a given interval. 5430 5431 5432 You create a timer with an interval and a callback. It will continue 5433 to fire on the interval until it is destroyed. 5434 5435 There are currently no one-off timers (instead, just create one and 5436 destroy it when it is triggered) nor are there pause/resume functions - 5437 the timer must again be destroyed and recreated if you want to pause it. 5438 5439 --- 5440 auto timer = new Timer(50, { it happened!; }); 5441 timer.destroy(); 5442 --- 5443 5444 Timers can only be expected to fire when the event loop is running and only 5445 once per iteration through the event loop. 5446 5447 History: 5448 Prior to December 9, 2020, a timer pulse set too high with a handler too 5449 slow could lock up the event loop. It now guarantees other things will 5450 get a chance to run between timer calls, even if that means not keeping up 5451 with the requested interval. 5452 */ 5453 version(with_timer) { 5454 class Timer { 5455 // FIXME: needs pause and unpause 5456 // FIXME: I might add overloads for ones that take a count of 5457 // how many elapsed since last time (on Windows, it will divide 5458 // the ticks thing given, on Linux it is just available) and 5459 // maybe one that takes an instance of the Timer itself too 5460 /// Create a timer with a callback when it triggers. 5461 this(int intervalInMilliseconds, void delegate() onPulse) { 5462 assert(onPulse !is null); 5463 5464 this.intervalInMilliseconds = intervalInMilliseconds; 5465 this.onPulse = onPulse; 5466 5467 version(Windows) { 5468 /* 5469 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5470 if(handle == 0) 5471 throw new WindowsApiException("SetTimer", GetLastError()); 5472 */ 5473 5474 // thanks to Archival 998 for the WaitableTimer blocks 5475 handle = CreateWaitableTimer(null, false, null); 5476 long initialTime = -intervalInMilliseconds; 5477 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5478 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 5479 5480 mapping[handle] = this; 5481 5482 } else version(linux) { 5483 static import ep = core.sys.linux.epoll; 5484 5485 import core.sys.linux.timerfd; 5486 5487 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5488 if(fd == -1) 5489 throw new Exception("timer create failed"); 5490 5491 mapping[fd] = this; 5492 5493 itimerspec value; 5494 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5495 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5496 5497 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5498 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5499 5500 if(timerfd_settime(fd, 0, &value, null) == -1) 5501 throw new Exception("couldn't make pulse timer"); 5502 5503 version(with_eventloop) { 5504 import arsd.eventloop; 5505 addFileEventListeners(fd, &trigger, null, null); 5506 } else { 5507 prepareEventLoop(); 5508 5509 ep.epoll_event ev = void; 5510 ev.events = ep.EPOLLIN; 5511 ev.data.fd = fd; 5512 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5513 } 5514 } else featureNotImplemented(); 5515 } 5516 5517 private int intervalInMilliseconds; 5518 5519 // just cuz I sometimes call it this. 5520 alias dispose = destroy; 5521 5522 /// Stop and destroy the timer object. 5523 void destroy() { 5524 version(Windows) { 5525 staticDestroy(handle); 5526 handle = null; 5527 } else version(linux) { 5528 staticDestroy(fd); 5529 fd = -1; 5530 } else featureNotImplemented(); 5531 } 5532 5533 version(Windows) 5534 static void staticDestroy(HANDLE handle) { 5535 if(handle) { 5536 // KillTimer(null, handle); 5537 CancelWaitableTimer(cast(void*)handle); 5538 mapping.remove(handle); 5539 CloseHandle(handle); 5540 } 5541 } 5542 else version(linux) 5543 static void staticDestroy(int fd) { 5544 if(fd != -1) { 5545 import unix = core.sys.posix.unistd; 5546 static import ep = core.sys.linux.epoll; 5547 5548 version(with_eventloop) { 5549 import arsd.eventloop; 5550 removeFileEventListeners(fd); 5551 } else { 5552 ep.epoll_event ev = void; 5553 ev.events = ep.EPOLLIN; 5554 ev.data.fd = fd; 5555 5556 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5557 } 5558 unix.close(fd); 5559 mapping.remove(fd); 5560 } 5561 } 5562 5563 ~this() { 5564 version(Windows) { if(handle) 5565 cleanupQueue.queue!staticDestroy(handle); 5566 } else version(linux) { if(fd != -1) 5567 cleanupQueue.queue!staticDestroy(fd); 5568 } 5569 } 5570 5571 5572 void changeTime(int intervalInMilliseconds) 5573 { 5574 this.intervalInMilliseconds = intervalInMilliseconds; 5575 version(Windows) 5576 { 5577 if(handle) 5578 { 5579 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5580 long initialTime = -intervalInMilliseconds; 5581 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5582 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 5583 } 5584 } 5585 } 5586 5587 5588 private: 5589 5590 void delegate() onPulse; 5591 5592 int lastEventLoopRoundTriggered; 5593 5594 void trigger() { 5595 version(linux) { 5596 import unix = core.sys.posix.unistd; 5597 long val; 5598 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 5599 } else version(Windows) { 5600 if(this.lastEventLoopRoundTriggered == eventLoopRound) 5601 return; // never try to actually run faster than the event loop 5602 lastEventLoopRoundTriggered = eventLoopRound; 5603 } else featureNotImplemented(); 5604 5605 onPulse(); 5606 } 5607 5608 version(Windows) 5609 void rearm() { 5610 5611 } 5612 5613 version(Windows) 5614 extern(Windows) 5615 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 5616 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 5617 if(Timer* t = timer in mapping) { 5618 try 5619 (*t).trigger(); 5620 catch(Exception e) { sdpy_abort(e); assert(0); } 5621 } 5622 } 5623 5624 version(Windows) { 5625 //UINT_PTR handle; 5626 //static Timer[UINT_PTR] mapping; 5627 HANDLE handle; 5628 __gshared Timer[HANDLE] mapping; 5629 } else version(linux) { 5630 int fd = -1; 5631 __gshared Timer[int] mapping; 5632 } else static assert(0, "timer not supported"); 5633 } 5634 } 5635 5636 version(Windows) 5637 private int eventLoopRound; 5638 5639 version(Windows) 5640 /// 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 5641 class WindowsHandleReader { 5642 /// 5643 this(void delegate() onReady, HANDLE handle) { 5644 this.onReady = onReady; 5645 this.handle = handle; 5646 5647 mapping[handle] = this; 5648 5649 enable(); 5650 } 5651 5652 /// 5653 void enable() { 5654 auto el = EventLoop.get().impl; 5655 el.handles ~= handle; 5656 } 5657 5658 /// 5659 void disable() { 5660 auto el = EventLoop.get().impl; 5661 for(int i = 0; i < el.handles.length; i++) { 5662 if(el.handles[i] is handle) { 5663 el.handles[i] = el.handles[$-1]; 5664 el.handles = el.handles[0 .. $-1]; 5665 return; 5666 } 5667 } 5668 } 5669 5670 void dispose() { 5671 disable(); 5672 if(handle) 5673 mapping.remove(handle); 5674 handle = null; 5675 } 5676 5677 void ready() { 5678 if(onReady) 5679 onReady(); 5680 } 5681 5682 HANDLE handle; 5683 void delegate() onReady; 5684 5685 __gshared WindowsHandleReader[HANDLE] mapping; 5686 } 5687 5688 version(Posix) 5689 /// Lets you add files to the event loop for reading. Use at your own risk. 5690 class PosixFdReader { 5691 /// 5692 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5693 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 5694 } 5695 5696 /// 5697 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5698 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 5699 } 5700 5701 /// 5702 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5703 this.onReady = onReady; 5704 this.fd = fd; 5705 this.captureWrites = captureWrites; 5706 this.captureReads = captureReads; 5707 5708 mapping[fd] = this; 5709 5710 version(with_eventloop) { 5711 import arsd.eventloop; 5712 addFileEventListeners(fd, &readyel); 5713 } else { 5714 enable(); 5715 } 5716 } 5717 5718 bool captureReads; 5719 bool captureWrites; 5720 5721 version(with_eventloop) {} else 5722 /// 5723 void enable() { 5724 prepareEventLoop(); 5725 5726 enabled = true; 5727 5728 version(linux) { 5729 static import ep = core.sys.linux.epoll; 5730 ep.epoll_event ev = void; 5731 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5732 // writeln("enable ", fd, " ", captureReads, " ", captureWrites); 5733 ev.data.fd = fd; 5734 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5735 } else { 5736 5737 } 5738 } 5739 5740 version(with_eventloop) {} else 5741 /// 5742 void disable() { 5743 prepareEventLoop(); 5744 5745 enabled = false; 5746 5747 version(linux) { 5748 static import ep = core.sys.linux.epoll; 5749 ep.epoll_event ev = void; 5750 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5751 // writeln("disable ", fd, " ", captureReads, " ", captureWrites); 5752 ev.data.fd = fd; 5753 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5754 } 5755 } 5756 5757 version(with_eventloop) {} else 5758 /// 5759 void dispose() { 5760 if(enabled) 5761 disable(); 5762 if(fd != -1) 5763 mapping.remove(fd); 5764 fd = -1; 5765 } 5766 5767 void delegate(int, bool, bool) onReady; 5768 5769 version(with_eventloop) 5770 void readyel() { 5771 onReady(fd, true, true); 5772 } 5773 5774 void ready(uint flags) { 5775 version(linux) { 5776 static import ep = core.sys.linux.epoll; 5777 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 5778 } else { 5779 import core.sys.posix.poll; 5780 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 5781 } 5782 } 5783 5784 void hup(uint flags) { 5785 if(onHup) 5786 onHup(); 5787 } 5788 5789 void delegate() onHup; 5790 5791 int fd = -1; 5792 private bool enabled; 5793 __gshared PosixFdReader[int] mapping; 5794 } 5795 5796 // basic functions to access the clipboard 5797 /+ 5798 5799 5800 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 5801 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 5802 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5803 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 5804 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 5805 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5806 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 5807 5808 +/ 5809 5810 /++ 5811 this does a delegate because it is actually an async call on X... 5812 the receiver may never be called if the clipboard is empty or unavailable 5813 gets plain text from the clipboard. 5814 +/ 5815 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 5816 version(Windows) { 5817 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5818 if(OpenClipboard(hwndOwner) == 0) 5819 throw new WindowsApiException("OpenClipboard", GetLastError()); 5820 scope(exit) 5821 CloseClipboard(); 5822 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 5823 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 5824 5825 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 5826 scope(exit) 5827 GlobalUnlock(dataHandle); 5828 5829 // FIXME: CR/LF conversions 5830 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 5831 int len = 0; 5832 auto d = data; 5833 while(*d) { 5834 d++; 5835 len++; 5836 } 5837 string s; 5838 s.reserve(len); 5839 foreach(dchar ch; data[0 .. len]) { 5840 s ~= ch; 5841 } 5842 receiver(s); 5843 } 5844 } 5845 } else version(X11) { 5846 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5847 } else version(OSXCocoa) { 5848 throw new NotYetImplementedException(); 5849 } else static assert(0); 5850 } 5851 5852 // FIXME: a clipboard listener might be cool btw 5853 5854 /++ 5855 this does a delegate because it is actually an async call on X... 5856 the receiver may never be called if the clipboard is empty or unavailable 5857 gets image from the clipboard. 5858 5859 templated because it introduces an optional dependency on arsd.bmp 5860 +/ 5861 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 5862 version(Windows) { 5863 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5864 if(OpenClipboard(hwndOwner) == 0) 5865 throw new WindowsApiException("OpenClipboard", GetLastError()); 5866 scope(exit) 5867 CloseClipboard(); 5868 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 5869 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 5870 scope(exit) 5871 GlobalUnlock(dataHandle); 5872 5873 auto len = GlobalSize(dataHandle); 5874 5875 import arsd.bmp; 5876 auto img = readBmp(data[0 .. len], false); 5877 receiver(img); 5878 } 5879 } 5880 } else version(X11) { 5881 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5882 } else version(OSXCocoa) { 5883 throw new NotYetImplementedException(); 5884 } else static assert(0); 5885 } 5886 5887 /// Copies some text to the clipboard. 5888 void setClipboardText(SimpleWindow clipboardOwner, string text) { 5889 assert(clipboardOwner !is null); 5890 version(Windows) { 5891 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5892 throw new WindowsApiException("OpenClipboard", GetLastError()); 5893 scope(exit) 5894 CloseClipboard(); 5895 EmptyClipboard(); 5896 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5897 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 5898 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 5899 if(auto data = cast(wchar*) GlobalLock(handle)) { 5900 auto slice = data[0 .. sz]; 5901 scope(failure) 5902 GlobalUnlock(handle); 5903 5904 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5905 5906 GlobalUnlock(handle); 5907 SetClipboardData(CF_UNICODETEXT, handle); 5908 } 5909 } else version(X11) { 5910 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 5911 } else version(OSXCocoa) { 5912 throw new NotYetImplementedException(); 5913 } else static assert(0); 5914 } 5915 5916 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 5917 assert(clipboardOwner !is null); 5918 version(Windows) { 5919 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5920 throw new WindowsApiException("OpenClipboard", GetLastError()); 5921 scope(exit) 5922 CloseClipboard(); 5923 EmptyClipboard(); 5924 5925 5926 import arsd.bmp; 5927 ubyte[] mdata; 5928 mdata.reserve(img.width * img.height); 5929 void sink(ubyte b) { 5930 mdata ~= b; 5931 } 5932 writeBmpIndirect(img, &sink, false); 5933 5934 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 5935 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 5936 if(auto data = cast(ubyte*) GlobalLock(handle)) { 5937 auto slice = data[0 .. mdata.length]; 5938 scope(failure) 5939 GlobalUnlock(handle); 5940 5941 slice[] = mdata[]; 5942 5943 GlobalUnlock(handle); 5944 SetClipboardData(CF_DIB, handle); 5945 } 5946 } else version(X11) { 5947 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 5948 mixin X11SetSelectionHandler_Basics; 5949 private const(ubyte)[] mdata; 5950 private const(ubyte)[] mdata_original; 5951 this(MemoryImage img) { 5952 import arsd.bmp; 5953 5954 mdata.reserve(img.width * img.height); 5955 void sink(ubyte b) { 5956 mdata ~= b; 5957 } 5958 writeBmpIndirect(img, &sink, true); 5959 5960 mdata_original = mdata; 5961 } 5962 5963 Atom[] availableFormats() { 5964 auto display = XDisplayConnection.get; 5965 return [ 5966 GetAtom!"image/bmp"(display), 5967 GetAtom!"TARGETS"(display) 5968 ]; 5969 } 5970 5971 ubyte[] getData(Atom format, return scope ubyte[] data) { 5972 if(mdata.length < data.length) { 5973 data[0 .. mdata.length] = mdata[]; 5974 auto ret = data[0 .. mdata.length]; 5975 mdata = mdata[$..$]; 5976 return ret; 5977 } else { 5978 data[] = mdata[0 .. data.length]; 5979 mdata = mdata[data.length .. $]; 5980 return data[]; 5981 } 5982 } 5983 5984 void done() { 5985 mdata = mdata_original; 5986 } 5987 } 5988 5989 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 5990 } else version(OSXCocoa) { 5991 throw new NotYetImplementedException(); 5992 } else static assert(0); 5993 } 5994 5995 5996 version(X11) { 5997 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 5998 5999 private Atom*[] interredAtoms; // for discardAndRecreate 6000 6001 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 6002 /// Platform-specific for X11. 6003 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6004 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6005 static Atom a; 6006 if(!a) { 6007 a = XInternAtom(display, name, !create); 6008 interredAtoms ~= &a; 6009 } 6010 if(a == None) 6011 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6012 return a; 6013 } 6014 6015 /// Platform-specific for X11 - gets atom names as a string. 6016 string getAtomName(Atom atom, Display* display) { 6017 auto got = XGetAtomName(display, atom); 6018 scope(exit) XFree(got); 6019 import core.stdc.string; 6020 string s = got[0 .. strlen(got)].idup; 6021 return s; 6022 } 6023 6024 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6025 void setPrimarySelection(SimpleWindow window, string text) { 6026 setX11Selection!"PRIMARY"(window, text); 6027 } 6028 6029 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6030 void setSecondarySelection(SimpleWindow window, string text) { 6031 setX11Selection!"SECONDARY"(window, text); 6032 } 6033 6034 interface X11SetSelectionHandler { 6035 // should include TARGETS right now 6036 Atom[] availableFormats(); 6037 // Return the slice of data you filled, empty slice if done. 6038 // this is to support the incremental thing 6039 ubyte[] getData(Atom format, return scope ubyte[] data); 6040 6041 void done(); 6042 6043 void handleRequest(XEvent); 6044 6045 bool matchesIncr(Window, Atom); 6046 void sendMoreIncr(XPropertyEvent*); 6047 } 6048 6049 mixin template X11SetSelectionHandler_Basics() { 6050 Window incrWindow; 6051 Atom incrAtom; 6052 Atom selectionAtom; 6053 Atom formatAtom; 6054 ubyte[] toSend; 6055 bool matchesIncr(Window w, Atom a) { 6056 return incrAtom && incrAtom == a && w == incrWindow; 6057 } 6058 void sendMoreIncr(XPropertyEvent* event) { 6059 auto display = XDisplayConnection.get; 6060 6061 XChangeProperty (display, 6062 incrWindow, 6063 incrAtom, 6064 formatAtom, 6065 8 /* bits */, PropModeReplace, 6066 toSend.ptr, cast(int) toSend.length); 6067 6068 if(toSend.length != 0) { 6069 toSend = this.getData(formatAtom, toSend[]); 6070 } else { 6071 this.done(); 6072 incrWindow = None; 6073 incrAtom = None; 6074 selectionAtom = None; 6075 formatAtom = None; 6076 toSend = null; 6077 } 6078 } 6079 void handleRequest(XEvent ev) { 6080 6081 auto display = XDisplayConnection.get; 6082 6083 XSelectionRequestEvent* event = &ev.xselectionrequest; 6084 XSelectionEvent selectionEvent; 6085 selectionEvent.type = EventType.SelectionNotify; 6086 selectionEvent.display = event.display; 6087 selectionEvent.requestor = event.requestor; 6088 selectionEvent.selection = event.selection; 6089 selectionEvent.time = event.time; 6090 selectionEvent.target = event.target; 6091 6092 bool supportedType() { 6093 foreach(t; this.availableFormats()) 6094 if(t == event.target) 6095 return true; 6096 return false; 6097 } 6098 6099 if(event.property == None) { 6100 selectionEvent.property = event.target; 6101 6102 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6103 XFlush(display); 6104 } if(event.target == GetAtom!"TARGETS"(display)) { 6105 /* respond with the supported types */ 6106 auto tlist = this.availableFormats(); 6107 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6108 selectionEvent.property = event.property; 6109 6110 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6111 XFlush(display); 6112 } else if(supportedType()) { 6113 auto buffer = new ubyte[](1024 * 64); 6114 auto toSend = this.getData(event.target, buffer[]); 6115 6116 if(toSend.length < 32 * 1024) { 6117 // small enough to send directly... 6118 selectionEvent.property = event.property; 6119 XChangeProperty (display, 6120 selectionEvent.requestor, 6121 selectionEvent.property, 6122 event.target, 6123 8 /* bits */, 0 /* PropModeReplace */, 6124 toSend.ptr, cast(int) toSend.length); 6125 6126 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6127 XFlush(display); 6128 } else { 6129 // large, let's send incrementally 6130 arch_ulong l = toSend.length; 6131 6132 // if I wanted other events from this window don't want to clear that out.... 6133 XWindowAttributes xwa; 6134 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6135 6136 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6137 6138 incrWindow = event.requestor; 6139 incrAtom = event.property; 6140 formatAtom = event.target; 6141 selectionAtom = event.selection; 6142 this.toSend = toSend; 6143 6144 selectionEvent.property = event.property; 6145 XChangeProperty (display, 6146 selectionEvent.requestor, 6147 selectionEvent.property, 6148 GetAtom!"INCR"(display), 6149 32 /* bits */, PropModeReplace, 6150 &l, 1); 6151 6152 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6153 XFlush(display); 6154 } 6155 //if(after) 6156 //after(); 6157 } else { 6158 debug(sdpy_clip) { 6159 writeln("Unsupported data ", getAtomName(event.target, display)); 6160 } 6161 selectionEvent.property = None; // I don't know how to handle this type... 6162 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6163 XFlush(display); 6164 } 6165 } 6166 } 6167 6168 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6169 mixin X11SetSelectionHandler_Basics; 6170 private const(ubyte)[] text; 6171 private const(ubyte)[] text_original; 6172 this(string text) { 6173 this.text = cast(const ubyte[]) text; 6174 this.text_original = this.text; 6175 } 6176 Atom[] availableFormats() { 6177 auto display = XDisplayConnection.get; 6178 return [ 6179 GetAtom!"UTF8_STRING"(display), 6180 GetAtom!"text/plain"(display), 6181 XA_STRING, 6182 GetAtom!"TARGETS"(display) 6183 ]; 6184 } 6185 6186 ubyte[] getData(Atom format, return scope ubyte[] data) { 6187 if(text.length < data.length) { 6188 data[0 .. text.length] = text[]; 6189 return data[0 .. text.length]; 6190 } else { 6191 data[] = text[0 .. data.length]; 6192 text = text[data.length .. $]; 6193 return data[]; 6194 } 6195 } 6196 6197 void done() { 6198 text = text_original; 6199 } 6200 } 6201 6202 /// 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?!) 6203 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6204 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6205 } 6206 6207 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6208 assert(window !is null); 6209 6210 auto display = XDisplayConnection.get(); 6211 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6212 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6213 else Atom a = GetAtom!atomName(display); 6214 6215 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6216 6217 window.impl.setSelectionHandlers[a] = data; 6218 } 6219 6220 /// 6221 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6222 getX11Selection!"PRIMARY"(window, handler); 6223 } 6224 6225 // added July 28, 2020 6226 // undocumented as experimental tho 6227 interface X11GetSelectionHandler { 6228 void handleData(Atom target, in ubyte[] data); 6229 Atom findBestFormat(Atom[] answer); 6230 6231 void prepareIncremental(Window, Atom); 6232 bool matchesIncr(Window, Atom); 6233 void handleIncrData(Atom, in ubyte[] data); 6234 } 6235 6236 mixin template X11GetSelectionHandler_Basics() { 6237 Window incrWindow; 6238 Atom incrAtom; 6239 6240 void prepareIncremental(Window w, Atom a) { 6241 incrWindow = w; 6242 incrAtom = a; 6243 } 6244 bool matchesIncr(Window w, Atom a) { 6245 return incrWindow == w && incrAtom == a; 6246 } 6247 6248 Atom incrFormatAtom; 6249 ubyte[] incrData; 6250 void handleIncrData(Atom format, in ubyte[] data) { 6251 incrFormatAtom = format; 6252 6253 if(data.length) 6254 incrData ~= data; 6255 else 6256 handleData(incrFormatAtom, incrData); 6257 6258 } 6259 } 6260 6261 /// 6262 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6263 assert(window !is null); 6264 6265 auto display = XDisplayConnection.get(); 6266 auto atom = GetAtom!atomName(display); 6267 6268 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6269 this(void delegate(in char[]) handler) { 6270 this.handler = handler; 6271 } 6272 6273 mixin X11GetSelectionHandler_Basics; 6274 6275 void delegate(in char[]) handler; 6276 6277 void handleData(Atom target, in ubyte[] data) { 6278 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6279 handler(cast(const char[]) data); 6280 } 6281 6282 Atom findBestFormat(Atom[] answer) { 6283 Atom best = None; 6284 foreach(option; answer) { 6285 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6286 best = option; 6287 break; 6288 } else if(option == XA_STRING) { 6289 best = option; 6290 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6291 best = option; 6292 } 6293 } 6294 return best; 6295 } 6296 } 6297 6298 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6299 6300 auto target = GetAtom!"TARGETS"(display); 6301 6302 // SDD_DATA is "simpledisplay.d data" 6303 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6304 } 6305 6306 /// Gets the image on the clipboard, if there is one. Added July 2020. 6307 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6308 assert(window !is null); 6309 6310 auto display = XDisplayConnection.get(); 6311 auto atom = GetAtom!atomName(display); 6312 6313 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6314 this(void delegate(MemoryImage) handler) { 6315 this.handler = handler; 6316 } 6317 6318 mixin X11GetSelectionHandler_Basics; 6319 6320 void delegate(MemoryImage) handler; 6321 6322 void handleData(Atom target, in ubyte[] data) { 6323 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6324 import arsd.bmp; 6325 handler(readBmp(data)); 6326 } 6327 } 6328 6329 Atom findBestFormat(Atom[] answer) { 6330 Atom best = None; 6331 foreach(option; answer) { 6332 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6333 best = option; 6334 } 6335 } 6336 return best; 6337 } 6338 6339 } 6340 6341 6342 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6343 6344 auto target = GetAtom!"TARGETS"(display); 6345 6346 // SDD_DATA is "simpledisplay.d data" 6347 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6348 } 6349 6350 6351 /// 6352 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6353 Atom actualType; 6354 int actualFormat; 6355 arch_ulong actualItems; 6356 arch_ulong bytesRemaining; 6357 void* data; 6358 6359 auto display = XDisplayConnection.get(); 6360 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6361 if(actualFormat == 0) 6362 return null; 6363 else { 6364 int byteLength; 6365 if(actualFormat == 32) { 6366 // 32 means it is a C long... which is variable length 6367 actualFormat = cast(int) arch_long.sizeof * 8; 6368 } 6369 6370 // then it is just a bit count 6371 byteLength = cast(int) (actualItems * actualFormat / 8); 6372 6373 auto d = new ubyte[](byteLength); 6374 d[] = cast(ubyte[]) data[0 .. byteLength]; 6375 XFree(data); 6376 return d; 6377 } 6378 } 6379 return null; 6380 } 6381 6382 /* defined in the systray spec */ 6383 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6384 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6385 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6386 6387 6388 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6389 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6390 public class GlobalHotkey { 6391 KeyEvent key; 6392 void delegate () handler; 6393 6394 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6395 6396 /// Create from initialzed KeyEvent object 6397 this (KeyEvent akey, void delegate () ahandler=null) { 6398 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6399 key = akey; 6400 handler = ahandler; 6401 } 6402 6403 /// Create from emacs-like key name ("C-M-Y", etc.) 6404 this (const(char)[] akey, void delegate () ahandler=null) { 6405 key = KeyEvent.parse(akey); 6406 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6407 handler = ahandler; 6408 } 6409 6410 } 6411 6412 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6413 //conwriteln("failed to grab key"); 6414 GlobalHotkeyManager.ghfailed = true; 6415 return 0; 6416 } 6417 6418 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6419 Image.impl.xshmfailed = true; 6420 return 0; 6421 } 6422 6423 private __gshared int errorHappened; 6424 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6425 import core.stdc.stdio; 6426 char[265] buffer; 6427 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6428 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); 6429 errorHappened = true; 6430 return 0; 6431 } 6432 6433 /++ 6434 Global hotkey manager. It contains static methods to manage global hotkeys. 6435 6436 --- 6437 try { 6438 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6439 } catch (Exception e) { 6440 conwriteln("ERROR registering hotkey!"); 6441 } 6442 EventLoop.get.run(); 6443 --- 6444 6445 The key strings are based on Emacs. In practical terms, 6446 `M` means `alt` and `H` means the Windows logo key. `C` 6447 is `ctrl`. 6448 6449 $(WARNING 6450 This is X-specific right now. If you are on 6451 Windows, try [registerHotKey] instead. 6452 6453 We will probably merge these into a single 6454 interface later. 6455 ) 6456 +/ 6457 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6458 version(X11) { 6459 void recreateAfterDisconnect() { 6460 throw new Exception("NOT IMPLEMENTED"); 6461 } 6462 void discardConnectionState() { 6463 throw new Exception("NOT IMPLEMENTED"); 6464 } 6465 } 6466 6467 private static immutable uint[8] masklist = [ 0, 6468 KeyOrButtonMask.LockMask, 6469 KeyOrButtonMask.Mod2Mask, 6470 KeyOrButtonMask.Mod3Mask, 6471 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6472 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6473 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6474 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6475 ]; 6476 private __gshared GlobalHotkeyManager ghmanager; 6477 private __gshared bool ghfailed = false; 6478 6479 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6480 if (modmask == 0) return false; 6481 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6482 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6483 return true; 6484 } 6485 6486 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6487 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6488 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6489 return modmask; 6490 } 6491 6492 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 6493 uint keycode = cast(uint)ke.key; 6494 auto dpy = XDisplayConnection.get; 6495 return XKeysymToKeycode(dpy, keycode); 6496 } 6497 6498 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6499 6500 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6501 6502 NativeEventHandler getNativeEventHandler () { 6503 return delegate int (XEvent e) { 6504 if (e.type != EventType.KeyPress) return 1; 6505 auto kev = cast(const(XKeyEvent)*)&e; 6506 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6507 if (auto ghkp = hash in globalHotkeyList) { 6508 try { 6509 ghkp.doHandle(); 6510 } catch (Exception e) { 6511 import core.stdc.stdio : stderr, fprintf; 6512 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6513 } 6514 } 6515 return 1; 6516 }; 6517 } 6518 6519 private this () { 6520 auto dpy = XDisplayConnection.get; 6521 auto root = RootWindow(dpy, DefaultScreen(dpy)); 6522 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 6523 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 6524 } 6525 6526 /// Register new global hotkey with initialized `GlobalHotkey` object. 6527 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 6528 static void register (GlobalHotkey gh) { 6529 if (gh is null) return; 6530 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 6531 6532 auto dpy = XDisplayConnection.get; 6533 immutable keycode = keyEvent2KeyCode(gh.key); 6534 6535 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 6536 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 6537 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 6538 XSync(dpy, 0/*False*/); 6539 6540 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6541 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6542 ghfailed = false; 6543 foreach (immutable uint ormask; masklist[]) { 6544 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 6545 } 6546 XSync(dpy, 0/*False*/); 6547 XSetErrorHandler(savedErrorHandler); 6548 6549 if (ghfailed) { 6550 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6551 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 6552 XSync(dpy, 0/*False*/); 6553 XSetErrorHandler(savedErrorHandler); 6554 throw new Exception("cannot register global hotkey"); 6555 } 6556 6557 globalHotkeyList[hash] = gh; 6558 } 6559 6560 /// Ditto 6561 static void register (const(char)[] akey, void delegate () ahandler) { 6562 register(new GlobalHotkey(akey, ahandler)); 6563 } 6564 6565 private static void removeByHash (ulong hash) { 6566 if (auto ghp = hash in globalHotkeyList) { 6567 auto dpy = XDisplayConnection.get; 6568 immutable keycode = keyEvent2KeyCode(ghp.key); 6569 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6570 XSync(dpy, 0/*False*/); 6571 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6572 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 6573 XSync(dpy, 0/*False*/); 6574 XSetErrorHandler(savedErrorHandler); 6575 globalHotkeyList.remove(hash); 6576 } 6577 } 6578 6579 /// Register new global hotkey with previously used `GlobalHotkey` object. 6580 /// It is safe to unregister unknown or invalid hotkey. 6581 static void unregister (GlobalHotkey gh) { 6582 //TODO: add second AA for faster search? prolly doesn't worth it. 6583 if (gh is null) return; 6584 foreach (const ref kv; globalHotkeyList.byKeyValue) { 6585 if (kv.value is gh) { 6586 removeByHash(kv.key); 6587 return; 6588 } 6589 } 6590 } 6591 6592 /// Ditto. 6593 static void unregister (const(char)[] key) { 6594 auto kev = KeyEvent.parse(key); 6595 immutable keycode = keyEvent2KeyCode(kev); 6596 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 6597 } 6598 } 6599 } 6600 6601 version(Windows) { 6602 /++ 6603 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 6604 6605 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). 6606 +/ 6607 void sendSyntheticInput(wstring s) { 6608 INPUT[] inputs; 6609 inputs.reserve(s.length * 2); 6610 6611 foreach(wchar c; s) { 6612 INPUT input; 6613 input.type = INPUT_KEYBOARD; 6614 input.ki.wScan = c; 6615 input.ki.dwFlags = KEYEVENTF_UNICODE; 6616 inputs ~= input; 6617 6618 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6619 inputs ~= input; 6620 } 6621 6622 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6623 throw new WindowsApiException("SendInput", GetLastError()); 6624 } 6625 6626 } 6627 6628 6629 // global hotkey helper function 6630 6631 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 6632 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6633 __gshared int hotkeyId = 0; 6634 int id = ++hotkeyId; 6635 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6636 throw new Exception("RegisterHotKey"); 6637 6638 __gshared void delegate()[WPARAM][HWND] handlers; 6639 6640 handlers[window.impl.hwnd][id] = handler; 6641 6642 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 6643 6644 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 6645 switch(msg) { 6646 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6647 case WM_HOTKEY: 6648 if(auto list = hwnd in handlers) { 6649 if(auto h = wParam in *list) { 6650 (*h)(); 6651 return 0; 6652 } 6653 } 6654 goto default; 6655 default: 6656 } 6657 if(oldHandler) 6658 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 6659 return 1; // pass it on 6660 }; 6661 6662 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6663 oldHandler = window.handleNativeEvent; 6664 window.handleNativeEvent = nativeEventHandler; 6665 } 6666 6667 return id; 6668 } 6669 6670 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 6671 void unregisterHotKey(SimpleWindow window, int id) { 6672 if(!UnregisterHotKey(window.impl.hwnd, id)) 6673 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 6674 } 6675 } 6676 6677 version (X11) { 6678 pragma(lib, "dl"); 6679 import core.sys.posix.dlfcn; 6680 } 6681 6682 /++ 6683 Allows for sending synthetic input to the X server via the Xtst 6684 extension or on Windows using SendInput. 6685 6686 Please remember user input is meant to be user - don't use this 6687 if you have some other alternative! 6688 6689 History: 6690 Added May 17, 2020 with the X implementation. 6691 6692 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.) 6693 Bugs: 6694 All methods on OSX Cocoa will throw not yet implemented exceptions. 6695 +/ 6696 struct SyntheticInput { 6697 @disable this(); 6698 6699 private int* refcount; 6700 6701 version(X11) { 6702 private void* lib; 6703 6704 private extern(C) { 6705 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6706 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6707 } 6708 } 6709 6710 /// The dummy param must be 0. 6711 this(int dummy) { 6712 version(X11) { 6713 lib = dlopen("libXtst.so", RTLD_NOW); 6714 if(lib is null) 6715 throw new Exception("cannot load xtest lib extension"); 6716 scope(failure) 6717 dlclose(lib); 6718 6719 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6720 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6721 6722 if(XTestFakeKeyEvent is null) 6723 throw new Exception("No XTestFakeKeyEvent"); 6724 if(XTestFakeButtonEvent is null) 6725 throw new Exception("No XTestFakeButtonEvent"); 6726 } 6727 6728 refcount = new int; 6729 *refcount = 1; 6730 } 6731 6732 this(this) { 6733 if(refcount) 6734 *refcount += 1; 6735 } 6736 6737 ~this() { 6738 if(refcount) { 6739 *refcount -= 1; 6740 if(*refcount == 0) 6741 // I commented this because if I close the lib before 6742 // XCloseDisplay, it is liable to segfault... so just 6743 // gonna keep it loaded if it is loaded, no big deal 6744 // anyway. 6745 {} // dlclose(lib); 6746 } 6747 } 6748 6749 /++ 6750 Simulates typing a string into the keyboard. 6751 6752 Bugs: 6753 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 6754 6755 Not implemented except on Windows and X11. 6756 +/ 6757 void sendSyntheticInput(string s) { 6758 version(Windows) { 6759 INPUT[] inputs; 6760 inputs.reserve(s.length * 2); 6761 6762 auto ei = GetMessageExtraInfo(); 6763 6764 foreach(wchar c; s) { 6765 INPUT input; 6766 input.type = INPUT_KEYBOARD; 6767 input.ki.wScan = c; 6768 input.ki.dwFlags = KEYEVENTF_UNICODE; 6769 input.ki.dwExtraInfo = ei; 6770 inputs ~= input; 6771 6772 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6773 inputs ~= input; 6774 } 6775 6776 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6777 throw new WindowsApiException("SendInput", GetLastError()); 6778 } 6779 } else version(X11) { 6780 int delay = 0; 6781 foreach(ch; s) { 6782 pressKey(cast(Key) ch, true, delay); 6783 pressKey(cast(Key) ch, false, delay); 6784 delay += 5; 6785 } 6786 } else throw new NotYetImplementedException(); 6787 } 6788 6789 /++ 6790 Sends a fake press or release key event. 6791 6792 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6793 6794 Bugs: 6795 The `delay` parameter is not implemented yet on Windows. 6796 6797 Not implemented except on Windows and X11. 6798 +/ 6799 void pressKey(Key key, bool pressed, int delay = 0) { 6800 version(Windows) { 6801 INPUT input; 6802 input.type = INPUT_KEYBOARD; 6803 input.ki.wVk = cast(ushort) key; 6804 6805 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 6806 input.ki.dwExtraInfo = GetMessageExtraInfo(); 6807 6808 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6809 throw new WindowsApiException("SendInput", GetLastError()); 6810 } 6811 } else version(X11) { 6812 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6813 } else throw new NotYetImplementedException(); 6814 } 6815 6816 /++ 6817 Sends a fake mouse button press or release event. 6818 6819 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6820 6821 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 6822 6823 Bugs: 6824 The `delay` parameter is not implemented yet on Windows. 6825 6826 The backButton and forwardButton will throw NotYetImplementedException on Windows. 6827 6828 All arguments will throw NotYetImplementedException on OSX Cocoa. 6829 +/ 6830 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6831 version(Windows) { 6832 INPUT input; 6833 input.type = INPUT_MOUSE; 6834 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6835 6836 // input.mi.mouseData for a wheel event 6837 6838 switch(button) { 6839 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 6840 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 6841 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 6842 case MouseButton.wheelUp: 6843 case MouseButton.wheelDown: 6844 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 6845 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 6846 break; 6847 case MouseButton.backButton: throw new NotYetImplementedException(); 6848 case MouseButton.forwardButton: throw new NotYetImplementedException(); 6849 default: 6850 } 6851 6852 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6853 throw new WindowsApiException("SendInput", GetLastError()); 6854 } 6855 } else version(X11) { 6856 int btn; 6857 6858 switch(button) { 6859 case MouseButton.left: btn = 1; break; 6860 case MouseButton.middle: btn = 2; break; 6861 case MouseButton.right: btn = 3; break; 6862 case MouseButton.wheelUp: btn = 4; break; 6863 case MouseButton.wheelDown: btn = 5; break; 6864 case MouseButton.backButton: btn = 8; break; 6865 case MouseButton.forwardButton: btn = 9; break; 6866 default: 6867 } 6868 6869 assert(btn); 6870 6871 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 6872 } else throw new NotYetImplementedException(); 6873 } 6874 6875 /// 6876 static void moveMouseArrowBy(int dx, int dy) { 6877 version(Windows) { 6878 INPUT input; 6879 input.type = INPUT_MOUSE; 6880 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6881 input.mi.dx = dx; 6882 input.mi.dy = dy; 6883 input.mi.dwFlags = MOUSEEVENTF_MOVE; 6884 6885 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6886 throw new WindowsApiException("SendInput", GetLastError()); 6887 } 6888 } else version(X11) { 6889 auto disp = XDisplayConnection.get(); 6890 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 6891 XFlush(disp); 6892 } else throw new NotYetImplementedException(); 6893 } 6894 6895 /// 6896 static void moveMouseArrowTo(int x, int y) { 6897 version(Windows) { 6898 INPUT input; 6899 input.type = INPUT_MOUSE; 6900 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6901 input.mi.dx = x; 6902 input.mi.dy = y; 6903 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 6904 6905 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6906 throw new WindowsApiException("SendInput", GetLastError()); 6907 } 6908 } else version(X11) { 6909 auto disp = XDisplayConnection.get(); 6910 auto root = RootWindow(disp, DefaultScreen(disp)); 6911 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 6912 XFlush(disp); 6913 } else throw new NotYetImplementedException(); 6914 } 6915 } 6916 6917 6918 6919 /++ 6920 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 6921 6922 See_Also: 6923 $(LIST 6924 *[ScreenPainter] 6925 *[ScreenPainter.rasterOp] 6926 ) 6927 +/ 6928 enum RasterOp { 6929 normal, /// Replaces the pixel. 6930 xor, /// Uses bitwise xor to draw. 6931 } 6932 6933 // being phobos-free keeps the size WAY down 6934 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 6935 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 6936 package(arsd) const(wchar)* toWStringz(string s) { 6937 wstring r; 6938 foreach(dchar c; s) 6939 r ~= c; 6940 r ~= '\0'; 6941 return r.ptr; 6942 } 6943 private string[] split(in void[] a, char c) { 6944 string[] ret; 6945 size_t previous = 0; 6946 foreach(i, char ch; cast(ubyte[]) a) { 6947 if(ch == c) { 6948 ret ~= cast(string) a[previous .. i]; 6949 previous = i + 1; 6950 } 6951 } 6952 if(previous != a.length) 6953 ret ~= cast(string) a[previous .. $]; 6954 return ret; 6955 } 6956 6957 version(without_opengl) { 6958 enum OpenGlOptions { 6959 no, 6960 } 6961 } else { 6962 /++ 6963 Determines if you want an OpenGL context created on the new window. 6964 6965 6966 See more: [#topics-3d|in the 3d topic]. 6967 6968 --- 6969 import arsd.simpledisplay; 6970 void main() { 6971 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 6972 6973 // Set up the matrix 6974 window.setAsCurrentOpenGlContext(); // make this window active 6975 6976 // This is called on each frame, we will draw our scene 6977 window.redrawOpenGlScene = delegate() { 6978 6979 }; 6980 6981 window.eventLoop(0); 6982 } 6983 --- 6984 +/ 6985 enum OpenGlOptions { 6986 no, /// No OpenGL context is created 6987 yes, /// Yes, create an OpenGL context 6988 } 6989 6990 version(X11) { 6991 static if (!SdpyIsUsingIVGLBinds) { 6992 6993 6994 struct __GLXFBConfigRec {} 6995 alias GLXFBConfig = __GLXFBConfigRec*; 6996 6997 //pragma(lib, "GL"); 6998 //pragma(lib, "GLU"); 6999 interface GLX { 7000 extern(C) nothrow @nogc { 7001 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 7002 const int *attrib_list); 7003 7004 void glXCopyContext(Display *dpy, GLXContext src, 7005 GLXContext dst, arch_ulong mask); 7006 7007 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7008 GLXContext share_list, Bool direct); 7009 7010 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7011 Pixmap pixmap); 7012 7013 void glXDestroyContext(Display *dpy, GLXContext ctx); 7014 7015 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7016 7017 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7018 int attrib, int *value); 7019 7020 GLXContext glXGetCurrentContext(); 7021 7022 GLXDrawable glXGetCurrentDrawable(); 7023 7024 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7025 7026 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7027 GLXContext ctx); 7028 7029 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7030 7031 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7032 7033 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7034 7035 void glXUseXFont(Font font, int first, int count, int list_base); 7036 7037 void glXWaitGL(); 7038 7039 void glXWaitX(); 7040 7041 7042 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7043 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7044 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7045 7046 char* glXQueryExtensionsString (Display*, int); 7047 void* glXGetProcAddress (const(char)*); 7048 7049 } 7050 } 7051 7052 version(OSX) 7053 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7054 else 7055 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7056 shared static this() { 7057 glx.loadDynamicLibrary(); 7058 } 7059 7060 alias glbindGetProcAddress = glXGetProcAddress; 7061 } 7062 } else version(Windows) { 7063 /* it is done below by interface GL */ 7064 } else 7065 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."); 7066 } 7067 7068 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7069 alias Resizablity = Resizability; 7070 7071 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7072 enum Resizability { 7073 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. 7074 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. 7075 /++ 7076 $(PITFALL 7077 Planned for the future but not implemented. 7078 ) 7079 7080 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. 7081 7082 History: 7083 Added November 11, 2022, but not yet implemented and may not be for some time. 7084 +/ 7085 /*@__future*/ allowResizingMaintainingAspectRatio, 7086 /++ 7087 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. 7088 7089 History: 7090 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. 7091 7092 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7093 +/ 7094 automaticallyScaleIfPossible, 7095 } 7096 7097 7098 /++ 7099 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7100 +/ 7101 enum TextAlignment : uint { 7102 Left = 0, /// 7103 Center = 1, /// 7104 Right = 2, /// 7105 7106 VerticalTop = 0, /// 7107 VerticalCenter = 4, /// 7108 VerticalBottom = 8, /// 7109 } 7110 7111 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7112 alias Rectangle = arsd.color.Rectangle; 7113 7114 7115 /++ 7116 Keyboard press and release events. 7117 +/ 7118 struct KeyEvent { 7119 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7120 Key key; 7121 ubyte hardwareCode; /// A platform and hardware specific code for the key 7122 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7123 7124 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7125 7126 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7127 7128 SimpleWindow window; /// associated Window 7129 7130 /++ 7131 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7132 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7133 to predict if char events are actually coming.. 7134 7135 Only available on X systems since this information is not given ahead of time elsewhere. 7136 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7137 7138 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7139 and potential quirks I'd recommend avoiding it. 7140 7141 History: 7142 Added April 26, 2021 (dub v9.5) 7143 +/ 7144 version(X11) 7145 dchar[] charsPossible; 7146 7147 // convert key event to simplified string representation a-la emacs 7148 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7149 uint dpos = 0; 7150 void put (const(char)[] s...) nothrow @trusted { 7151 static if (growdest) { 7152 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7153 } else { 7154 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7155 } 7156 } 7157 7158 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7159 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7160 } 7161 7162 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7163 7164 // put modifiers 7165 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7166 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7167 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7168 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7169 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7170 7171 if (this.key) { 7172 foreach (string kn; __traits(allMembers, Key)) { 7173 if (this.key == __traits(getMember, Key, kn)) { 7174 // HACK! 7175 static if (kn == "N0") put("0"); 7176 else static if (kn == "N1") put("1"); 7177 else static if (kn == "N2") put("2"); 7178 else static if (kn == "N3") put("3"); 7179 else static if (kn == "N4") put("4"); 7180 else static if (kn == "N5") put("5"); 7181 else static if (kn == "N6") put("6"); 7182 else static if (kn == "N7") put("7"); 7183 else static if (kn == "N8") put("8"); 7184 else static if (kn == "N9") put("9"); 7185 else put(kn); 7186 return dest[0..dpos]; 7187 } 7188 } 7189 put("Unknown"); 7190 } else { 7191 if (dpos && dest[dpos-1] == '+') --dpos; 7192 } 7193 return dest[0..dpos]; 7194 } 7195 7196 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7197 7198 /** Parse string into key name with modifiers. It accepts things like: 7199 * 7200 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7201 * 7202 * Ctrl+Win+1 -- windows style 7203 * 7204 * Ctrl-Win-1 -- '-' is a valid delimiter too 7205 * 7206 * Ctrl Win 1 -- and space 7207 * 7208 * and even "Win + 1 + Ctrl". 7209 */ 7210 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7211 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7212 7213 // remove trailing spaces 7214 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7215 7216 // tokens delimited by blank, '+', or '-' 7217 // null on eol 7218 const(char)[] getToken () nothrow @trusted @nogc { 7219 // remove leading spaces and delimiters 7220 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7221 if (name.length == 0) return null; // oops, no more tokens 7222 // get token 7223 size_t epos = 0; 7224 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7225 assert(epos > 0 && epos <= name.length); 7226 auto res = name[0..epos]; 7227 name = name[epos..$]; 7228 return res; 7229 } 7230 7231 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7232 if (s0.length != s1.length) return false; 7233 foreach (immutable ci, char c0; s0) { 7234 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7235 char c1 = s1[ci]; 7236 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7237 if (c0 != c1) return false; 7238 } 7239 return true; 7240 } 7241 7242 if (ignoreModsOut !is null) *ignoreModsOut = false; 7243 if (updown !is null) *updown = -1; 7244 KeyEvent res; 7245 res.key = cast(Key)0; // just in case 7246 const(char)[] tk, tkn; // last token 7247 bool allowEmascStyle = true; 7248 bool ignoreModifiers = false; 7249 tokenloop: for (;;) { 7250 tk = tkn; 7251 tkn = getToken(); 7252 //k8: yay, i took "Bloody Mess" trait from Fallout! 7253 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7254 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7255 if (allowEmascStyle && tkn.length != 0) { 7256 if (tk.length == 1) { 7257 char mdc = tk[0]; 7258 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7259 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7260 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7261 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7262 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7263 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7264 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7265 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7266 } 7267 } 7268 allowEmascStyle = false; 7269 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7270 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7271 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7272 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7273 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7274 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7275 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7276 if (tk.length == 0) continue; 7277 // try key name 7278 if (res.key == 0) { 7279 // little hack 7280 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7281 final switch (tk[0]) { 7282 case '0': tk = "N0"; break; 7283 case '1': tk = "N1"; break; 7284 case '2': tk = "N2"; break; 7285 case '3': tk = "N3"; break; 7286 case '4': tk = "N4"; break; 7287 case '5': tk = "N5"; break; 7288 case '6': tk = "N6"; break; 7289 case '7': tk = "N7"; break; 7290 case '8': tk = "N8"; break; 7291 case '9': tk = "N9"; break; 7292 } 7293 } 7294 foreach (string kn; __traits(allMembers, Key)) { 7295 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7296 } 7297 } 7298 // unknown or duplicate key name, get out of here 7299 break; 7300 } 7301 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7302 return res; // something 7303 } 7304 7305 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7306 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7307 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7308 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7309 } 7310 bool ignoreMods; 7311 int updown; 7312 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7313 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7314 if (this.key != ke.key) { 7315 // things like "ctrl+alt" are complicated 7316 uint tkm = this.modifierState&modmask; 7317 uint kkm = ke.modifierState&modmask; 7318 Key tk = this.key; 7319 // ke 7320 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7321 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7322 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7323 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7324 // this 7325 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7326 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7327 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7328 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7329 return (tk == ke.key && tkm == kkm); 7330 } 7331 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7332 } 7333 } 7334 7335 /// Sets the application name. 7336 @property string ApplicationName(string name) { 7337 return _applicationName = name; 7338 } 7339 7340 string _applicationName; 7341 7342 /// ditto 7343 @property string ApplicationName() { 7344 if(_applicationName is null) { 7345 import core.runtime; 7346 return Runtime.args[0]; 7347 } 7348 return _applicationName; 7349 } 7350 7351 7352 /// Type of a [MouseEvent]. 7353 enum MouseEventType : int { 7354 motion = 0, /// The mouse moved inside the window 7355 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7356 buttonReleased = 2, /// A mouse button was released 7357 } 7358 7359 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7360 /++ 7361 Listen for this on your event listeners if you are interested in mouse action. 7362 7363 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. 7364 7365 Examples: 7366 7367 This will draw boxes on the window with the mouse as you hold the left button. 7368 --- 7369 import arsd.simpledisplay; 7370 7371 void main() { 7372 auto window = new SimpleWindow(); 7373 7374 window.eventLoop(0, 7375 (MouseEvent ev) { 7376 if(ev.modifierState & ModifierState.leftButtonDown) { 7377 auto painter = window.draw(); 7378 painter.fillColor = Color.red; 7379 painter.outlineColor = Color.black; 7380 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7381 } 7382 } 7383 ); 7384 } 7385 --- 7386 +/ 7387 struct MouseEvent { 7388 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7389 7390 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. 7391 int y; /// Current Y position of the cursor when the event fired. 7392 7393 int dx; /// Change in X position since last report 7394 int dy; /// Change in Y position since last report 7395 7396 MouseButton button; /// See [MouseButton] 7397 int modifierState; /// See [ModifierState] 7398 7399 version(X11) 7400 private Time timestamp; 7401 7402 /// Returns a linear representation of mouse button, 7403 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7404 /// 7405 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7406 @property ubyte buttonLinear() const { 7407 import core.bitop; 7408 if(button == 0) 7409 return 0; 7410 return (bsf(button) + 1) & 0b1111; 7411 } 7412 7413 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7414 7415 SimpleWindow window; /// The window in which the event happened. 7416 7417 Point globalCoordinates() { 7418 Point p; 7419 if(window is null) 7420 throw new Exception("wtf"); 7421 static if(UsingSimpledisplayX11) { 7422 Window child; 7423 XTranslateCoordinates( 7424 XDisplayConnection.get, 7425 window.impl.window, 7426 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7427 x, y, &p.x, &p.y, &child); 7428 return p; 7429 } else version(Windows) { 7430 POINT[1] points; 7431 points[0].x = x; 7432 points[0].y = y; 7433 MapWindowPoints( 7434 window.impl.hwnd, 7435 null, 7436 points.ptr, 7437 points.length 7438 ); 7439 p.x = points[0].x; 7440 p.y = points[0].y; 7441 7442 return p; 7443 } else version(OSXCocoa) { 7444 throw new NotYetImplementedException(); 7445 } else static assert(0); 7446 } 7447 7448 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7449 7450 /** 7451 can contain emacs-like modifier prefix 7452 case-insensitive names: 7453 lmbX/leftX 7454 rmbX/rightX 7455 mmbX/middleX 7456 wheelX 7457 motion (no prefix allowed) 7458 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7459 */ 7460 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7461 if (str.length == 0) return false; // just in case 7462 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7463 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7464 auto anchor = str; 7465 uint mods = 0; // uint.max == any 7466 // interesting bits in kmod 7467 uint kmodmask = 7468 ModifierState.shift| 7469 ModifierState.ctrl| 7470 ModifierState.alt| 7471 ModifierState.windows| 7472 ModifierState.leftButtonDown| 7473 ModifierState.middleButtonDown| 7474 ModifierState.rightButtonDown| 7475 0; 7476 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7477 bool wasButtons = false; 7478 while (str.length) { 7479 if (str.ptr[0] <= ' ') { 7480 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7481 continue; 7482 } 7483 // one-letter modifier? 7484 if (str.length >= 2 && str.ptr[1] == '-') { 7485 switch (str.ptr[0]) { 7486 case '*': // "any" modifier (cannot be undone) 7487 mods = mods.max; 7488 break; 7489 case 'C': case 'c': // emacs "ctrl" 7490 if (mods != mods.max) mods |= ModifierState.ctrl; 7491 break; 7492 case 'M': case 'm': // emacs "meta" 7493 if (mods != mods.max) mods |= ModifierState.alt; 7494 break; 7495 case 'S': case 's': // emacs "shift" 7496 if (mods != mods.max) mods |= ModifierState.shift; 7497 break; 7498 case 'H': case 'h': // emacs "hyper" (aka winkey) 7499 if (mods != mods.max) mods |= ModifierState.windows; 7500 break; 7501 default: 7502 return false; // unknown modifier 7503 } 7504 str = str[2..$]; 7505 continue; 7506 } 7507 // word 7508 char[16] buf = void; // locased 7509 auto wep = 0; 7510 while (str.length) { 7511 immutable char ch = str.ptr[0]; 7512 if (ch <= ' ' || ch == '-') break; 7513 str = str[1..$]; 7514 if (wep > buf.length) return false; // too long 7515 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7516 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7517 else return false; // invalid char 7518 } 7519 if (wep == 0) return false; // just in case 7520 uint bnum; 7521 enum UpDown { None = -1, Up, Down, Any } 7522 auto updown = UpDown.None; // 0: up; 1: down 7523 switch (buf[0..wep]) { 7524 // left button 7525 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 7526 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 7527 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 7528 case "lmb": case "left": bnum = 0; break; 7529 // middle button 7530 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 7531 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 7532 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 7533 case "mmb": case "middle": bnum = 1; break; 7534 // right button 7535 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 7536 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 7537 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 7538 case "rmb": case "right": bnum = 2; break; 7539 // wheel 7540 case "wheelup": updown = UpDown.Up; goto case "wheel"; 7541 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 7542 case "wheelany": updown = UpDown.Any; goto case "wheel"; 7543 case "wheel": bnum = 3; break; 7544 // motion 7545 case "motion": bnum = 7; break; 7546 // unknown 7547 default: return false; 7548 } 7549 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7550 // parse possible "-up" or "-down" 7551 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 7552 wep = 0; 7553 foreach (immutable idx, immutable char ch; str[1..$]) { 7554 if (ch <= ' ' || ch == '-') break; 7555 assert(idx == wep); // for now; trick 7556 if (wep > buf.length) { wep = 0; break; } // too long 7557 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7558 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7559 else { wep = 0; break; } // invalid char 7560 } 7561 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 7562 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 7563 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 7564 // remove parsed part 7565 if (updown != UpDown.None) str = str[wep+1..$]; 7566 } 7567 if (updown == UpDown.None) { 7568 updown = UpDown.Down; 7569 } 7570 wasButtons = wasButtons || (bnum <= 2); 7571 //assert(updown != UpDown.None); 7572 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7573 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 7574 if (lastButt != lastButt.max) { 7575 if ((lastButt&0xff) >= 3) return false; // wheel or motion 7576 if (mods != mods.max) { 7577 uint butbit = 0; 7578 final switch (lastButt&0x03) { 7579 case 0: butbit = ModifierState.leftButtonDown; break; 7580 case 1: butbit = ModifierState.middleButtonDown; break; 7581 case 2: butbit = ModifierState.rightButtonDown; break; 7582 } 7583 if (lastButt&Flag.Down) mods |= butbit; 7584 else if (lastButt&Flag.Up) mods &= ~butbit; 7585 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 7586 } 7587 } 7588 // remember last button 7589 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 7590 } 7591 // no button -- nothing to do 7592 if (lastButt == lastButt.max) return false; 7593 // done parsing, check if something's left 7594 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 7595 // remove action button from mask 7596 if ((lastButt&0xff) < 3) { 7597 final switch (lastButt&0x03) { 7598 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 7599 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 7600 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 7601 } 7602 } 7603 // special case: "Motion" means "ignore buttons" 7604 if ((lastButt&0xff) == 7 && !wasButtons) { 7605 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 7606 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 7607 } 7608 uint kmod = event.modifierState&kmodmask; 7609 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 7610 // check modifier state 7611 if (mods != mods.max) { 7612 if (kmod != mods) return false; 7613 } 7614 // now check type 7615 if ((lastButt&0xff) == 7) { 7616 // motion 7617 if (event.type != MouseEventType.motion) return false; 7618 } else if ((lastButt&0xff) == 3) { 7619 // wheel 7620 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 7621 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 7622 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 7623 return false; 7624 } else { 7625 // buttons 7626 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 7627 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 7628 { 7629 return false; 7630 } 7631 // button number 7632 switch (lastButt&0x03) { 7633 case 0: if (event.button != MouseButton.left) return false; break; 7634 case 1: if (event.button != MouseButton.middle) return false; break; 7635 case 2: if (event.button != MouseButton.right) return false; break; 7636 default: return false; 7637 } 7638 } 7639 return true; 7640 } 7641 } 7642 7643 version(arsd_mevent_strcmp_test) unittest { 7644 MouseEvent event; 7645 event.type = MouseEventType.buttonPressed; 7646 event.button = MouseButton.left; 7647 event.modifierState = ModifierState.ctrl; 7648 assert(event == "C-LMB"); 7649 assert(event != "C-LMBUP"); 7650 assert(event != "C-LMB-UP"); 7651 assert(event != "C-S-LMB"); 7652 assert(event == "*-LMB"); 7653 assert(event != "*-LMB-UP"); 7654 7655 event.type = MouseEventType.buttonReleased; 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.button = MouseButton.right; 7664 event.modifierState |= ModifierState.shift; 7665 event.type = MouseEventType.buttonPressed; 7666 assert(event != "C-LMB"); 7667 assert(event != "C-LMBUP"); 7668 assert(event != "C-LMB-UP"); 7669 assert(event != "C-S-LMB"); 7670 assert(event != "*-LMB"); 7671 assert(event != "*-LMB-UP"); 7672 7673 assert(event != "C-RMB"); 7674 assert(event != "C-RMBUP"); 7675 assert(event != "C-RMB-UP"); 7676 assert(event == "C-S-RMB"); 7677 assert(event == "*-RMB"); 7678 assert(event != "*-RMB-UP"); 7679 } 7680 7681 /// This gives a few more options to drawing lines and such 7682 struct Pen { 7683 Color color; /// the foreground color 7684 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. 7685 Style style; /// See [Style] 7686 /+ 7687 // From X.h 7688 7689 #define LineSolid 0 7690 #define LineOnOffDash 1 7691 #define LineDoubleDash 2 7692 LineDou- The full path of the line is drawn, but the 7693 bleDash even dashes are filled differently from the 7694 odd dashes (see fill-style) with CapButt 7695 style used where even and odd dashes meet. 7696 7697 7698 7699 /* capStyle */ 7700 7701 #define CapNotLast 0 7702 #define CapButt 1 7703 #define CapRound 2 7704 #define CapProjecting 3 7705 7706 /* joinStyle */ 7707 7708 #define JoinMiter 0 7709 #define JoinRound 1 7710 #define JoinBevel 2 7711 7712 /* fillStyle */ 7713 7714 #define FillSolid 0 7715 #define FillTiled 1 7716 #define FillStippled 2 7717 #define FillOpaqueStippled 3 7718 7719 7720 +/ 7721 /// Style of lines drawn 7722 enum Style { 7723 Solid, /// a solid line 7724 Dashed, /// a dashed line 7725 Dotted, /// a dotted line 7726 } 7727 } 7728 7729 7730 /++ 7731 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 7732 7733 7734 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 7735 7736 $(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.) 7737 7738 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. 7739 7740 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 7741 7742 $(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. 7743 7744 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! 7745 7746 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!) 7747 7748 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 7749 7750 --- 7751 auto image = new Image(256, 256); 7752 scope(exit) destroy(image); 7753 --- 7754 7755 As long as you don't hold on to it outside the scope. 7756 7757 I might change it to be an owned pointer at some point in the future. 7758 7759 ) 7760 7761 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7762 you can also often get a fair amount of speedup by getting the raw data format and 7763 writing some custom code. 7764 7765 FIXME INSERT EXAMPLES HERE 7766 7767 7768 +/ 7769 final class Image { 7770 /// 7771 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7772 this.width = width; 7773 this.height = height; 7774 this.enableAlpha = enableAlpha; 7775 7776 impl.createImage(width, height, forcexshm, enableAlpha); 7777 } 7778 7779 /// 7780 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7781 this(size.width, size.height, forcexshm, enableAlpha); 7782 } 7783 7784 private bool suppressDestruction; 7785 7786 version(X11) 7787 this(XImage* handle) { 7788 this.handle = handle; 7789 this.rawData = cast(ubyte*) handle.data; 7790 this.width = handle.width; 7791 this.height = handle.height; 7792 this.enableAlpha = handle.depth == 32; 7793 suppressDestruction = true; 7794 } 7795 7796 ~this() { 7797 if(suppressDestruction) return; 7798 impl.dispose(); 7799 } 7800 7801 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7802 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7803 pure const @system nothrow { 7804 /* 7805 To use these to draw a blue rectangle with size WxH at position X,Y... 7806 7807 // make certain that it will fit before we proceed 7808 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! 7809 7810 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7811 // (though calculating them isn't really that expensive). 7812 auto nextLineAdjustment = img.adjustmentForNextLine(); 7813 auto offR = img.redByteOffset(); 7814 auto offB = img.blueByteOffset(); 7815 auto offG = img.greenByteOffset(); 7816 auto bpp = img.bytesPerPixel(); 7817 7818 auto data = img.getDataPointer(); 7819 7820 // figure out the starting byte offset 7821 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7822 7823 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7824 7825 // and now our drawing loop for the rectangle 7826 foreach(y; 0 .. H) { 7827 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7828 foreach(x; 0 .. W) { 7829 // write our color 7830 data[offR] = 0; 7831 data[offG] = 0; 7832 data[offB] = 255; 7833 7834 data += bpp; // moving to the next pixel is just an addition... 7835 } 7836 startOfLine += nextLineAdjustment; 7837 } 7838 7839 7840 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7841 7842 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7843 can be made into a bitmask or something so we can write them as *uint... 7844 */ 7845 7846 /// 7847 int offsetForTopLeftPixel() { 7848 version(X11) { 7849 return 0; 7850 } else version(Windows) { 7851 if(enableAlpha) { 7852 return (width * 4) * (height - 1); 7853 } else { 7854 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7855 } 7856 } else version(OSXCocoa) { 7857 return 0 ; //throw new NotYetImplementedException(); 7858 } else static assert(0, "fill in this info for other OSes"); 7859 } 7860 7861 /// 7862 int offsetForPixel(int x, int y) { 7863 version(X11) { 7864 auto offset = (y * width + x) * 4; 7865 return offset; 7866 } else version(Windows) { 7867 if(enableAlpha) { 7868 auto itemsPerLine = width * 4; 7869 // remember, bmps are upside down 7870 auto offset = itemsPerLine * (height - y - 1) + x * 4; 7871 return offset; 7872 } else { 7873 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 7874 // remember, bmps are upside down 7875 auto offset = itemsPerLine * (height - y - 1) + x * 3; 7876 return offset; 7877 } 7878 } else version(OSXCocoa) { 7879 return 0 ; //throw new NotYetImplementedException(); 7880 } else static assert(0, "fill in this info for other OSes"); 7881 } 7882 7883 /// 7884 int adjustmentForNextLine() { 7885 version(X11) { 7886 return width * 4; 7887 } else version(Windows) { 7888 // windows bmps are upside down, so the adjustment is actually negative 7889 if(enableAlpha) 7890 return - (cast(int) width * 4); 7891 else 7892 return -((cast(int) width * 3 + 3) / 4) * 4; 7893 } else version(OSXCocoa) { 7894 return 0 ; //throw new NotYetImplementedException(); 7895 } else static assert(0, "fill in this info for other OSes"); 7896 } 7897 7898 /// once you have the position of a pixel, use these to get to the proper color 7899 int redByteOffset() { 7900 version(X11) { 7901 return 2; 7902 } else version(Windows) { 7903 return 2; 7904 } else version(OSXCocoa) { 7905 return 0 ; //throw new NotYetImplementedException(); 7906 } else static assert(0, "fill in this info for other OSes"); 7907 } 7908 7909 /// 7910 int greenByteOffset() { 7911 version(X11) { 7912 return 1; 7913 } else version(Windows) { 7914 return 1; 7915 } else version(OSXCocoa) { 7916 return 0 ; //throw new NotYetImplementedException(); 7917 } else static assert(0, "fill in this info for other OSes"); 7918 } 7919 7920 /// 7921 int blueByteOffset() { 7922 version(X11) { 7923 return 0; 7924 } else version(Windows) { 7925 return 0; 7926 } else version(OSXCocoa) { 7927 return 0 ; //throw new NotYetImplementedException(); 7928 } else static assert(0, "fill in this info for other OSes"); 7929 } 7930 7931 /// Only valid if [enableAlpha] is true 7932 int alphaByteOffset() { 7933 version(X11) { 7934 return 3; 7935 } else version(Windows) { 7936 return 3; 7937 } else version(OSXCocoa) { 7938 return 3; //throw new NotYetImplementedException(); 7939 } else static assert(0, "fill in this info for other OSes"); 7940 } 7941 } 7942 7943 /// 7944 final void putPixel(int x, int y, Color c) { 7945 if(x < 0 || x >= width) 7946 return; 7947 if(y < 0 || y >= height) 7948 return; 7949 7950 impl.setPixel(x, y, c); 7951 } 7952 7953 /// 7954 final Color getPixel(int x, int y) { 7955 if(x < 0 || x >= width) 7956 return Color.transparent; 7957 if(y < 0 || y >= height) 7958 return Color.transparent; 7959 7960 version(OSXCocoa) throw new NotYetImplementedException(); else 7961 return impl.getPixel(x, y); 7962 } 7963 7964 /// 7965 final void opIndexAssign(Color c, int x, int y) { 7966 putPixel(x, y, c); 7967 } 7968 7969 /// 7970 TrueColorImage toTrueColorImage() { 7971 auto tci = new TrueColorImage(width, height); 7972 convertToRgbaBytes(tci.imageData.bytes); 7973 return tci; 7974 } 7975 7976 /// 7977 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) { 7978 auto tci = i.getAsTrueColorImage(); 7979 auto img = new Image(tci.width, tci.height, false, enableAlpha); 7980 img.setRgbaBytes(tci.imageData.bytes); 7981 return img; 7982 } 7983 7984 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 7985 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 7986 /// if you pass null, it will allocate a new one. 7987 ubyte[] getRgbaBytes(ubyte[] where = null) { 7988 if(where is null) 7989 where = new ubyte[this.width*this.height*4]; 7990 convertToRgbaBytes(where); 7991 return where; 7992 } 7993 7994 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 7995 void setRgbaBytes(in ubyte[] from ) { 7996 assert(from.length == this.width * this.height * 4); 7997 setFromRgbaBytes(from); 7998 } 7999 8000 // FIXME: make properly cross platform by getting rgba right 8001 8002 /// warning: this is not portable across platforms because the data format can change 8003 ubyte* getDataPointer() { 8004 return impl.rawData; 8005 } 8006 8007 /// for use with getDataPointer 8008 final int bytesPerLine() const pure @safe nothrow { 8009 version(Windows) 8010 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 8011 else version(X11) 8012 return 4 * width; 8013 else version(OSXCocoa) 8014 return 4 * width; 8015 else static assert(0); 8016 } 8017 8018 /// for use with getDataPointer 8019 final int bytesPerPixel() const pure @safe nothrow { 8020 version(Windows) 8021 return enableAlpha ? 4 : 3; 8022 else version(X11) 8023 return 4; 8024 else version(OSXCocoa) 8025 return 4; 8026 else static assert(0); 8027 } 8028 8029 /// 8030 immutable int width; 8031 8032 /// 8033 immutable int height; 8034 8035 /// 8036 immutable bool enableAlpha; 8037 //private: 8038 mixin NativeImageImplementation!() impl; 8039 } 8040 8041 /++ 8042 A convenience function to pop up a window displaying the image. 8043 If you pass a win, it will draw the image in it. Otherwise, it will 8044 create a window with the size of the image and run its event loop, closing 8045 when a key is pressed. 8046 8047 History: 8048 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8049 always block until the application quit which could cause bizarre behavior 8050 inside a more complex application. Now, the default is to block until 8051 this window closes if it is the only event loop running, and otherwise, 8052 not to block at all and just pop up the display window asynchronously. 8053 +/ 8054 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8055 if(win is null) { 8056 win = new SimpleWindow(image); 8057 { 8058 auto p = win.draw; 8059 p.drawImage(Point(0, 0), image); 8060 } 8061 win.eventLoopWithBlockingMode( 8062 bm, 0, 8063 (KeyEvent ev) { 8064 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8065 } ); 8066 } else { 8067 win.image = image; 8068 } 8069 } 8070 8071 enum FontWeight : int { 8072 dontcare = 0, 8073 thin = 100, 8074 extralight = 200, 8075 light = 300, 8076 regular = 400, 8077 medium = 500, 8078 semibold = 600, 8079 bold = 700, 8080 extrabold = 800, 8081 heavy = 900 8082 } 8083 8084 /++ 8085 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8086 8087 History: 8088 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8089 +/ 8090 interface MeasurableFont { 8091 /++ 8092 Returns true if it is a monospace font, meaning each of the 8093 glyphs (at least the ascii characters) have matching width 8094 and no kerning, so you can determine the display width of some 8095 strings by simply multiplying the string width by [averageWidth]. 8096 8097 (Please note that multiply doesn't $(I actually) work in general, 8098 consider characters like tab and newline, but it does sometimes.) 8099 +/ 8100 bool isMonospace(); 8101 8102 /++ 8103 The average width of glyphs in the font, traditionally equal to the 8104 width of the lowercase x. Can be used to estimate bounding boxes, 8105 especially if the font [isMonospace]. 8106 8107 Given in pixels. 8108 +/ 8109 int averageWidth(); 8110 /++ 8111 The height of the bounding box of a line. 8112 +/ 8113 int height(); 8114 /++ 8115 The maximum ascent of a glyph above the baseline. 8116 8117 Given in pixels. 8118 +/ 8119 int ascent(); 8120 /++ 8121 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8122 8123 Given in pixels. 8124 +/ 8125 int descent(); 8126 /++ 8127 The display width of the given string, and if you provide a window, it will use it to 8128 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8129 8130 Given in pixels. 8131 +/ 8132 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8133 8134 } 8135 8136 // FIXME: i need a font cache and it needs to handle disconnects. 8137 8138 /++ 8139 Represents a font loaded off the operating system or the X server. 8140 8141 8142 While the api here is unified cross platform, the fonts are not necessarily 8143 available, even across machines of the same platform, so be sure to always check 8144 for null (using [isNull]) and have a fallback plan. 8145 8146 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8147 8148 Worst case, a null font will automatically fall back to the default font loaded 8149 for your system. 8150 +/ 8151 class OperatingSystemFont : MeasurableFont { 8152 // FIXME: when the X Connection is lost, these need to be invalidated! 8153 // that means I need to store the original stuff again to reconstruct it too. 8154 8155 version(X11) { 8156 XFontStruct* font; 8157 XFontSet fontset; 8158 8159 version(with_xft) { 8160 XftFont* xftFont; 8161 bool isXft; 8162 } 8163 } else version(Windows) { 8164 HFONT font; 8165 int width_; 8166 int height_; 8167 } else version(OSXCocoa) { 8168 // FIXME 8169 } else static assert(0); 8170 8171 /++ 8172 Constructs the class and immediately calls [load]. 8173 +/ 8174 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8175 load(name, size, weight, italic); 8176 } 8177 8178 /++ 8179 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8180 8181 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8182 8183 History: 8184 Added January 24, 2021. 8185 +/ 8186 this() { 8187 // this space intentionally left blank 8188 } 8189 8190 /++ 8191 Constructs a copy of the given font object. 8192 8193 History: 8194 Added January 7, 2023. 8195 +/ 8196 this(OperatingSystemFont font) { 8197 if(font is null || font.loadedInfo is LoadedInfo.init) 8198 loadDefault(); 8199 else 8200 load(font.loadedInfo.tupleof); 8201 } 8202 8203 /++ 8204 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8205 8206 History: 8207 Added November 13, 2020. 8208 +/ 8209 version(with_xft) 8210 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8211 unload(); 8212 8213 if(!XftLibrary.attempted) { 8214 XftLibrary.loadDynamicLibrary(); 8215 } 8216 8217 if(!XftLibrary.loadSuccessful) 8218 return false; 8219 8220 auto display = XDisplayConnection.get; 8221 8222 char[256] nameBuffer = void; 8223 int nbp = 0; 8224 8225 void add(in char[] a) { 8226 nameBuffer[nbp .. nbp + a.length] = a[]; 8227 nbp += a.length; 8228 } 8229 add(name); 8230 8231 if(size) { 8232 add(":size="); 8233 add(toInternal!string(size)); 8234 } 8235 if(weight != FontWeight.dontcare) { 8236 add(":weight="); 8237 add(weightToString(weight)); 8238 } 8239 if(italic) 8240 add(":slant=100"); 8241 8242 nameBuffer[nbp] = 0; 8243 8244 this.xftFont = XftFontOpenName( 8245 display, 8246 DefaultScreen(display), 8247 nameBuffer.ptr 8248 ); 8249 8250 this.isXft = true; 8251 8252 if(xftFont !is null) { 8253 isMonospace_ = stringWidth("x") == stringWidth("M"); 8254 ascent_ = xftFont.ascent; 8255 descent_ = xftFont.descent; 8256 } 8257 8258 return !isNull(); 8259 } 8260 8261 /++ 8262 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8263 8264 8265 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. 8266 8267 If `pattern` is null, it returns all available font families. 8268 8269 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. 8270 8271 The format of the pattern is platform-specific. 8272 8273 History: 8274 Added May 1, 2021 (dub v9.5) 8275 +/ 8276 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8277 version(Windows) { 8278 auto hdc = GetDC(null); 8279 scope(exit) ReleaseDC(null, hdc); 8280 LOGFONT logfont; 8281 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8282 auto localHandler = *(cast(typeof(handler)*) p); 8283 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8284 } 8285 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8286 } else version(X11) { 8287 //import core.stdc.stdio; 8288 bool done = false; 8289 version(with_xft) { 8290 if(!XftLibrary.attempted) { 8291 XftLibrary.loadDynamicLibrary(); 8292 } 8293 8294 if(!XftLibrary.loadSuccessful) 8295 goto skipXft; 8296 8297 if(!FontConfigLibrary.attempted) 8298 FontConfigLibrary.loadDynamicLibrary(); 8299 if(!FontConfigLibrary.loadSuccessful) 8300 goto skipXft; 8301 8302 { 8303 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8304 if(got is null) 8305 goto skipXft; 8306 scope(exit) FcFontSetDestroy(got); 8307 8308 auto fontPatterns = got.fonts[0 .. got.nfont]; 8309 foreach(candidate; fontPatterns) { 8310 char* where, whereStyle; 8311 8312 char* pmg = FcNameUnparse(candidate); 8313 8314 //FcPatternGetString(candidate, "family", 0, &where); 8315 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8316 //if(where && whereStyle) { 8317 if(pmg) { 8318 if(!handler(pmg.sliceCString)) 8319 return; 8320 //printf("%s || %s %s\n", pmg, where, whereStyle); 8321 } 8322 } 8323 } 8324 } 8325 8326 skipXft: 8327 8328 if(pattern is null) 8329 pattern = "*"; 8330 8331 int count; 8332 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8333 scope(exit) XFreeFontNames(coreFontsRaw); 8334 8335 auto coreFonts = coreFontsRaw[0 .. count]; 8336 8337 foreach(font; coreFonts) { 8338 char[128] tmp; 8339 tmp[0 ..5] = "core:"; 8340 auto cf = font.sliceCString; 8341 if(5 + cf.length > tmp.length) 8342 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8343 tmp[5 .. 5 + cf.length] = cf; 8344 if(!handler(tmp[0 .. 5 + cf.length])) 8345 return; 8346 } 8347 } 8348 } 8349 8350 /++ 8351 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8352 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8353 8354 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8355 underlying system doesn't support returning the raw bytes. 8356 8357 History: 8358 Added September 10, 2021 (dub v10.3) 8359 +/ 8360 ubyte[] getTtfBytes() { 8361 if(isNull) 8362 return null; 8363 8364 version(Windows) { 8365 auto dc = GetDC(null); 8366 auto orig = SelectObject(dc, font); 8367 8368 scope(exit) { 8369 SelectObject(dc, orig); 8370 ReleaseDC(null, dc); 8371 } 8372 8373 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8374 if(res == GDI_ERROR) 8375 return null; 8376 8377 ubyte[] buffer = new ubyte[](res); 8378 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8379 if(res == GDI_ERROR) 8380 return null; // wtf really tbh 8381 8382 return buffer; 8383 } else version(with_xft) { 8384 if(isXft && xftFont) { 8385 if(!FontConfigLibrary.attempted) 8386 FontConfigLibrary.loadDynamicLibrary(); 8387 if(!FontConfigLibrary.loadSuccessful) 8388 return null; 8389 8390 char* file; 8391 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8392 if (file !is null && file[0]) { 8393 import core.stdc.stdio; 8394 auto fp = fopen(file, "rb"); 8395 if(fp is null) 8396 return null; 8397 scope(exit) 8398 fclose(fp); 8399 fseek(fp, 0, SEEK_END); 8400 ubyte[] buffer = new ubyte[](ftell(fp)); 8401 fseek(fp, 0, SEEK_SET); 8402 8403 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8404 if(got != buffer.length) 8405 return null; 8406 8407 return buffer; 8408 } 8409 } 8410 } 8411 return null; 8412 } 8413 } 8414 8415 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8416 8417 private string weightToString(FontWeight weight) { 8418 with(FontWeight) 8419 final switch(weight) { 8420 case dontcare: return "*"; 8421 case thin: return "extralight"; 8422 case extralight: return "extralight"; 8423 case light: return "light"; 8424 case regular: return "regular"; 8425 case medium: return "medium"; 8426 case semibold: return "demibold"; 8427 case bold: return "bold"; 8428 case extrabold: return "demibold"; 8429 case heavy: return "black"; 8430 } 8431 } 8432 8433 /++ 8434 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 8435 8436 History: 8437 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8438 +/ 8439 version(X11) 8440 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8441 unload(); 8442 8443 string xfontstr; 8444 8445 if(name.length > 3 && name[0 .. 3] == "-*-") { 8446 // this is kinda a disgusting hack but if the user sends an exact 8447 // string I'd like to honor it... 8448 xfontstr = name; 8449 } else { 8450 string weightstr = weightToString(weight); 8451 string sizestr; 8452 if(size == 0) 8453 sizestr = "*"; 8454 else 8455 sizestr = toInternal!string(size); 8456 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 8457 } 8458 8459 // writeln(xfontstr); 8460 8461 auto display = XDisplayConnection.get; 8462 8463 font = XLoadQueryFont(display, xfontstr.ptr); 8464 if(font is null) 8465 return false; 8466 8467 char** lol; 8468 int lol2; 8469 char* lol3; 8470 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8471 8472 prepareFontInfo(); 8473 8474 return !isNull(); 8475 } 8476 8477 version(X11) 8478 private void prepareFontInfo() { 8479 if(font !is null) { 8480 isMonospace_ = stringWidth("l") == stringWidth("M"); 8481 ascent_ = font.max_bounds.ascent; 8482 descent_ = font.max_bounds.descent; 8483 } 8484 } 8485 8486 /++ 8487 Loads a Windows font. You probably want to use [load] instead to be more generic. 8488 8489 History: 8490 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8491 +/ 8492 version(Windows) 8493 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8494 unload(); 8495 8496 WCharzBuffer buffer = WCharzBuffer(name); 8497 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8498 8499 prepareFontInfo(hdc); 8500 8501 return !isNull(); 8502 } 8503 8504 version(Windows) 8505 void prepareFontInfo(HDC hdc = null) { 8506 if(font is null) 8507 return; 8508 8509 TEXTMETRIC tm; 8510 auto dc = hdc ? hdc : GetDC(null); 8511 auto orig = SelectObject(dc, font); 8512 GetTextMetrics(dc, &tm); 8513 SelectObject(dc, orig); 8514 if(hdc is null) 8515 ReleaseDC(null, dc); 8516 8517 width_ = tm.tmAveCharWidth; 8518 height_ = tm.tmHeight; 8519 ascent_ = tm.tmAscent; 8520 descent_ = tm.tmDescent; 8521 // 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. 8522 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 8523 } 8524 8525 8526 /++ 8527 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 8528 8529 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 8530 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 8531 8532 On Windows, it forwards directly to [loadWin32]. 8533 8534 Params: 8535 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 8536 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. 8537 weight = approximate boldness, results may vary. 8538 italic = try to get a slanted version of the given font. 8539 8540 History: 8541 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. 8542 +/ 8543 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8544 this.loadedInfo = LoadedInfo(name, size, weight, italic); 8545 version(X11) { 8546 version(with_xft) { 8547 if(name.length > 5 && name[0 .. 5] == "core:") { 8548 goto core; 8549 } 8550 8551 if(loadXft(name, size, weight, italic)) 8552 return true; 8553 // if xft fails, fallback to core to avoid breaking 8554 // code that already depended on this. 8555 } 8556 8557 core: 8558 8559 if(name.length > 5 && name[0 .. 5] == "core:") { 8560 name = name[5 .. $]; 8561 } 8562 8563 return loadCoreX(name, size, weight, italic); 8564 } else version(Windows) { 8565 return loadWin32(name, size, weight, italic); 8566 } else version(OSXCocoa) { 8567 // FIXME 8568 return false; 8569 } else static assert(0); 8570 } 8571 8572 private struct LoadedInfo { 8573 string name; 8574 int size; 8575 FontWeight weight; 8576 bool italic; 8577 } 8578 private LoadedInfo loadedInfo; 8579 8580 /// 8581 void unload() { 8582 if(isNull()) 8583 return; 8584 8585 version(X11) { 8586 auto display = XDisplayConnection.display; 8587 8588 if(display is null) 8589 return; 8590 8591 version(with_xft) { 8592 if(isXft) { 8593 if(xftFont) 8594 XftFontClose(display, xftFont); 8595 isXft = false; 8596 xftFont = null; 8597 return; 8598 } 8599 } 8600 8601 if(font && font !is ScreenPainterImplementation.defaultfont) 8602 XFreeFont(display, font); 8603 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 8604 XFreeFontSet(display, fontset); 8605 8606 font = null; 8607 fontset = null; 8608 } else version(Windows) { 8609 DeleteObject(font); 8610 font = null; 8611 } else version(OSXCocoa) { 8612 // FIXME 8613 } else static assert(0); 8614 } 8615 8616 private bool isMonospace_; 8617 8618 /++ 8619 History: 8620 Added January 16, 2021 8621 +/ 8622 bool isMonospace() { 8623 return isMonospace_; 8624 } 8625 8626 /++ 8627 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 8628 8629 History: 8630 Added March 26, 2020 8631 Documented January 16, 2021 8632 +/ 8633 int averageWidth() { 8634 version(X11) { 8635 return stringWidth("x"); 8636 } else version(Windows) 8637 return width_; 8638 else assert(0); 8639 } 8640 8641 /++ 8642 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 8643 8644 History: 8645 Added January 16, 2021 8646 +/ 8647 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 8648 // FIXME: what about tab? 8649 if(isNull) 8650 return 0; 8651 8652 version(X11) { 8653 version(with_xft) 8654 if(isXft && xftFont !is null) { 8655 //return xftFont.max_advance_width; 8656 XGlyphInfo extents; 8657 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 8658 // writeln(extents); 8659 return extents.xOff; 8660 } 8661 if(font is null) 8662 return 0; 8663 else if(fontset) { 8664 XRectangle rect; 8665 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 8666 8667 return rect.width; 8668 } else { 8669 return XTextWidth(font, s.ptr, cast(int) s.length); 8670 } 8671 } else version(Windows) { 8672 WCharzBuffer buffer = WCharzBuffer(s); 8673 8674 return stringWidth(buffer.slice, window); 8675 } 8676 else assert(0); 8677 } 8678 8679 version(Windows) 8680 /// ditto 8681 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 8682 if(isNull) 8683 return 0; 8684 version(Windows) { 8685 SIZE size; 8686 8687 prepareContext(window); 8688 scope(exit) releaseContext(); 8689 8690 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 8691 8692 return size.cx; 8693 } else { 8694 // std.conv can do this easily but it is slow to import and i don't think it is worth it 8695 static assert(0, "not implemented yet"); 8696 //return stringWidth(s, window); 8697 } 8698 } 8699 8700 private { 8701 int prepRefcount; 8702 8703 version(Windows) { 8704 HDC dc; 8705 HANDLE orig; 8706 HWND hwnd; 8707 } 8708 } 8709 /++ 8710 [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. 8711 8712 History: 8713 Added January 23, 2021 8714 +/ 8715 void prepareContext(SimpleWindow window = null) { 8716 prepRefcount++; 8717 if(prepRefcount == 1) { 8718 version(Windows) { 8719 hwnd = window is null ? null : window.impl.hwnd; 8720 dc = GetDC(hwnd); 8721 orig = SelectObject(dc, font); 8722 } 8723 } 8724 } 8725 /// ditto 8726 void releaseContext() { 8727 prepRefcount--; 8728 if(prepRefcount == 0) { 8729 version(Windows) { 8730 SelectObject(dc, orig); 8731 ReleaseDC(hwnd, dc); 8732 hwnd = null; 8733 dc = null; 8734 orig = null; 8735 } 8736 } 8737 } 8738 8739 /+ 8740 FIXME: I think I need advance and kerning pair 8741 8742 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 8743 +/ 8744 8745 /++ 8746 Returns the height of the font. 8747 8748 History: 8749 Added March 26, 2020 8750 Documented January 16, 2021 8751 +/ 8752 int height() { 8753 version(X11) { 8754 version(with_xft) 8755 if(isXft && xftFont !is null) { 8756 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 8757 } 8758 if(font is null) 8759 return 0; 8760 return font.max_bounds.ascent + font.max_bounds.descent; 8761 } else version(Windows) 8762 return height_; 8763 else assert(0); 8764 } 8765 8766 private int ascent_; 8767 private int descent_; 8768 8769 /++ 8770 Max ascent above the baseline. 8771 8772 History: 8773 Added January 22, 2021 8774 +/ 8775 int ascent() { 8776 return ascent_; 8777 } 8778 8779 /++ 8780 Max descent below the baseline. 8781 8782 History: 8783 Added January 22, 2021 8784 +/ 8785 int descent() { 8786 return descent_; 8787 } 8788 8789 /++ 8790 Loads the default font used by [ScreenPainter] if none others are loaded. 8791 8792 Returns: 8793 This method mutates the `this` object, but then returns `this` for 8794 easy chaining like: 8795 8796 --- 8797 auto font = foo.isNull ? foo : foo.loadDefault 8798 --- 8799 8800 History: 8801 Added previously, but left unimplemented until January 24, 2021. 8802 +/ 8803 OperatingSystemFont loadDefault() { 8804 unload(); 8805 8806 loadedInfo = LoadedInfo.init; 8807 8808 version(X11) { 8809 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 8810 // but meh since sdpy does its own thing, this should be ok too 8811 8812 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8813 this.font = ScreenPainterImplementation.defaultfont; 8814 this.fontset = ScreenPainterImplementation.defaultfontset; 8815 8816 prepareFontInfo(); 8817 } else version(Windows) { 8818 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8819 this.font = ScreenPainterImplementation.defaultGuiFont; 8820 8821 prepareFontInfo(); 8822 } else throw new NotYetImplementedException(); 8823 8824 return this; 8825 } 8826 8827 /// 8828 bool isNull() { 8829 version(OSXCocoa) throw new NotYetImplementedException(); else { 8830 version(with_xft) 8831 if(isXft) 8832 return xftFont is null; 8833 return font is null; 8834 } 8835 } 8836 8837 /* Metrics */ 8838 /+ 8839 GetABCWidth 8840 GetKerningPairs 8841 8842 if I do it right, I can size it all here, and match 8843 what happens when I draw the full string with the OS functions. 8844 8845 subclasses might do the same thing while getting the glyphs on images 8846 struct GlyphInfo { 8847 int glyph; 8848 8849 size_t stringIdxStart; 8850 size_t stringIdxEnd; 8851 8852 Rectangle boundingBox; 8853 } 8854 GlyphInfo[] getCharBoxes() { 8855 // XftTextExtentsUtf8 8856 return null; 8857 8858 } 8859 +/ 8860 8861 ~this() { 8862 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 8863 unload(); 8864 } 8865 } 8866 8867 version(Windows) 8868 private string sliceCString(const(wchar)[] w) { 8869 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 8870 } 8871 8872 private inout(char)[] sliceCString(inout(char)* s) { 8873 import core.stdc.string; 8874 auto len = strlen(s); 8875 return s[0 .. len]; 8876 } 8877 8878 /** 8879 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 8880 than constructing it directly. Then, it is reference counted so you can pass it 8881 at around and when the last ref goes out of scope, the buffered drawing activities 8882 are all carried out. 8883 8884 8885 Most functions use the outlineColor instead of taking a color themselves. 8886 ScreenPainter is reference counted and draws its buffer to the screen when its 8887 final reference goes out of scope. 8888 */ 8889 struct ScreenPainter { 8890 CapableOfBeingDrawnUpon window; 8891 this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle, bool manualInvalidations) { 8892 this.window = window; 8893 if(window.closed) 8894 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 8895 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 8896 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 8897 if(window.activeScreenPainter !is null) { 8898 impl = window.activeScreenPainter; 8899 if(impl.referenceCount == 0) { 8900 impl.window = window; 8901 impl.create(handle); 8902 } 8903 impl.manualInvalidations = manualInvalidations; 8904 impl.referenceCount++; 8905 // writeln("refcount ++ ", impl.referenceCount); 8906 } else { 8907 impl = new ScreenPainterImplementation; 8908 impl.window = window; 8909 impl.create(handle); 8910 impl.referenceCount = 1; 8911 impl.manualInvalidations = manualInvalidations; 8912 window.activeScreenPainter = impl; 8913 // writeln("constructed"); 8914 } 8915 8916 copyActiveOriginals(); 8917 } 8918 8919 /++ 8920 EXPERIMENTAL. subject to change. 8921 8922 When you draw a cursor, you can draw this to notify your window of where it is, 8923 for IME systems to use. 8924 +/ 8925 void notifyCursorPosition(int x, int y, int width, int height) { 8926 if(auto w = cast(SimpleWindow) window) { 8927 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 8928 } 8929 } 8930 8931 /++ 8932 If you are using manual invalidations, this informs the 8933 window system that a section needs to be redrawn. 8934 8935 If you didn't opt into manual invalidation, you don't 8936 have to call this. 8937 8938 History: 8939 Added December 30, 2021 (dub v10.5) 8940 +/ 8941 void invalidateRect(Rectangle rect) { 8942 if(impl is null) return; 8943 8944 // transform(rect) 8945 rect.left += _originX; 8946 rect.right += _originX; 8947 rect.top += _originY; 8948 rect.bottom += _originY; 8949 8950 impl.invalidateRect(rect); 8951 } 8952 8953 private Pen originalPen; 8954 private Color originalFillColor; 8955 private arsd.color.Rectangle originalClipRectangle; 8956 private OperatingSystemFont originalFont; 8957 void copyActiveOriginals() { 8958 if(impl is null) return; 8959 originalPen = impl._activePen; 8960 originalFillColor = impl._fillColor; 8961 originalClipRectangle = impl._clipRectangle; 8962 originalFont = impl._activeFont; 8963 } 8964 8965 ~this() { 8966 if(impl is null) return; 8967 impl.referenceCount--; 8968 //writeln("refcount -- ", impl.referenceCount); 8969 if(impl.referenceCount == 0) { 8970 // writeln("destructed"); 8971 impl.dispose(); 8972 *window.activeScreenPainter = ScreenPainterImplementation.init; 8973 // writeln("paint finished"); 8974 } else { 8975 // there is still an active reference, reset stuff so the 8976 // next user doesn't get weirdness via the reference 8977 this.rasterOp = RasterOp.normal; 8978 pen = originalPen; 8979 fillColor = originalFillColor; 8980 if(originalFont) 8981 setFont(originalFont); 8982 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 8983 } 8984 } 8985 8986 this(this) { 8987 if(impl is null) return; 8988 impl.referenceCount++; 8989 //writeln("refcount ++ ", impl.referenceCount); 8990 8991 copyActiveOriginals(); 8992 } 8993 8994 private int _originX; 8995 private int _originY; 8996 @property int originX() { return _originX; } 8997 @property int originY() { return _originY; } 8998 @property int originX(int a) { 8999 _originX = a; 9000 return _originX; 9001 } 9002 @property int originY(int a) { 9003 _originY = a; 9004 return _originY; 9005 } 9006 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 9007 private void transform(ref Point p) { 9008 if(impl is null) return; 9009 p.x += _originX; 9010 p.y += _originY; 9011 } 9012 9013 // this needs to be checked BEFORE the originX/Y transformation 9014 private bool isClipped(Point p) { 9015 return !currentClipRectangle.contains(p); 9016 } 9017 private bool isClipped(Point p, int width, int height) { 9018 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 9019 } 9020 private bool isClipped(Point p, Size s) { 9021 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 9022 } 9023 private bool isClipped(Point p, Point p2) { 9024 // need to ensure the end points are actually included inside, so the +1 does that 9025 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 9026 } 9027 9028 9029 /++ 9030 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9031 9032 Returns: 9033 The old clip rectangle. 9034 9035 History: 9036 Return value was `void` prior to May 10, 2021. 9037 9038 +/ 9039 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9040 if(impl is null) return currentClipRectangle; 9041 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9042 return currentClipRectangle; // no need to do anything 9043 auto old = currentClipRectangle; 9044 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9045 transform(pt); 9046 9047 impl.setClipRectangle(pt.x, pt.y, width, height); 9048 9049 return old; 9050 } 9051 9052 /// ditto 9053 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9054 if(impl is null) return currentClipRectangle; 9055 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9056 } 9057 9058 /// 9059 void setFont(OperatingSystemFont font) { 9060 if(impl is null) return; 9061 impl.setFont(font); 9062 } 9063 9064 /// 9065 int fontHeight() { 9066 if(impl is null) return 0; 9067 return impl.fontHeight(); 9068 } 9069 9070 private Pen activePen; 9071 9072 /// 9073 @property void pen(Pen p) { 9074 if(impl is null) return; 9075 activePen = p; 9076 impl.pen(p); 9077 } 9078 9079 /// 9080 @scriptable 9081 @property void outlineColor(Color c) { 9082 if(impl is null) return; 9083 if(activePen.color == c) 9084 return; 9085 activePen.color = c; 9086 impl.pen(activePen); 9087 } 9088 9089 /// 9090 @scriptable 9091 @property void fillColor(Color c) { 9092 if(impl is null) return; 9093 impl.fillColor(c); 9094 } 9095 9096 /// 9097 @property void rasterOp(RasterOp op) { 9098 if(impl is null) return; 9099 impl.rasterOp(op); 9100 } 9101 9102 9103 void updateDisplay() { 9104 // FIXME this should do what the dtor does 9105 } 9106 9107 /// 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) 9108 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9109 if(impl is null) return; 9110 if(isClipped(upperLeft, width, height)) return; 9111 transform(upperLeft); 9112 version(Windows) { 9113 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9114 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9115 RECT clip = scroll; 9116 RECT uncovered; 9117 HRGN hrgn; 9118 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9119 throw new WindowsApiException("ScrollDC", GetLastError()); 9120 9121 } else version(X11) { 9122 // FIXME: clip stuff outside this rectangle 9123 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9124 } else version(OSXCocoa) { 9125 throw new NotYetImplementedException(); 9126 } else static assert(0); 9127 } 9128 9129 /// 9130 void clear(Color color = Color.white()) { 9131 if(impl is null) return; 9132 fillColor = color; 9133 outlineColor = color; 9134 drawRectangle(Point(0, 0), window.width, window.height); 9135 } 9136 9137 /++ 9138 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9139 9140 Params: 9141 upperLeft = point on the window where the upper left corner of the image will be drawn 9142 imageUpperLeft = point on the image to start the slice to draw 9143 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. 9144 History: 9145 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9146 +/ 9147 version(OSXCocoa) {} else // NotYetImplementedException 9148 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9149 if(impl is null) return; 9150 if(isClipped(upperLeft, s.width, s.height)) return; 9151 transform(upperLeft); 9152 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9153 } 9154 9155 /// 9156 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9157 if(impl is null) return; 9158 //if(isClipped(upperLeft, w, h)) return; // FIXME 9159 transform(upperLeft); 9160 if(w == 0 || w > i.width) 9161 w = i.width; 9162 if(h == 0 || h > i.height) 9163 h = i.height; 9164 if(upperLeftOfImage.x < 0) 9165 upperLeftOfImage.x = 0; 9166 if(upperLeftOfImage.y < 0) 9167 upperLeftOfImage.y = 0; 9168 9169 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9170 } 9171 9172 /// 9173 Size textSize(in char[] text) { 9174 if(impl is null) return Size(0, 0); 9175 return impl.textSize(text); 9176 } 9177 9178 /++ 9179 Draws a string in the window with the set font (see [setFont] to change it). 9180 9181 Params: 9182 upperLeft = the upper left point of the bounding box of the text 9183 text = the string to draw 9184 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9185 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9186 +/ 9187 @scriptable 9188 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9189 if(impl is null) return; 9190 if(lowerRight.x != 0 || lowerRight.y != 0) { 9191 if(isClipped(upperLeft, lowerRight)) return; 9192 transform(lowerRight); 9193 } else { 9194 if(isClipped(upperLeft, textSize(text))) return; 9195 } 9196 transform(upperLeft); 9197 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9198 } 9199 9200 /++ 9201 Draws text using a custom font. 9202 9203 This is still MAJOR work in progress. 9204 9205 Creating a [DrawableFont] can be tricky and require additional dependencies. 9206 +/ 9207 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9208 if(impl is null) return; 9209 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9210 transform(upperLeft); 9211 font.drawString(this, upperLeft, text); 9212 } 9213 9214 version(Windows) 9215 void drawText(Point upperLeft, scope const(wchar)[] text) { 9216 if(impl is null) return; 9217 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9218 transform(upperLeft); 9219 9220 if(text.length && text[$-1] == '\n') 9221 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9222 9223 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9224 } 9225 9226 static struct TextDrawingContext { 9227 Point boundingBoxUpperLeft; 9228 Point boundingBoxLowerRight; 9229 9230 Point currentLocation; 9231 9232 Point lastDrewUpperLeft; 9233 Point lastDrewLowerRight; 9234 9235 // how do i do right aligned rich text? 9236 // i kinda want to do a pre-made drawing then right align 9237 // draw the whole block. 9238 // 9239 // That's exactly the diff: inline vs block stuff. 9240 9241 // I need to get coordinates of an inline section out too, 9242 // not just a bounding box, but a series of bounding boxes 9243 // should be ok. Consider what's needed to detect a click 9244 // on a link in the middle of a paragraph breaking a line. 9245 // 9246 // Generally, we should be able to get the rectangles of 9247 // any portion we draw. 9248 // 9249 // It also needs to tell what text is left if it overflows 9250 // out of the box, so we can do stuff like float images around 9251 // it. It should not attempt to draw a letter that would be 9252 // clipped. 9253 // 9254 // I might also turn off word wrap stuff. 9255 } 9256 9257 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9258 if(impl is null) return; 9259 // FIXME 9260 } 9261 9262 /// Drawing an individual pixel is slow. Avoid it if possible. 9263 void drawPixel(Point where) { 9264 if(impl is null) return; 9265 if(isClipped(where)) return; 9266 transform(where); 9267 impl.drawPixel(where.x, where.y); 9268 } 9269 9270 9271 /// Draws a pen using the current pen / outlineColor 9272 @scriptable 9273 void drawLine(Point starting, Point ending) { 9274 if(impl is null) return; 9275 if(isClipped(starting, ending)) return; 9276 transform(starting); 9277 transform(ending); 9278 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9279 } 9280 9281 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9282 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9283 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9284 @scriptable 9285 void drawRectangle(Point upperLeft, int width, int height) { 9286 if(impl is null) return; 9287 if(isClipped(upperLeft, width, height)) return; 9288 transform(upperLeft); 9289 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9290 } 9291 9292 /// ditto 9293 void drawRectangle(Point upperLeft, Size size) { 9294 if(impl is null) return; 9295 if(isClipped(upperLeft, size.width, size.height)) return; 9296 transform(upperLeft); 9297 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9298 } 9299 9300 /// ditto 9301 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9302 if(impl is null) return; 9303 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9304 transform(upperLeft); 9305 transform(lowerRightInclusive); 9306 impl.drawRectangle(upperLeft.x, upperLeft.y, 9307 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9308 } 9309 9310 // overload added on May 12, 2021 9311 /// ditto 9312 void drawRectangle(Rectangle rect) { 9313 drawRectangle(rect.upperLeft, rect.size); 9314 } 9315 9316 /// Arguments are the points of the bounding rectangle 9317 void drawEllipse(Point upperLeft, Point lowerRight) { 9318 if(impl is null) return; 9319 if(isClipped(upperLeft, lowerRight)) return; 9320 transform(upperLeft); 9321 transform(lowerRight); 9322 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9323 } 9324 9325 /++ 9326 start and finish are units of degrees * 64 9327 9328 History: 9329 The Windows implementation didn't match the Linux implementation until September 24, 2021. 9330 9331 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9332 +/ 9333 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 9334 if(impl is null) return; 9335 // FIXME: not actually implemented 9336 if(isClipped(upperLeft, width, height)) return; 9337 transform(upperLeft); 9338 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 9339 } 9340 9341 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9342 void drawCircle(Point upperLeft, int diameter) { 9343 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9344 } 9345 9346 /// . 9347 void drawPolygon(Point[] vertexes) { 9348 if(impl is null) return; 9349 assert(vertexes.length); 9350 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 9351 foreach(ref vertex; vertexes) { 9352 if(vertex.x < minX) 9353 minX = vertex.x; 9354 if(vertex.y < minY) 9355 minY = vertex.y; 9356 if(vertex.x > maxX) 9357 maxX = vertex.x; 9358 if(vertex.y > maxY) 9359 maxY = vertex.y; 9360 transform(vertex); 9361 } 9362 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 9363 impl.drawPolygon(vertexes); 9364 } 9365 9366 /// ditto 9367 void drawPolygon(Point[] vertexes...) { 9368 if(impl is null) return; 9369 drawPolygon(vertexes); 9370 } 9371 9372 9373 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 9374 9375 //mixin NativeScreenPainterImplementation!() impl; 9376 9377 9378 // HACK: if I mixin the impl directly, it won't let me override the copy 9379 // constructor! The linker complains about there being multiple definitions. 9380 // I'll make the best of it and reference count it though. 9381 ScreenPainterImplementation* impl; 9382 } 9383 9384 // HACK: I need a pointer to the implementation so it's separate 9385 struct ScreenPainterImplementation { 9386 CapableOfBeingDrawnUpon window; 9387 int referenceCount; 9388 mixin NativeScreenPainterImplementation!(); 9389 } 9390 9391 // FIXME: i haven't actually tested the sprite class on MS Windows 9392 9393 /** 9394 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 9395 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 9396 9397 9398 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 9399 though I'm not sure that's ideal and the implementation might change. 9400 9401 You create one by giving a window and an image. It optimizes for that window, 9402 and copies the image into it to use as the initial picture. Creating a sprite 9403 can be quite slow (especially over a network connection) so you should do it 9404 as little as possible and just hold on to your sprite handles after making them. 9405 simpledisplay does try to do its best though, using the XSHM extension if available, 9406 but you should still write your code as if it will always be slow. 9407 9408 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 9409 a fast operation - much faster than drawing the Image itself every time. 9410 9411 `Sprite` represents a scarce resource which should be freed when you 9412 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 9413 after it has been disposed. If you are unsure about this, don't take chances, 9414 just let the garbage collector do it for you. But ideally, you can manage its 9415 lifetime more efficiently. 9416 9417 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 9418 support alpha blending in its drawing at this time. That might change in the 9419 future, but if you need alpha blending right now, use OpenGL instead. See 9420 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 9421 9422 Update: on April 23, 2021, I finally added alpha blending support. You must opt 9423 in by setting the enableAlpha = true in the constructor. 9424 */ 9425 version(OSXCocoa) {} else // NotYetImplementedException 9426 class Sprite : CapableOfBeingDrawnUpon { 9427 9428 /// 9429 ScreenPainter draw() { 9430 return ScreenPainter(this, handle, false); 9431 } 9432 9433 /++ 9434 Copies the sprite's current state into a [TrueColorImage]. 9435 9436 Be warned: this can be a very slow operation 9437 9438 History: 9439 Actually implemented on March 14, 2021 9440 +/ 9441 TrueColorImage takeScreenshot() { 9442 return trueColorImageFromNativeHandle(handle, width, height); 9443 } 9444 9445 void delegate() paintingFinishedDg() { return null; } 9446 bool closed() { return false; } 9447 ScreenPainterImplementation* activeScreenPainter_; 9448 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 9449 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 9450 9451 version(Windows) 9452 private ubyte* rawData; 9453 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 9454 // ditto on the XPicture stuff 9455 9456 version(X11) { 9457 private static XRenderPictFormat* RGB24; 9458 private static XRenderPictFormat* ARGB32; 9459 9460 private Picture xrenderPicture; 9461 } 9462 9463 version(X11) 9464 private static void requireXRender() { 9465 if(!XRenderLibrary.loadAttempted) { 9466 XRenderLibrary.loadDynamicLibrary(); 9467 } 9468 9469 if(!XRenderLibrary.loadSuccessful) 9470 throw new Exception("XRender library load failure"); 9471 9472 auto display = XDisplayConnection.get; 9473 9474 // FIXME: if we migrate X displays, these need to be changed 9475 if(RGB24 is null) 9476 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 9477 if(ARGB32 is null) 9478 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 9479 } 9480 9481 protected this() {} 9482 9483 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 9484 this._width = width; 9485 this._height = height; 9486 this.enableAlpha = enableAlpha; 9487 9488 version(X11) { 9489 auto display = XDisplayConnection.get(); 9490 9491 if(enableAlpha) { 9492 requireXRender(); 9493 } 9494 9495 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 9496 9497 if(enableAlpha) { 9498 XRenderPictureAttributes attrs; 9499 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 9500 } 9501 } else version(Windows) { 9502 version(CRuntime_DigitalMars) { 9503 //if(enableAlpha) 9504 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 9505 } 9506 9507 BITMAPINFO infoheader; 9508 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 9509 infoheader.bmiHeader.biWidth = width; 9510 infoheader.bmiHeader.biHeight = height; 9511 infoheader.bmiHeader.biPlanes = 1; 9512 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 9513 infoheader.bmiHeader.biCompression = BI_RGB; 9514 9515 // FIXME: this should prolly be a device dependent bitmap... 9516 handle = CreateDIBSection( 9517 null, 9518 &infoheader, 9519 DIB_RGB_COLORS, 9520 cast(void**) &rawData, 9521 null, 9522 0); 9523 9524 if(handle is null) 9525 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 9526 } 9527 } 9528 9529 /// Makes a sprite based on the image with the initial contents from the Image 9530 this(SimpleWindow win, Image i) { 9531 this(win, i.width, i.height, i.enableAlpha); 9532 9533 version(X11) { 9534 auto display = XDisplayConnection.get(); 9535 auto gc = XCreateGC(display, this.handle, 0, null); 9536 scope(exit) XFreeGC(display, gc); 9537 if(i.usingXshm) 9538 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 9539 else 9540 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 9541 } else version(Windows) { 9542 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 9543 auto arrLength = itemsPerLine * height; 9544 rawData[0..arrLength] = i.rawData[0..arrLength]; 9545 } else version(OSXCocoa) { 9546 // FIXME: I have no idea if this is even any good 9547 ubyte* rawData; 9548 9549 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 9550 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 9551 colorSpace, 9552 kCGImageAlphaPremultipliedLast 9553 |kCGBitmapByteOrder32Big); 9554 CGColorSpaceRelease(colorSpace); 9555 rawData = CGBitmapContextGetData(context); 9556 9557 auto rdl = (width * height * 4); 9558 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 9559 } else static assert(0); 9560 } 9561 9562 /++ 9563 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 9564 9565 Params: 9566 where = point on the window where the upper left corner of the image will be drawn 9567 imageUpperLeft = point on the image to start the slice to draw 9568 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. 9569 History: 9570 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9571 +/ 9572 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9573 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 9574 } 9575 9576 /// Call this when you're ready to get rid of it 9577 void dispose() { 9578 version(X11) { 9579 staticDispose(xrenderPicture, handle); 9580 xrenderPicture = None; 9581 handle = None; 9582 } else version(Windows) { 9583 staticDispose(handle); 9584 handle = null; 9585 } else version(OSXCocoa) { 9586 staticDispose(context); 9587 context = null; 9588 } else static assert(0); 9589 9590 } 9591 9592 version(X11) 9593 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 9594 if(xrenderPicture) 9595 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 9596 if(handle) 9597 XFreePixmap(XDisplayConnection.get(), handle); 9598 } 9599 else version(Windows) 9600 static void staticDispose(HBITMAP handle) { 9601 if(handle) 9602 DeleteObject(handle); 9603 } 9604 else version(OSXCocoa) 9605 static void staticDispose(CGContextRef context) { 9606 if(context) 9607 CGContextRelease(context); 9608 } 9609 9610 ~this() { 9611 version(X11) { if(xrenderPicture || handle) 9612 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 9613 } else version(Windows) { if(handle) 9614 cleanupQueue.queue!staticDispose(handle); 9615 } else version(OSXCocoa) { if(context) 9616 cleanupQueue.queue!staticDispose(context); 9617 } else static assert(0); 9618 } 9619 9620 /// 9621 final @property int width() { return _width; } 9622 9623 /// 9624 final @property int height() { return _height; } 9625 9626 /// 9627 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 9628 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 9629 } 9630 9631 auto nativeHandle() { 9632 return handle; 9633 } 9634 9635 private: 9636 9637 int _width; 9638 int _height; 9639 bool enableAlpha; 9640 version(X11) 9641 Pixmap handle; 9642 else version(Windows) 9643 HBITMAP handle; 9644 else version(OSXCocoa) 9645 CGContextRef context; 9646 else static assert(0); 9647 } 9648 9649 /++ 9650 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 9651 9652 History: 9653 Added November 20, 2021 (dub v10.4) 9654 +/ 9655 abstract class Gradient : Sprite { 9656 protected this(int w, int h) { 9657 version(X11) { 9658 Sprite.requireXRender(); 9659 9660 super(); 9661 enableAlpha = true; 9662 _width = w; 9663 _height = h; 9664 } else version(Windows) { 9665 super(null, w, h, true); // on Windows i'm just making a bitmap myself 9666 } 9667 } 9668 9669 version(Windows) 9670 final void forEachPixel(scope Color delegate(int x, int y) dg) { 9671 auto ptr = rawData; 9672 foreach(j; 0 .. _height) 9673 foreach(i; 0 .. _width) { 9674 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 9675 *rawData = (color.a * color.b) / 255; rawData++; 9676 *rawData = (color.a * color.g) / 255; rawData++; 9677 *rawData = (color.a * color.r) / 255; rawData++; 9678 *rawData = color.a; rawData++; 9679 } 9680 } 9681 9682 version(X11) 9683 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 9684 assert(stops.length > 0); 9685 assert(stops.length <= 16, "I got lazy with buffers"); 9686 9687 XFixed[16] stopsPositions = void; 9688 XRenderColor[16] colors = void; 9689 9690 foreach(idx, stop; stops) { 9691 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 9692 auto c = stop.c; 9693 colors[idx] = XRenderColor( 9694 cast(ushort)(c.r * ushort.max / 255), 9695 cast(ushort)(c.g * ushort.max / 255), 9696 cast(ushort)(c.b * ushort.max / 255), 9697 cast(ushort)(c.a * ubyte.max) // max value here is fractional 9698 ); 9699 } 9700 9701 xrenderPicture = dg(stopsPositions, colors); 9702 } 9703 9704 /// 9705 static struct Stop { 9706 float percentage; /// between 0 and 1.0 9707 Color c; 9708 } 9709 } 9710 9711 /++ 9712 Creates a linear gradient between p1 and p2. 9713 9714 X ONLY RIGHT NOW 9715 9716 History: 9717 Added November 20, 2021 (dub v10.4) 9718 9719 Bugs: 9720 Not yet implemented on Windows. 9721 +/ 9722 class LinearGradient : Gradient { 9723 /++ 9724 9725 +/ 9726 this(Point p1, Point p2, Stop[] stops...) { 9727 super(p2.x, p2.y); 9728 9729 version(X11) { 9730 XLinearGradient gradient; 9731 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 9732 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 9733 9734 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9735 return XRenderCreateLinearGradient( 9736 XDisplayConnection.get, 9737 &gradient, 9738 stopsPositions.ptr, 9739 colors.ptr, 9740 cast(int) stops.length); 9741 }); 9742 } else version(Windows) { 9743 // FIXME 9744 forEachPixel((int x, int y) { 9745 import core.stdc.math; 9746 9747 //sqrtf( 9748 9749 return Color.transparent; 9750 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9751 }); 9752 } 9753 } 9754 } 9755 9756 /++ 9757 A conical gradient goes from color to color around a circumference from a center point. 9758 9759 X ONLY RIGHT NOW 9760 9761 History: 9762 Added November 20, 2021 (dub v10.4) 9763 9764 Bugs: 9765 Not yet implemented on Windows. 9766 +/ 9767 class ConicalGradient : Gradient { 9768 /++ 9769 9770 +/ 9771 this(Point center, float angleInDegrees, Stop[] stops...) { 9772 super(center.x * 2, center.y * 2); 9773 9774 version(X11) { 9775 XConicalGradient gradient; 9776 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 9777 gradient.angle = cast(int)(angleInDegrees * ushort.max); 9778 9779 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9780 return XRenderCreateConicalGradient( 9781 XDisplayConnection.get, 9782 &gradient, 9783 stopsPositions.ptr, 9784 colors.ptr, 9785 cast(int) stops.length); 9786 }); 9787 } else version(Windows) { 9788 // FIXME 9789 forEachPixel((int x, int y) { 9790 import core.stdc.math; 9791 9792 //sqrtf( 9793 9794 return Color.transparent; 9795 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9796 }); 9797 9798 } 9799 } 9800 } 9801 9802 /++ 9803 A radial gradient goes from color to color based on distance from the center. 9804 It is like rings of color. 9805 9806 X ONLY RIGHT NOW 9807 9808 9809 More specifically, you create two circles: an inner circle and an outer circle. 9810 The gradient is only drawn in the area outside the inner circle but inside the outer 9811 circle. The closest line between those two circles forms the line for the gradient 9812 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 9813 9814 History: 9815 Added November 20, 2021 (dub v10.4) 9816 9817 Bugs: 9818 Not yet implemented on Windows. 9819 +/ 9820 class RadialGradient : Gradient { 9821 /++ 9822 9823 +/ 9824 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 9825 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 9826 9827 version(X11) { 9828 XRadialGradient gradient; 9829 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 9830 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 9831 9832 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9833 return XRenderCreateRadialGradient( 9834 XDisplayConnection.get, 9835 &gradient, 9836 stopsPositions.ptr, 9837 colors.ptr, 9838 cast(int) stops.length); 9839 }); 9840 } else version(Windows) { 9841 // FIXME 9842 forEachPixel((int x, int y) { 9843 import core.stdc.math; 9844 9845 //sqrtf( 9846 9847 return Color.transparent; 9848 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9849 }); 9850 } 9851 } 9852 } 9853 9854 9855 9856 /+ 9857 NOT IMPLEMENTED 9858 9859 A display-stored image optimized for relatively quick drawing, like 9860 [Sprite], but this one supports alpha channel blending and does NOT 9861 support direct drawing upon it with a [ScreenPainter]. 9862 9863 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 9864 plain [ScreenPainter]... sort of. 9865 9866 On X11, it requires the Xrender extension and library. This is available 9867 almost everywhere though. 9868 9869 History: 9870 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 9871 +/ 9872 version(none) 9873 class AlphaSprite { 9874 /++ 9875 Copies the given image into it. 9876 +/ 9877 this(MemoryImage img) { 9878 9879 if(!XRenderLibrary.loadAttempted) { 9880 XRenderLibrary.loadDynamicLibrary(); 9881 9882 // FIXME: this needs to be reconstructed when the X server changes 9883 repopulateX(); 9884 } 9885 if(!XRenderLibrary.loadSuccessful) 9886 throw new Exception("XRender library load failure"); 9887 9888 // I probably need to put the alpha mask in a separate Picture 9889 // ugh 9890 // maybe the Sprite itself can have an alpha bitmask anyway 9891 9892 9893 auto display = XDisplayConnection.get(); 9894 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 9895 9896 9897 XRenderPictureAttributes attrs; 9898 9899 handle = XRenderCreatePicture( 9900 XDisplayConnection.get, 9901 pixmap, 9902 RGBA, 9903 0, 9904 &attrs 9905 ); 9906 9907 } 9908 9909 // maybe i'll use the create gradient functions too with static factories.. 9910 9911 void drawAt(ScreenPainter painter, Point where) { 9912 //painter.drawPixmap(this, where); 9913 9914 XRenderPictureAttributes attrs; 9915 9916 auto pic = XRenderCreatePicture( 9917 XDisplayConnection.get, 9918 painter.impl.d, 9919 RGB, 9920 0, 9921 &attrs 9922 ); 9923 9924 XRenderComposite( 9925 XDisplayConnection.get, 9926 3, // PictOpOver 9927 handle, 9928 None, 9929 pic, 9930 0, // src 9931 0, 9932 0, // mask 9933 0, 9934 10, // dest 9935 10, 9936 100, // width 9937 100 9938 ); 9939 9940 /+ 9941 XRenderFreePicture( 9942 XDisplayConnection.get, 9943 pic 9944 ); 9945 9946 XRenderFreePicture( 9947 XDisplayConnection.get, 9948 fill 9949 ); 9950 +/ 9951 // on Windows you can stretch but Xrender still can't :( 9952 } 9953 9954 static XRenderPictFormat* RGB; 9955 static XRenderPictFormat* RGBA; 9956 static void repopulateX() { 9957 auto display = XDisplayConnection.get; 9958 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 9959 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 9960 } 9961 9962 XPixmap pixmap; 9963 Picture handle; 9964 } 9965 9966 /// 9967 interface CapableOfBeingDrawnUpon { 9968 /// 9969 ScreenPainter draw(); 9970 /// 9971 int width(); 9972 /// 9973 int height(); 9974 protected ScreenPainterImplementation* activeScreenPainter(); 9975 protected void activeScreenPainter(ScreenPainterImplementation*); 9976 bool closed(); 9977 9978 void delegate() paintingFinishedDg(); 9979 9980 /// Be warned: this can be a very slow operation 9981 TrueColorImage takeScreenshot(); 9982 } 9983 9984 /// 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]. 9985 void flushGui() { 9986 version(X11) { 9987 auto dpy = XDisplayConnection.get(); 9988 XLockDisplay(dpy); 9989 scope(exit) XUnlockDisplay(dpy); 9990 XFlush(dpy); 9991 } 9992 } 9993 9994 /++ 9995 Runs the given code in the GUI thread when its event loop 9996 is available, blocking until it completes. This allows you 9997 to create and manipulate windows from another thread without 9998 invoking undefined behavior. 9999 10000 If this is the gui thread, it runs the code immediately. 10001 10002 If no gui thread exists yet, the current thread is assumed 10003 to be it. Attempting to create windows or run the event loop 10004 in any other thread will cause an assertion failure. 10005 10006 10007 $(TIP 10008 Did you know you can use UFCS on delegate literals? 10009 10010 () { 10011 // code here 10012 }.runInGuiThread; 10013 ) 10014 10015 Returns: 10016 `true` if the function was called, `false` if it was not. 10017 The function may not be called because the gui thread had 10018 already terminated by the time you called this. 10019 10020 History: 10021 Added April 10, 2020 (v7.2.0) 10022 10023 Return value added and implementation tweaked to avoid locking 10024 at program termination on February 24, 2021 (v9.2.1). 10025 +/ 10026 bool runInGuiThread(scope void delegate() dg) @trusted { 10027 claimGuiThread(); 10028 10029 if(thisIsGuiThread) { 10030 dg(); 10031 return true; 10032 } 10033 10034 if(guiThreadTerminating) 10035 return false; 10036 10037 import core.sync.semaphore; 10038 static Semaphore sc; 10039 if(sc is null) 10040 sc = new Semaphore(); 10041 10042 static RunQueueMember* rqm; 10043 if(rqm is null) 10044 rqm = new RunQueueMember; 10045 rqm.dg = cast(typeof(rqm.dg)) dg; 10046 rqm.signal = sc; 10047 rqm.thrown = null; 10048 10049 synchronized(runInGuiThreadLock) { 10050 runInGuiThreadQueue ~= rqm; 10051 } 10052 10053 if(!SimpleWindow.eventWakeUp()) 10054 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10055 10056 rqm.signal.wait(); 10057 auto t = rqm.thrown; 10058 10059 if(t) 10060 throw t; 10061 10062 return true; 10063 } 10064 10065 // note it runs sync if this is the gui thread.... 10066 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10067 claimGuiThread(); 10068 10069 try { 10070 10071 if(thisIsGuiThread) { 10072 dg(); 10073 return; 10074 } 10075 10076 if(guiThreadTerminating) 10077 return; 10078 10079 RunQueueMember* rqm = new RunQueueMember; 10080 rqm.dg = cast(typeof(rqm.dg)) dg; 10081 rqm.signal = null; 10082 rqm.thrown = null; 10083 10084 synchronized(runInGuiThreadLock) { 10085 runInGuiThreadQueue ~= rqm; 10086 } 10087 10088 if(!SimpleWindow.eventWakeUp()) 10089 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10090 } catch(Exception e) { 10091 if(handleError) 10092 handleError(e); 10093 } 10094 } 10095 10096 private void runPendingRunInGuiThreadDelegates() { 10097 more: 10098 RunQueueMember* next; 10099 synchronized(runInGuiThreadLock) { 10100 if(runInGuiThreadQueue.length) { 10101 next = runInGuiThreadQueue[0]; 10102 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10103 } else { 10104 next = null; 10105 } 10106 } 10107 10108 if(next) { 10109 try { 10110 next.dg(); 10111 next.thrown = null; 10112 } catch(Throwable t) { 10113 next.thrown = t; 10114 } 10115 10116 if(next.signal) 10117 next.signal.notify(); 10118 10119 goto more; 10120 } 10121 } 10122 10123 private void claimGuiThread() nothrow { 10124 import core.atomic; 10125 if(cas(&guiThreadExists_, false, true)) 10126 thisIsGuiThread = true; 10127 } 10128 10129 private struct RunQueueMember { 10130 void delegate() dg; 10131 import core.sync.semaphore; 10132 Semaphore signal; 10133 Throwable thrown; 10134 } 10135 10136 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10137 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10138 private bool thisIsGuiThread = false; 10139 private shared bool guiThreadExists_ = false; 10140 private shared bool guiThreadTerminating = false; 10141 10142 /++ 10143 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10144 event loop. All windows must be exclusively created and managed by a single thread. 10145 10146 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10147 when you call one of its constructors. 10148 10149 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10150 one. If so, you can run gui functions on it. If not, don't. The helper functions 10151 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10152 10153 The reason this function is available is in case you want to message pass between a gui 10154 thread and your current thread. If no gui thread exists or if this is the gui thread, 10155 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10156 10157 History: 10158 Added December 3, 2021 (dub v10.5) 10159 +/ 10160 public bool guiThreadExists() { 10161 return guiThreadExists_; 10162 } 10163 10164 /++ 10165 Returns `true` if this thread is either running or set to be running the 10166 simpledisplay.d gui core event loop because it owns windows. 10167 10168 It is important to keep gui-related functionality in the right thread, so you will 10169 want to `runInGuiThread` when you call them (with some specific exceptions called 10170 out in those specific functions' documentation). Notably, all windows must be 10171 created and managed only from the gui thread. 10172 10173 Will return false if simpledisplay's other functions haven't been called 10174 yet; check [guiThreadExists] in addition to this. 10175 10176 History: 10177 Added December 3, 2021 (dub v10.5) 10178 +/ 10179 public bool thisThreadRunningGui() { 10180 return thisIsGuiThread; 10181 } 10182 10183 /++ 10184 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10185 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10186 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10187 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10188 file instead if you are in one of those situations). 10189 10190 It does not support outputting very many types; just strings and ints are likely to actually work. 10191 10192 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10193 is unspecified meaning I can change it at any time. The only point of this function is to help 10194 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10195 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10196 in those contexts. 10197 10198 $(WARNING 10199 I reserve the right to change this function at any time. You can use it if it helps you 10200 but do not rely on it for anything permanent. 10201 ) 10202 10203 History: 10204 Added December 3, 2021. Not formally supported under any stable tag. 10205 +/ 10206 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10207 try { 10208 version(Windows) { 10209 import core.sys.windows.wincon; 10210 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10211 AllocConsole(); 10212 const(char)* fn = "CONOUT$"; 10213 } else version(Posix) { 10214 const(char)* fn = "/dev/tty"; 10215 } else static assert(0, "Function not implemented for your system"); 10216 10217 if(fileOverride.length) 10218 fn = fileOverride.ptr; 10219 10220 import core.stdc.stdio; 10221 auto fp = fopen(fn, "wt"); 10222 if(fp is null) return; 10223 scope(exit) fclose(fp); 10224 10225 string str; 10226 foreach(item; t) { 10227 static if(is(typeof(item) : const(char)[])) 10228 str ~= item; 10229 else 10230 str ~= toInternal!string(item); 10231 str ~= " "; 10232 } 10233 str ~= "\n"; 10234 10235 fwrite(str.ptr, 1, str.length, fp); 10236 fflush(fp); 10237 } catch(Exception e) { 10238 // sorry no hope 10239 } 10240 } 10241 10242 private void guiThreadFinalize() { 10243 assert(thisIsGuiThread); 10244 10245 guiThreadTerminating = true; // don't add any more from this point on 10246 runPendingRunInGuiThreadDelegates(); 10247 } 10248 10249 /+ 10250 interface IPromise { 10251 void reportProgress(int current, int max, string message); 10252 10253 /+ // not formally in cuz of templates but still 10254 IPromise Then(); 10255 IPromise Catch(); 10256 IPromise Finally(); 10257 +/ 10258 } 10259 10260 /+ 10261 auto promise = async({ ... }); 10262 promise.Then(whatever). 10263 Then(whateverelse). 10264 Catch((exception) { }); 10265 10266 10267 A promise is run inside a fiber and it looks something like: 10268 10269 try { 10270 auto res = whatever(); 10271 auto res2 = whateverelse(res); 10272 } catch(Exception e) { 10273 { }(e); 10274 } 10275 10276 When a thing succeeds, it is passed as an arg to the next 10277 +/ 10278 class Promise(T) : IPromise { 10279 auto Then() { return null; } 10280 auto Catch() { return null; } 10281 auto Finally() { return null; } 10282 10283 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10284 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10285 T await(); 10286 } 10287 10288 interface Task { 10289 } 10290 10291 interface Resolvable(T) : Task { 10292 void run(); 10293 10294 void resolve(T); 10295 10296 Resolvable!T then(void delegate(T)); // returns a new promise 10297 Resolvable!T error(Throwable); // js catch 10298 Resolvable!T completed(); // js finally 10299 10300 } 10301 10302 /++ 10303 Runs `work` in a helper thread and sends its return value back to the main gui 10304 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10305 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10306 kill the program. 10307 10308 You can call reportProgress(position, max, message) to update your parent window 10309 on your progress. 10310 10311 I should also use `shared` methods. FIXME 10312 10313 History: 10314 Added March 6, 2021 (dub version 9.3). 10315 +/ 10316 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 10317 uponCompletion(work(null)); 10318 } 10319 10320 +/ 10321 10322 /// Used internal to dispatch events to various classes. 10323 interface CapableOfHandlingNativeEvent { 10324 NativeEventHandler getNativeEventHandler(); 10325 10326 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 10327 10328 version(X11) { 10329 // if this is impossible, you are allowed to just throw from it 10330 // Note: if you call it from another object, set a flag cuz the manger will call you again 10331 void recreateAfterDisconnect(); 10332 // discard any *connection specific* state, but keep enough that you 10333 // can be recreated if possible. discardConnectionState() is always called immediately 10334 // before recreateAfterDisconnect(), so you can set a flag there to decide if 10335 // you need initialization order 10336 void discardConnectionState(); 10337 } 10338 } 10339 10340 version(X11) 10341 /++ 10342 State of keys on mouse events, especially motion. 10343 10344 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 10345 +/ 10346 enum ModifierState : uint { 10347 shift = 1, /// 10348 capsLock = 2, /// 10349 ctrl = 4, /// 10350 alt = 8, /// Not always available on Windows 10351 windows = 64, /// ditto 10352 numLock = 16, /// 10353 10354 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10355 middleButtonDown = 512, /// ditto 10356 rightButtonDown = 1024, /// ditto 10357 } 10358 else version(Windows) 10359 /// ditto 10360 enum ModifierState : uint { 10361 shift = 4, /// 10362 ctrl = 8, /// 10363 10364 // i'm not sure if the next two are available 10365 alt = 256, /// not always available on Windows 10366 windows = 512, /// ditto 10367 10368 capsLock = 1024, /// 10369 numLock = 2048, /// 10370 10371 leftButtonDown = 1, /// not available on key events 10372 middleButtonDown = 16, /// ditto 10373 rightButtonDown = 2, /// ditto 10374 10375 backButtonDown = 0x20, /// not available on X 10376 forwardButtonDown = 0x40, /// ditto 10377 } 10378 else version(OSXCocoa) 10379 // FIXME FIXME NotYetImplementedException 10380 enum ModifierState : uint { 10381 shift = 1, /// 10382 capsLock = 2, /// 10383 ctrl = 4, /// 10384 alt = 8, /// Not always available on Windows 10385 windows = 64, /// ditto 10386 numLock = 16, /// 10387 10388 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10389 middleButtonDown = 512, /// ditto 10390 rightButtonDown = 1024, /// ditto 10391 } 10392 10393 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 10394 enum MouseButton : int { 10395 none = 0, 10396 left = 1, /// 10397 right = 2, /// 10398 middle = 4, /// 10399 wheelUp = 8, /// 10400 wheelDown = 16, /// 10401 backButton = 32, /// often found on the thumb and used for back in browsers 10402 forwardButton = 64, /// often found on the thumb and used for forward in browsers 10403 } 10404 10405 version(X11) { 10406 // FIXME: match ASCII whenever we can. Most of it is already there, 10407 // but there's a few exceptions and mismatches with Windows 10408 10409 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 10410 enum Key { 10411 Escape = 0xff1b, /// 10412 F1 = 0xffbe, /// 10413 F2 = 0xffbf, /// 10414 F3 = 0xffc0, /// 10415 F4 = 0xffc1, /// 10416 F5 = 0xffc2, /// 10417 F6 = 0xffc3, /// 10418 F7 = 0xffc4, /// 10419 F8 = 0xffc5, /// 10420 F9 = 0xffc6, /// 10421 F10 = 0xffc7, /// 10422 F11 = 0xffc8, /// 10423 F12 = 0xffc9, /// 10424 PrintScreen = 0xff61, /// 10425 ScrollLock = 0xff14, /// 10426 Pause = 0xff13, /// 10427 Grave = 0x60, /// The $(BACKTICK) ~ key 10428 // number keys across the top of the keyboard 10429 N1 = 0x31, /// Number key atop the keyboard 10430 N2 = 0x32, /// 10431 N3 = 0x33, /// 10432 N4 = 0x34, /// 10433 N5 = 0x35, /// 10434 N6 = 0x36, /// 10435 N7 = 0x37, /// 10436 N8 = 0x38, /// 10437 N9 = 0x39, /// 10438 N0 = 0x30, /// 10439 Dash = 0x2d, /// 10440 Equals = 0x3d, /// 10441 Backslash = 0x5c, /// The \ | key 10442 Backspace = 0xff08, /// 10443 Insert = 0xff63, /// 10444 Home = 0xff50, /// 10445 PageUp = 0xff55, /// 10446 Delete = 0xffff, /// 10447 End = 0xff57, /// 10448 PageDown = 0xff56, /// 10449 Up = 0xff52, /// 10450 Down = 0xff54, /// 10451 Left = 0xff51, /// 10452 Right = 0xff53, /// 10453 10454 Tab = 0xff09, /// 10455 Q = 0x71, /// 10456 W = 0x77, /// 10457 E = 0x65, /// 10458 R = 0x72, /// 10459 T = 0x74, /// 10460 Y = 0x79, /// 10461 U = 0x75, /// 10462 I = 0x69, /// 10463 O = 0x6f, /// 10464 P = 0x70, /// 10465 LeftBracket = 0x5b, /// the [ { key 10466 RightBracket = 0x5d, /// the ] } key 10467 CapsLock = 0xffe5, /// 10468 A = 0x61, /// 10469 S = 0x73, /// 10470 D = 0x64, /// 10471 F = 0x66, /// 10472 G = 0x67, /// 10473 H = 0x68, /// 10474 J = 0x6a, /// 10475 K = 0x6b, /// 10476 L = 0x6c, /// 10477 Semicolon = 0x3b, /// 10478 Apostrophe = 0x27, /// 10479 Enter = 0xff0d, /// 10480 Shift = 0xffe1, /// 10481 Z = 0x7a, /// 10482 X = 0x78, /// 10483 C = 0x63, /// 10484 V = 0x76, /// 10485 B = 0x62, /// 10486 N = 0x6e, /// 10487 M = 0x6d, /// 10488 Comma = 0x2c, /// 10489 Period = 0x2e, /// 10490 Slash = 0x2f, /// the / ? key 10491 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 10492 Ctrl = 0xffe3, /// 10493 Windows = 0xffeb, /// 10494 Alt = 0xffe9, /// 10495 Space = 0x20, /// 10496 Alt_r = 0xffea, /// ditto of shift_r 10497 Windows_r = 0xffec, /// 10498 Menu = 0xff67, /// 10499 Ctrl_r = 0xffe4, /// 10500 10501 NumLock = 0xff7f, /// 10502 Divide = 0xffaf, /// The / key on the number pad 10503 Multiply = 0xffaa, /// The * key on the number pad 10504 Minus = 0xffad, /// The - key on the number pad 10505 Plus = 0xffab, /// The + key on the number pad 10506 PadEnter = 0xff8d, /// Numberpad enter key 10507 Pad1 = 0xff9c, /// Numberpad keys 10508 Pad2 = 0xff99, /// 10509 Pad3 = 0xff9b, /// 10510 Pad4 = 0xff96, /// 10511 Pad5 = 0xff9d, /// 10512 Pad6 = 0xff98, /// 10513 Pad7 = 0xff95, /// 10514 Pad8 = 0xff97, /// 10515 Pad9 = 0xff9a, /// 10516 Pad0 = 0xff9e, /// 10517 PadDot = 0xff9f, /// 10518 } 10519 } else version(Windows) { 10520 // the character here is for en-us layouts and for illustration only 10521 // if you actually want to get characters, wait for character events 10522 // (the argument to your event handler is simply a dchar) 10523 // those will be converted by the OS for the right locale. 10524 10525 enum Key { 10526 Escape = 0x1b, 10527 F1 = 0x70, 10528 F2 = 0x71, 10529 F3 = 0x72, 10530 F4 = 0x73, 10531 F5 = 0x74, 10532 F6 = 0x75, 10533 F7 = 0x76, 10534 F8 = 0x77, 10535 F9 = 0x78, 10536 F10 = 0x79, 10537 F11 = 0x7a, 10538 F12 = 0x7b, 10539 PrintScreen = 0x2c, 10540 ScrollLock = 0x91, 10541 Pause = 0x13, 10542 Grave = 0xc0, 10543 // number keys across the top of the keyboard 10544 N1 = 0x31, 10545 N2 = 0x32, 10546 N3 = 0x33, 10547 N4 = 0x34, 10548 N5 = 0x35, 10549 N6 = 0x36, 10550 N7 = 0x37, 10551 N8 = 0x38, 10552 N9 = 0x39, 10553 N0 = 0x30, 10554 Dash = 0xbd, 10555 Equals = 0xbb, 10556 Backslash = 0xdc, 10557 Backspace = 0x08, 10558 Insert = 0x2d, 10559 Home = 0x24, 10560 PageUp = 0x21, 10561 Delete = 0x2e, 10562 End = 0x23, 10563 PageDown = 0x22, 10564 Up = 0x26, 10565 Down = 0x28, 10566 Left = 0x25, 10567 Right = 0x27, 10568 10569 Tab = 0x09, 10570 Q = 0x51, 10571 W = 0x57, 10572 E = 0x45, 10573 R = 0x52, 10574 T = 0x54, 10575 Y = 0x59, 10576 U = 0x55, 10577 I = 0x49, 10578 O = 0x4f, 10579 P = 0x50, 10580 LeftBracket = 0xdb, 10581 RightBracket = 0xdd, 10582 CapsLock = 0x14, 10583 A = 0x41, 10584 S = 0x53, 10585 D = 0x44, 10586 F = 0x46, 10587 G = 0x47, 10588 H = 0x48, 10589 J = 0x4a, 10590 K = 0x4b, 10591 L = 0x4c, 10592 Semicolon = 0xba, 10593 Apostrophe = 0xde, 10594 Enter = 0x0d, 10595 Shift = 0x10, 10596 Z = 0x5a, 10597 X = 0x58, 10598 C = 0x43, 10599 V = 0x56, 10600 B = 0x42, 10601 N = 0x4e, 10602 M = 0x4d, 10603 Comma = 0xbc, 10604 Period = 0xbe, 10605 Slash = 0xbf, 10606 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10607 Ctrl = 0x11, 10608 Windows = 0x5b, 10609 Alt = -5, // FIXME 10610 Space = 0x20, 10611 Alt_r = 0xffea, // ditto of shift_r 10612 Windows_r = 0x5c, // ditto of shift_r 10613 Menu = 0x5d, 10614 Ctrl_r = 0xa3, // ditto of shift_r 10615 10616 NumLock = 0x90, 10617 Divide = 0x6f, 10618 Multiply = 0x6a, 10619 Minus = 0x6d, 10620 Plus = 0x6b, 10621 PadEnter = -8, // FIXME 10622 Pad1 = 0x61, 10623 Pad2 = 0x62, 10624 Pad3 = 0x63, 10625 Pad4 = 0x64, 10626 Pad5 = 0x65, 10627 Pad6 = 0x66, 10628 Pad7 = 0x67, 10629 Pad8 = 0x68, 10630 Pad9 = 0x69, 10631 Pad0 = 0x60, 10632 PadDot = 0x6e, 10633 } 10634 10635 // I'm keeping this around for reference purposes 10636 // ideally all these buttons will be listed for all platforms, 10637 // but now now I'm just focusing on my US keyboard 10638 version(none) 10639 enum Key { 10640 LBUTTON = 0x01, 10641 RBUTTON = 0x02, 10642 CANCEL = 0x03, 10643 MBUTTON = 0x04, 10644 //static if (_WIN32_WINNT > = 0x500) { 10645 XBUTTON1 = 0x05, 10646 XBUTTON2 = 0x06, 10647 //} 10648 BACK = 0x08, 10649 TAB = 0x09, 10650 CLEAR = 0x0C, 10651 RETURN = 0x0D, 10652 SHIFT = 0x10, 10653 CONTROL = 0x11, 10654 MENU = 0x12, 10655 PAUSE = 0x13, 10656 CAPITAL = 0x14, 10657 KANA = 0x15, 10658 HANGEUL = 0x15, 10659 HANGUL = 0x15, 10660 JUNJA = 0x17, 10661 FINAL = 0x18, 10662 HANJA = 0x19, 10663 KANJI = 0x19, 10664 ESCAPE = 0x1B, 10665 CONVERT = 0x1C, 10666 NONCONVERT = 0x1D, 10667 ACCEPT = 0x1E, 10668 MODECHANGE = 0x1F, 10669 SPACE = 0x20, 10670 PRIOR = 0x21, 10671 NEXT = 0x22, 10672 END = 0x23, 10673 HOME = 0x24, 10674 LEFT = 0x25, 10675 UP = 0x26, 10676 RIGHT = 0x27, 10677 DOWN = 0x28, 10678 SELECT = 0x29, 10679 PRINT = 0x2A, 10680 EXECUTE = 0x2B, 10681 SNAPSHOT = 0x2C, 10682 INSERT = 0x2D, 10683 DELETE = 0x2E, 10684 HELP = 0x2F, 10685 LWIN = 0x5B, 10686 RWIN = 0x5C, 10687 APPS = 0x5D, 10688 SLEEP = 0x5F, 10689 NUMPAD0 = 0x60, 10690 NUMPAD1 = 0x61, 10691 NUMPAD2 = 0x62, 10692 NUMPAD3 = 0x63, 10693 NUMPAD4 = 0x64, 10694 NUMPAD5 = 0x65, 10695 NUMPAD6 = 0x66, 10696 NUMPAD7 = 0x67, 10697 NUMPAD8 = 0x68, 10698 NUMPAD9 = 0x69, 10699 MULTIPLY = 0x6A, 10700 ADD = 0x6B, 10701 SEPARATOR = 0x6C, 10702 SUBTRACT = 0x6D, 10703 DECIMAL = 0x6E, 10704 DIVIDE = 0x6F, 10705 F1 = 0x70, 10706 F2 = 0x71, 10707 F3 = 0x72, 10708 F4 = 0x73, 10709 F5 = 0x74, 10710 F6 = 0x75, 10711 F7 = 0x76, 10712 F8 = 0x77, 10713 F9 = 0x78, 10714 F10 = 0x79, 10715 F11 = 0x7A, 10716 F12 = 0x7B, 10717 F13 = 0x7C, 10718 F14 = 0x7D, 10719 F15 = 0x7E, 10720 F16 = 0x7F, 10721 F17 = 0x80, 10722 F18 = 0x81, 10723 F19 = 0x82, 10724 F20 = 0x83, 10725 F21 = 0x84, 10726 F22 = 0x85, 10727 F23 = 0x86, 10728 F24 = 0x87, 10729 NUMLOCK = 0x90, 10730 SCROLL = 0x91, 10731 LSHIFT = 0xA0, 10732 RSHIFT = 0xA1, 10733 LCONTROL = 0xA2, 10734 RCONTROL = 0xA3, 10735 LMENU = 0xA4, 10736 RMENU = 0xA5, 10737 //static if (_WIN32_WINNT > = 0x500) { 10738 BROWSER_BACK = 0xA6, 10739 BROWSER_FORWARD = 0xA7, 10740 BROWSER_REFRESH = 0xA8, 10741 BROWSER_STOP = 0xA9, 10742 BROWSER_SEARCH = 0xAA, 10743 BROWSER_FAVORITES = 0xAB, 10744 BROWSER_HOME = 0xAC, 10745 VOLUME_MUTE = 0xAD, 10746 VOLUME_DOWN = 0xAE, 10747 VOLUME_UP = 0xAF, 10748 MEDIA_NEXT_TRACK = 0xB0, 10749 MEDIA_PREV_TRACK = 0xB1, 10750 MEDIA_STOP = 0xB2, 10751 MEDIA_PLAY_PAUSE = 0xB3, 10752 LAUNCH_MAIL = 0xB4, 10753 LAUNCH_MEDIA_SELECT = 0xB5, 10754 LAUNCH_APP1 = 0xB6, 10755 LAUNCH_APP2 = 0xB7, 10756 //} 10757 OEM_1 = 0xBA, 10758 //static if (_WIN32_WINNT > = 0x500) { 10759 OEM_PLUS = 0xBB, 10760 OEM_COMMA = 0xBC, 10761 OEM_MINUS = 0xBD, 10762 OEM_PERIOD = 0xBE, 10763 //} 10764 OEM_2 = 0xBF, 10765 OEM_3 = 0xC0, 10766 OEM_4 = 0xDB, 10767 OEM_5 = 0xDC, 10768 OEM_6 = 0xDD, 10769 OEM_7 = 0xDE, 10770 OEM_8 = 0xDF, 10771 //static if (_WIN32_WINNT > = 0x500) { 10772 OEM_102 = 0xE2, 10773 //} 10774 PROCESSKEY = 0xE5, 10775 //static if (_WIN32_WINNT > = 0x500) { 10776 PACKET = 0xE7, 10777 //} 10778 ATTN = 0xF6, 10779 CRSEL = 0xF7, 10780 EXSEL = 0xF8, 10781 EREOF = 0xF9, 10782 PLAY = 0xFA, 10783 ZOOM = 0xFB, 10784 NONAME = 0xFC, 10785 PA1 = 0xFD, 10786 OEM_CLEAR = 0xFE, 10787 } 10788 10789 } else version(OSXCocoa) { 10790 // FIXME 10791 enum Key { 10792 Escape = 0x1b, 10793 F1 = 0x70, 10794 F2 = 0x71, 10795 F3 = 0x72, 10796 F4 = 0x73, 10797 F5 = 0x74, 10798 F6 = 0x75, 10799 F7 = 0x76, 10800 F8 = 0x77, 10801 F9 = 0x78, 10802 F10 = 0x79, 10803 F11 = 0x7a, 10804 F12 = 0x7b, 10805 PrintScreen = 0x2c, 10806 ScrollLock = -2, // FIXME 10807 Pause = -3, // FIXME 10808 Grave = 0xc0, 10809 // number keys across the top of the keyboard 10810 N1 = 0x31, 10811 N2 = 0x32, 10812 N3 = 0x33, 10813 N4 = 0x34, 10814 N5 = 0x35, 10815 N6 = 0x36, 10816 N7 = 0x37, 10817 N8 = 0x38, 10818 N9 = 0x39, 10819 N0 = 0x30, 10820 Dash = 0xbd, 10821 Equals = 0xbb, 10822 Backslash = 0xdc, 10823 Backspace = 0x08, 10824 Insert = 0x2d, 10825 Home = 0x24, 10826 PageUp = 0x21, 10827 Delete = 0x2e, 10828 End = 0x23, 10829 PageDown = 0x22, 10830 Up = 0x26, 10831 Down = 0x28, 10832 Left = 0x25, 10833 Right = 0x27, 10834 10835 Tab = 0x09, 10836 Q = 0x51, 10837 W = 0x57, 10838 E = 0x45, 10839 R = 0x52, 10840 T = 0x54, 10841 Y = 0x59, 10842 U = 0x55, 10843 I = 0x49, 10844 O = 0x4f, 10845 P = 0x50, 10846 LeftBracket = 0xdb, 10847 RightBracket = 0xdd, 10848 CapsLock = 0x14, 10849 A = 0x41, 10850 S = 0x53, 10851 D = 0x44, 10852 F = 0x46, 10853 G = 0x47, 10854 H = 0x48, 10855 J = 0x4a, 10856 K = 0x4b, 10857 L = 0x4c, 10858 Semicolon = 0xba, 10859 Apostrophe = 0xde, 10860 Enter = 0x0d, 10861 Shift = 0x10, 10862 Z = 0x5a, 10863 X = 0x58, 10864 C = 0x43, 10865 V = 0x56, 10866 B = 0x42, 10867 N = 0x4e, 10868 M = 0x4d, 10869 Comma = 0xbc, 10870 Period = 0xbe, 10871 Slash = 0xbf, 10872 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10873 Ctrl = 0x11, 10874 Windows = 0x5b, 10875 Alt = -5, // FIXME 10876 Space = 0x20, 10877 Alt_r = 0xffea, // ditto of shift_r 10878 Windows_r = -6, // FIXME 10879 Menu = 0x5d, 10880 Ctrl_r = -7, // FIXME 10881 10882 NumLock = 0x90, 10883 Divide = 0x6f, 10884 Multiply = 0x6a, 10885 Minus = 0x6d, 10886 Plus = 0x6b, 10887 PadEnter = -8, // FIXME 10888 // FIXME for the rest of these: 10889 Pad1 = 0xff9c, 10890 Pad2 = 0xff99, 10891 Pad3 = 0xff9b, 10892 Pad4 = 0xff96, 10893 Pad5 = 0xff9d, 10894 Pad6 = 0xff98, 10895 Pad7 = 0xff95, 10896 Pad8 = 0xff97, 10897 Pad9 = 0xff9a, 10898 Pad0 = 0xff9e, 10899 PadDot = 0xff9f, 10900 } 10901 10902 } 10903 10904 /* Additional utilities */ 10905 10906 10907 Color fromHsl(real h, real s, real l) { 10908 return arsd.color.fromHsl([h,s,l]); 10909 } 10910 10911 10912 10913 /* ********** What follows is the system-specific implementations *********/ 10914 version(Windows) { 10915 10916 10917 // helpers for making HICONs from MemoryImages 10918 class WindowsIcon { 10919 struct Win32Icon(int colorCount) { 10920 align(1): 10921 uint biSize; 10922 int biWidth; 10923 int biHeight; 10924 ushort biPlanes; 10925 ushort biBitCount; 10926 uint biCompression; 10927 uint biSizeImage; 10928 int biXPelsPerMeter; 10929 int biYPelsPerMeter; 10930 uint biClrUsed; 10931 uint biClrImportant; 10932 RGBQUAD[colorCount] biColors; 10933 /* Pixels: 10934 Uint8 pixels[] 10935 */ 10936 /* Mask: 10937 Uint8 mask[] 10938 */ 10939 10940 // FIXME: this sux, needs to be dynamic 10941 ubyte[/*4096*/ 256*256*4 + 256*256/8] data; 10942 10943 void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 10944 width = mi.width; 10945 height = mi.height; 10946 10947 auto indexedImage = cast(IndexedImage) mi; 10948 if(indexedImage is null) 10949 indexedImage = quantize(mi.getAsTrueColorImage()); 10950 10951 assert(width <= 256, "image too wide"); 10952 assert(height <= 256, "image too tall"); 10953 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 10954 assert(height %4 == 0, "image not multiple of 4 height"); 10955 10956 int icon_plen = height*((width+3)&~3); 10957 int icon_mlen = height*((((width+7)/8)+3)&~3); 10958 icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 10959 10960 biSize = 40; 10961 biWidth = width; 10962 biHeight = height*2; 10963 biPlanes = 1; 10964 biBitCount = 8; 10965 biSizeImage = icon_plen+icon_mlen; 10966 10967 int offset = 0; 10968 int andOff = icon_plen * 8; // the and offset is in bits 10969 for(int y = height - 1; y >= 0; y--) { 10970 int off2 = y * width; 10971 foreach(x; 0 .. width) { 10972 const b = indexedImage.data[off2 + x]; 10973 data[offset] = b; 10974 offset++; 10975 10976 const andBit = andOff % 8; 10977 const andIdx = andOff / 8; 10978 assert(b < indexedImage.palette.length); 10979 // this is anded to the destination, since and 0 means erase, 10980 // we want that to be opaque, and 1 for transparent 10981 auto transparent = (indexedImage.palette[b].a <= 127); 10982 data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0); 10983 10984 andOff++; 10985 } 10986 10987 andOff += andOff % 32; 10988 } 10989 10990 foreach(idx, entry; indexedImage.palette) { 10991 if(entry.a > 127) { 10992 biColors[idx].rgbBlue = entry.b; 10993 biColors[idx].rgbGreen = entry.g; 10994 biColors[idx].rgbRed = entry.r; 10995 } else { 10996 biColors[idx].rgbBlue = 255; 10997 biColors[idx].rgbGreen = 255; 10998 biColors[idx].rgbRed = 255; 10999 } 11000 } 11001 11002 /* 11003 data[0..icon_plen] = getFlippedUnfilteredDatastream(png); 11004 data[icon_plen..icon_plen+icon_mlen] = getANDMask(png); 11005 //icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0); 11006 auto pngMap = fetchPaletteWin32(png); 11007 biColors[0..pngMap.length] = pngMap[]; 11008 */ 11009 } 11010 } 11011 11012 11013 Win32Icon!(256) icon_win32; 11014 11015 11016 this(MemoryImage mi) { 11017 int icon_len, width, height; 11018 11019 icon_win32.fromMemoryImage(mi, icon_len, width, height); 11020 11021 /* 11022 PNG* png = readPnpngData); 11023 PNGHeader pngh = getHeader(png); 11024 void* icon_win32; 11025 if(pngh.depth == 4) { 11026 auto i = new Win32Icon!(16); 11027 i.fromPNG(png, pngh, icon_len, width, height); 11028 icon_win32 = i; 11029 } 11030 else if(pngh.depth == 8) { 11031 auto i = new Win32Icon!(256); 11032 i.fromPNG(png, pngh, icon_len, width, height); 11033 icon_win32 = i; 11034 } else assert(0); 11035 */ 11036 11037 hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0); 11038 11039 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11040 } 11041 11042 ~this() { 11043 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11044 DestroyIcon(hIcon); 11045 } 11046 11047 HICON hIcon; 11048 } 11049 11050 11051 11052 11053 11054 11055 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11056 alias HWND NativeWindowHandle; 11057 11058 extern(Windows) 11059 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11060 try { 11061 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11062 // it returns zero if the message is handled, so we won't do anything more there 11063 // do I like that though? 11064 int mustReturn; 11065 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11066 if(mustReturn) 11067 return ret; 11068 } 11069 11070 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11071 if(window.getNativeEventHandler !is null) { 11072 int mustReturn; 11073 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11074 if(mustReturn) 11075 return ret; 11076 } 11077 if(auto w = cast(SimpleWindow) (*window)) 11078 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11079 else 11080 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11081 } else { 11082 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11083 } 11084 } catch (Exception e) { 11085 try { 11086 sdpy_abort(e); 11087 return 0; 11088 } catch(Exception e) { assert(0); } 11089 } 11090 } 11091 11092 void sdpy_abort(Throwable e) nothrow { 11093 try 11094 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11095 catch(Exception e) 11096 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11097 ExitProcess(1); 11098 } 11099 11100 mixin template NativeScreenPainterImplementation() { 11101 HDC hdc; 11102 HWND hwnd; 11103 //HDC windowHdc; 11104 HBITMAP oldBmp; 11105 11106 void create(NativeWindowHandle window) { 11107 hwnd = window; 11108 11109 if(auto sw = cast(SimpleWindow) this.window) { 11110 // drawing on a window, double buffer 11111 auto windowHdc = GetDC(hwnd); 11112 11113 auto buffer = sw.impl.buffer; 11114 if(buffer is null) { 11115 hdc = windowHdc; 11116 windowDc = true; 11117 } else { 11118 hdc = CreateCompatibleDC(windowHdc); 11119 11120 ReleaseDC(hwnd, windowHdc); 11121 11122 oldBmp = SelectObject(hdc, buffer); 11123 } 11124 } else { 11125 // drawing on something else, draw directly 11126 hdc = CreateCompatibleDC(null); 11127 SelectObject(hdc, window); 11128 } 11129 11130 // X doesn't draw a text background, so neither should we 11131 SetBkMode(hdc, TRANSPARENT); 11132 11133 ensureDefaultFontLoaded(); 11134 11135 if(defaultGuiFont) { 11136 SelectObject(hdc, defaultGuiFont); 11137 // DeleteObject(defaultGuiFont); 11138 } 11139 } 11140 11141 static HFONT defaultGuiFont; 11142 static void ensureDefaultFontLoaded() { 11143 static bool triedDefaultGuiFont = false; 11144 if(!triedDefaultGuiFont) { 11145 NONCLIENTMETRICS params; 11146 params.cbSize = params.sizeof; 11147 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11148 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11149 } 11150 triedDefaultGuiFont = true; 11151 } 11152 } 11153 11154 private OperatingSystemFont _activeFont; 11155 11156 void setFont(OperatingSystemFont font) { 11157 _activeFont = font; 11158 if(font && font.font) { 11159 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11160 // error... how to handle tho? 11161 } else { 11162 11163 } 11164 } 11165 else if(defaultGuiFont) 11166 SelectObject(hdc, defaultGuiFont); 11167 } 11168 11169 arsd.color.Rectangle _clipRectangle; 11170 11171 void setClipRectangle(int x, int y, int width, int height) { 11172 auto old = _clipRectangle; 11173 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11174 if(old == _clipRectangle) 11175 return; 11176 11177 if(width == 0 || height == 0) { 11178 SelectClipRgn(hdc, null); 11179 } else { 11180 auto region = CreateRectRgn(x, y, x + width, y + height); 11181 SelectClipRgn(hdc, region); 11182 DeleteObject(region); 11183 } 11184 } 11185 11186 11187 // just because we can on Windows... 11188 //void create(Image image); 11189 11190 void invalidateRect(Rectangle invalidRect) { 11191 RECT rect; 11192 rect.left = invalidRect.left; 11193 rect.right = invalidRect.right; 11194 rect.top = invalidRect.top; 11195 rect.bottom = invalidRect.bottom; 11196 InvalidateRect(hwnd, &rect, false); 11197 } 11198 bool manualInvalidations; 11199 11200 void dispose() { 11201 // FIXME: this.window.width/height is probably wrong 11202 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11203 // ReleaseDC(hwnd, windowHdc); 11204 11205 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11206 if(cast(SimpleWindow) this.window) { 11207 if(!manualInvalidations) 11208 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11209 } 11210 11211 if(originalPen !is null) 11212 SelectObject(hdc, originalPen); 11213 if(currentPen !is null) 11214 DeleteObject(currentPen); 11215 if(originalBrush !is null) 11216 SelectObject(hdc, originalBrush); 11217 if(currentBrush !is null) 11218 DeleteObject(currentBrush); 11219 11220 SelectObject(hdc, oldBmp); 11221 11222 if(windowDc) 11223 ReleaseDC(hwnd, hdc); 11224 else 11225 DeleteDC(hdc); 11226 11227 if(window.paintingFinishedDg !is null) 11228 window.paintingFinishedDg()(); 11229 } 11230 11231 bool windowDc; 11232 HPEN originalPen; 11233 HPEN currentPen; 11234 11235 Pen _activePen; 11236 11237 Color _outlineColor; 11238 11239 @property void pen(Pen p) { 11240 _activePen = p; 11241 _outlineColor = p.color; 11242 11243 HPEN pen; 11244 if(p.color.a == 0) { 11245 pen = GetStockObject(NULL_PEN); 11246 } else { 11247 int style = PS_SOLID; 11248 final switch(p.style) { 11249 case Pen.Style.Solid: 11250 style = PS_SOLID; 11251 break; 11252 case Pen.Style.Dashed: 11253 style = PS_DASH; 11254 break; 11255 case Pen.Style.Dotted: 11256 style = PS_DOT; 11257 break; 11258 } 11259 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11260 } 11261 auto orig = SelectObject(hdc, pen); 11262 if(originalPen is null) 11263 originalPen = orig; 11264 11265 if(currentPen !is null) 11266 DeleteObject(currentPen); 11267 11268 currentPen = pen; 11269 11270 // the outline is like a foreground since it's done that way on X 11271 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11272 11273 } 11274 11275 @property void rasterOp(RasterOp op) { 11276 int mode; 11277 final switch(op) { 11278 case RasterOp.normal: 11279 mode = R2_COPYPEN; 11280 break; 11281 case RasterOp.xor: 11282 mode = R2_XORPEN; 11283 break; 11284 } 11285 SetROP2(hdc, mode); 11286 } 11287 11288 HBRUSH originalBrush; 11289 HBRUSH currentBrush; 11290 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11291 @property void fillColor(Color c) { 11292 if(c == _fillColor) 11293 return; 11294 _fillColor = c; 11295 HBRUSH brush; 11296 if(c.a == 0) { 11297 brush = GetStockObject(HOLLOW_BRUSH); 11298 } else { 11299 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11300 } 11301 auto orig = SelectObject(hdc, brush); 11302 if(originalBrush is null) 11303 originalBrush = orig; 11304 11305 if(currentBrush !is null) 11306 DeleteObject(currentBrush); 11307 11308 currentBrush = brush; 11309 11310 // background color is NOT set because X doesn't draw text backgrounds 11311 // SetBkColor(hdc, RGB(255, 255, 255)); 11312 } 11313 11314 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11315 BITMAP bm; 11316 11317 HDC hdcMem = CreateCompatibleDC(hdc); 11318 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11319 11320 GetObject(i.handle, bm.sizeof, &bm); 11321 11322 // or should I AlphaBlend!??!?! 11323 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11324 11325 SelectObject(hdcMem, hbmOld); 11326 DeleteDC(hdcMem); 11327 } 11328 11329 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11330 BITMAP bm; 11331 11332 HDC hdcMem = CreateCompatibleDC(hdc); 11333 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 11334 11335 GetObject(s.handle, bm.sizeof, &bm); 11336 11337 version(CRuntime_DigitalMars) goto noalpha; 11338 11339 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 11340 if(s.enableAlpha) { 11341 auto dw = w ? w : bm.bmWidth; 11342 auto dh = h ? h : bm.bmHeight; 11343 BLENDFUNCTION bf; 11344 bf.BlendOp = AC_SRC_OVER; 11345 bf.SourceConstantAlpha = 255; 11346 bf.AlphaFormat = AC_SRC_ALPHA; 11347 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 11348 } else { 11349 noalpha: 11350 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 11351 } 11352 11353 SelectObject(hdcMem, hbmOld); 11354 DeleteDC(hdcMem); 11355 } 11356 11357 Size textSize(scope const(char)[] text) { 11358 bool dummyX; 11359 if(text.length == 0) { 11360 text = " "; 11361 dummyX = true; 11362 } 11363 RECT rect; 11364 WCharzBuffer buffer = WCharzBuffer(text); 11365 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 11366 return Size(dummyX ? 0 : rect.right, rect.bottom); 11367 } 11368 11369 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 11370 if(text.length && text[$-1] == '\n') 11371 text = text[0 .. $-1]; // tailing newlines are weird on windows... 11372 if(text.length && text[$-1] == '\r') 11373 text = text[0 .. $-1]; 11374 11375 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 11376 if(x2 == 0 && y2 == 0) { 11377 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 11378 } else { 11379 RECT rect; 11380 rect.left = x; 11381 rect.top = y; 11382 rect.right = x2; 11383 rect.bottom = y2; 11384 11385 uint mode = DT_LEFT; 11386 if(alignment & TextAlignment.Right) 11387 mode = DT_RIGHT; 11388 else if(alignment & TextAlignment.Center) 11389 mode = DT_CENTER; 11390 11391 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 11392 if(alignment & TextAlignment.VerticalCenter) 11393 mode |= DT_VCENTER | DT_SINGLELINE; 11394 11395 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 11396 } 11397 11398 /* 11399 uint mode; 11400 11401 if(alignment & TextAlignment.Center) 11402 mode = TA_CENTER; 11403 11404 SetTextAlign(hdc, mode); 11405 */ 11406 } 11407 11408 int fontHeight() { 11409 TEXTMETRIC metric; 11410 if(GetTextMetricsW(hdc, &metric)) { 11411 return metric.tmHeight; 11412 } 11413 11414 return 16; // idk just guessing here, maybe we should throw 11415 } 11416 11417 void drawPixel(int x, int y) { 11418 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 11419 } 11420 11421 // The basic shapes, outlined 11422 11423 void drawLine(int x1, int y1, int x2, int y2) { 11424 MoveToEx(hdc, x1, y1, null); 11425 LineTo(hdc, x2, y2); 11426 } 11427 11428 void drawRectangle(int x, int y, int width, int height) { 11429 // FIXME: with a wider pen this might not draw quite right. im not sure. 11430 gdi.Rectangle(hdc, x, y, x + width, y + height); 11431 } 11432 11433 /// Arguments are the points of the bounding rectangle 11434 void drawEllipse(int x1, int y1, int x2, int y2) { 11435 Ellipse(hdc, x1, y1, x2, y2); 11436 } 11437 11438 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11439 if((start % (360*64)) == (finish % (360*64))) 11440 drawEllipse(x1, y1, x1 + width, y1 + height); 11441 else { 11442 import core.stdc.math; 11443 float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323; 11444 float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323; 11445 11446 auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2); 11447 auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2); 11448 auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2); 11449 auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2); 11450 11451 if(_activePen.color.a) 11452 Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11453 if(_fillColor.a) 11454 Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11455 } 11456 } 11457 11458 void drawPolygon(Point[] vertexes) { 11459 POINT[] points; 11460 points.length = vertexes.length; 11461 11462 foreach(i, p; vertexes) { 11463 points[i].x = p.x; 11464 points[i].y = p.y; 11465 } 11466 11467 Polygon(hdc, points.ptr, cast(int) points.length); 11468 } 11469 } 11470 11471 11472 // Mix this into the SimpleWindow class 11473 mixin template NativeSimpleWindowImplementation() { 11474 int curHidden = 0; // counter 11475 __gshared static bool[string] knownWinClasses; 11476 static bool altPressed = false; 11477 11478 HANDLE oldCursor; 11479 11480 void hideCursor () { 11481 if(curHidden == 0) 11482 oldCursor = SetCursor(null); 11483 ++curHidden; 11484 } 11485 11486 void showCursor () { 11487 --curHidden; 11488 if(curHidden == 0) { 11489 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 11490 } 11491 } 11492 11493 11494 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 11495 11496 void setMinSize (int minwidth, int minheight) { 11497 minWidth = minwidth; 11498 minHeight = minheight; 11499 } 11500 void setMaxSize (int maxwidth, int maxheight) { 11501 maxWidth = maxwidth; 11502 maxHeight = maxheight; 11503 } 11504 11505 // FIXME i'm not sure that Windows has this functionality 11506 // though it is nonessential anyway. 11507 void setResizeGranularity (int granx, int grany) {} 11508 11509 ScreenPainter getPainter(bool manualInvalidations) { 11510 return ScreenPainter(this, hwnd, manualInvalidations); 11511 } 11512 11513 HBITMAP buffer; 11514 11515 void setTitle(string title) { 11516 WCharzBuffer bfr = WCharzBuffer(title); 11517 SetWindowTextW(hwnd, bfr.ptr); 11518 } 11519 11520 string getTitle() { 11521 auto len = GetWindowTextLengthW(hwnd); 11522 if (!len) 11523 return null; 11524 wchar[256] tmpBuffer; 11525 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 11526 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 11527 auto str = buffer[0 .. len2]; 11528 return makeUtf8StringFromWindowsString(str); 11529 } 11530 11531 void move(int x, int y) { 11532 RECT rect; 11533 GetWindowRect(hwnd, &rect); 11534 // move it while maintaining the same size... 11535 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 11536 } 11537 11538 void resize(int w, int h) { 11539 RECT rect; 11540 GetWindowRect(hwnd, &rect); 11541 11542 RECT client; 11543 GetClientRect(hwnd, &client); 11544 11545 rect.right = rect.right - client.right + w; 11546 rect.bottom = rect.bottom - client.bottom + h; 11547 11548 // same position, new size for the client rectangle 11549 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 11550 11551 updateOpenglViewportIfNeeded(w, h); 11552 } 11553 11554 void moveResize (int x, int y, int w, int h) { 11555 // what's given is the client rectangle, we need to adjust 11556 11557 RECT rect; 11558 rect.left = x; 11559 rect.top = y; 11560 rect.right = w + x; 11561 rect.bottom = h + y; 11562 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 11563 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11564 11565 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 11566 updateOpenglViewportIfNeeded(w, h); 11567 if (windowResized !is null) windowResized(w, h); 11568 } 11569 11570 version(without_opengl) {} else { 11571 HGLRC ghRC; 11572 HDC ghDC; 11573 } 11574 11575 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 11576 string cnamec; 11577 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 11578 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 11579 cnamec = "DSimpleWindow"; 11580 } else { 11581 cnamec = sdpyWindowClass; 11582 } 11583 11584 WCharzBuffer cn = WCharzBuffer(cnamec); 11585 11586 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 11587 11588 if(cnamec !in knownWinClasses) { 11589 WNDCLASSEX wc; 11590 11591 // FIXME: I might be able to use cbWndExtra to hold the pointer back 11592 // to the object. Maybe. 11593 wc.cbSize = wc.sizeof; 11594 wc.cbClsExtra = 0; 11595 wc.cbWndExtra = 0; 11596 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 11597 wc.hCursor = LoadCursorW(null, IDC_ARROW); 11598 wc.hIcon = LoadIcon(hInstance, null); 11599 wc.hInstance = hInstance; 11600 wc.lpfnWndProc = &WndProc; 11601 wc.lpszClassName = cn.ptr; 11602 wc.hIconSm = null; 11603 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 11604 if(!RegisterClassExW(&wc)) 11605 throw new WindowsApiException("RegisterClassExW", GetLastError()); 11606 knownWinClasses[cnamec] = true; 11607 } 11608 11609 int style; 11610 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 11611 11612 // FIXME: windowType and customizationFlags 11613 final switch(windowType) { 11614 case WindowTypes.normal: 11615 if(resizability == Resizability.fixedSize) { 11616 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 11617 } else { 11618 style = WS_OVERLAPPEDWINDOW; 11619 } 11620 break; 11621 case WindowTypes.undecorated: 11622 style = WS_POPUP | WS_SYSMENU; 11623 break; 11624 case WindowTypes.eventOnly: 11625 _hidden = true; 11626 break; 11627 case WindowTypes.dropdownMenu: 11628 case WindowTypes.popupMenu: 11629 case WindowTypes.notification: 11630 style = WS_POPUP; 11631 flags |= WS_EX_NOACTIVATE; 11632 break; 11633 case WindowTypes.nestedChild: 11634 style = WS_CHILD; 11635 break; 11636 case WindowTypes.minimallyWrapped: 11637 assert(0, "construct minimally wrapped through the other ctor overlad"); 11638 } 11639 11640 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11641 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 11642 11643 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 11644 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 11645 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 11646 11647 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11648 setOpacity(255); 11649 11650 SimpleWindow.nativeMapping[hwnd] = this; 11651 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 11652 11653 if(windowType == WindowTypes.eventOnly) 11654 return; 11655 11656 HDC hdc = GetDC(hwnd); 11657 11658 11659 version(without_opengl) {} 11660 else { 11661 if(opengl == OpenGlOptions.yes) { 11662 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 11663 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 11664 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 11665 ghDC = hdc; 11666 PIXELFORMATDESCRIPTOR pfd; 11667 11668 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 11669 pfd.nVersion = 1; 11670 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 11671 pfd.dwLayerMask = PFD_MAIN_PLANE; 11672 pfd.iPixelType = PFD_TYPE_RGBA; 11673 pfd.cColorBits = 24; 11674 pfd.cDepthBits = 24; 11675 pfd.cAccumBits = 0; 11676 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 11677 11678 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 11679 11680 if (pixelformat == 0) 11681 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 11682 11683 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 11684 throw new WindowsApiException("SetPixelFormat", GetLastError()); 11685 11686 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 11687 // windoze is idiotic: we have to have OpenGL context to get function addresses 11688 // so we will create fake context to get that stupid address 11689 auto tmpcc = wglCreateContext(ghDC); 11690 if (tmpcc !is null) { 11691 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 11692 wglMakeCurrent(ghDC, tmpcc); 11693 wglInitOtherFunctions(); 11694 } 11695 } 11696 11697 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 11698 int[9] contextAttribs = [ 11699 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 11700 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 11701 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 11702 // for modern context, set "forward compatibility" flag too 11703 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 11704 0/*None*/, 11705 ]; 11706 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 11707 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 11708 // activate fallback mode 11709 // 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; 11710 ghRC = wglCreateContext(ghDC); 11711 } 11712 if (ghRC is null) 11713 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 11714 } else { 11715 // try to do at least something 11716 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 11717 sdpyOpenGLContextVersion = 0; 11718 ghRC = wglCreateContext(ghDC); 11719 } 11720 if (ghRC is null) 11721 throw new WindowsApiException("wglCreateContext", GetLastError()); 11722 } 11723 } 11724 } 11725 11726 if(opengl == OpenGlOptions.no) { 11727 buffer = CreateCompatibleBitmap(hdc, width, height); 11728 11729 auto hdcBmp = CreateCompatibleDC(hdc); 11730 // make sure it's filled with a blank slate 11731 auto oldBmp = SelectObject(hdcBmp, buffer); 11732 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 11733 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 11734 gdi.Rectangle(hdcBmp, 0, 0, width, height); 11735 SelectObject(hdcBmp, oldBmp); 11736 SelectObject(hdcBmp, oldBrush); 11737 SelectObject(hdcBmp, oldPen); 11738 DeleteDC(hdcBmp); 11739 11740 bmpWidth = width; 11741 bmpHeight = height; 11742 11743 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 11744 } 11745 11746 // We want the window's client area to match the image size 11747 RECT rcClient, rcWindow; 11748 POINT ptDiff; 11749 GetClientRect(hwnd, &rcClient); 11750 GetWindowRect(hwnd, &rcWindow); 11751 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 11752 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 11753 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 11754 11755 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 11756 ShowWindow(hwnd, SW_SHOWNORMAL); 11757 } else { 11758 _hidden = true; 11759 } 11760 this._visibleForTheFirstTimeCalled = false; // hack! 11761 } 11762 11763 11764 void dispose() { 11765 if(buffer) 11766 DeleteObject(buffer); 11767 } 11768 11769 void closeWindow() { 11770 if(ghRC) { 11771 wglDeleteContext(ghRC); 11772 ghRC = null; 11773 } 11774 DestroyWindow(hwnd); 11775 } 11776 11777 bool setOpacity(ubyte alpha) { 11778 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 11779 } 11780 11781 HANDLE currentCursor; 11782 11783 // returns zero if it recognized the event 11784 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 11785 MouseEvent mouse; 11786 11787 void mouseEvent(bool isScreen, ulong mods) { 11788 auto x = LOWORD(lParam); 11789 auto y = HIWORD(lParam); 11790 if(isScreen) { 11791 POINT p; 11792 p.x = x; 11793 p.y = y; 11794 ScreenToClient(hwnd, &p); 11795 x = cast(ushort) p.x; 11796 y = cast(ushort) p.y; 11797 } 11798 11799 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 11800 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 11801 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 11802 } 11803 11804 mouse.x = x + offsetX; 11805 mouse.y = y + offsetY; 11806 11807 wind.mdx(mouse); 11808 mouse.modifierState = cast(int) mods; 11809 mouse.window = wind; 11810 11811 if(wind.handleMouseEvent) 11812 wind.handleMouseEvent(mouse); 11813 } 11814 11815 switch(msg) { 11816 case WM_GETMINMAXINFO: 11817 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 11818 11819 if(wind.minWidth > 0) { 11820 RECT rect; 11821 rect.left = 100; 11822 rect.top = 100; 11823 rect.right = wind.minWidth + 100; 11824 rect.bottom = wind.minHeight + 100; 11825 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11826 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11827 11828 mmi.ptMinTrackSize.x = rect.right - rect.left; 11829 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 11830 } 11831 11832 if(wind.maxWidth < int.max) { 11833 RECT rect; 11834 rect.left = 100; 11835 rect.top = 100; 11836 rect.right = wind.maxWidth + 100; 11837 rect.bottom = wind.maxHeight + 100; 11838 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11839 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11840 11841 mmi.ptMaxTrackSize.x = rect.right - rect.left; 11842 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 11843 } 11844 break; 11845 case WM_CHAR: 11846 wchar c = cast(wchar) wParam; 11847 if(wind.handleCharEvent) 11848 wind.handleCharEvent(cast(dchar) c); 11849 break; 11850 case WM_SETFOCUS: 11851 case WM_KILLFOCUS: 11852 wind._focused = (msg == WM_SETFOCUS); 11853 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 11854 if(wind.onFocusChange) 11855 wind.onFocusChange(msg == WM_SETFOCUS); 11856 break; 11857 11858 case WM_SYSKEYDOWN: 11859 goto case; 11860 case WM_SYSKEYUP: 11861 if(lParam & (1 << 29)) { 11862 goto case; 11863 } else { 11864 // no window has keyboard focus 11865 goto default; 11866 } 11867 case WM_KEYDOWN: 11868 case WM_KEYUP: 11869 KeyEvent ev; 11870 ev.key = cast(Key) wParam; 11871 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 11872 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 11873 11874 ev.hardwareCode = (lParam & 0xff0000) >> 16; 11875 11876 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 11877 ev.modifierState |= ModifierState.shift; 11878 //k8: this doesn't work; thanks for nothing, windows 11879 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 11880 ev.modifierState |= ModifierState.alt;*/ 11881 // this never seems to actually be set 11882 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11883 11884 if (wParam == 0x12) { 11885 altPressed = (msg == WM_SYSKEYDOWN); 11886 } 11887 11888 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 11889 altPressed = false; 11890 } 11891 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 11892 11893 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11894 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 11895 ev.modifierState |= ModifierState.ctrl; 11896 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 11897 ev.modifierState |= ModifierState.windows; 11898 if(GetKeyState(Key.NumLock)) 11899 ev.modifierState |= ModifierState.numLock; 11900 if(GetKeyState(Key.CapsLock)) 11901 ev.modifierState |= ModifierState.capsLock; 11902 11903 /+ 11904 // we always want to send the character too, so let's convert it 11905 ubyte[256] state; 11906 wchar[16] buffer; 11907 GetKeyboardState(state.ptr); 11908 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 11909 11910 foreach(dchar d; buffer) { 11911 ev.character = d; 11912 break; 11913 } 11914 +/ 11915 11916 ev.window = wind; 11917 if(wind.handleKeyEvent) 11918 wind.handleKeyEvent(ev); 11919 break; 11920 case 0x020a /*WM_MOUSEWHEEL*/: 11921 // send click 11922 mouse.type = cast(MouseEventType) 1; 11923 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 11924 mouseEvent(true, LOWORD(wParam)); 11925 11926 // also send release 11927 mouse.type = cast(MouseEventType) 2; 11928 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 11929 mouseEvent(true, LOWORD(wParam)); 11930 break; 11931 case WM_MOUSEMOVE: 11932 mouse.type = cast(MouseEventType) 0; 11933 mouseEvent(false, wParam); 11934 break; 11935 case WM_LBUTTONDOWN: 11936 case WM_LBUTTONDBLCLK: 11937 mouse.type = cast(MouseEventType) 1; 11938 mouse.button = MouseButton.left; 11939 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 11940 mouseEvent(false, wParam); 11941 break; 11942 case WM_LBUTTONUP: 11943 mouse.type = cast(MouseEventType) 2; 11944 mouse.button = MouseButton.left; 11945 mouseEvent(false, wParam); 11946 break; 11947 case WM_RBUTTONDOWN: 11948 case WM_RBUTTONDBLCLK: 11949 mouse.type = cast(MouseEventType) 1; 11950 mouse.button = MouseButton.right; 11951 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 11952 mouseEvent(false, wParam); 11953 break; 11954 case WM_RBUTTONUP: 11955 mouse.type = cast(MouseEventType) 2; 11956 mouse.button = MouseButton.right; 11957 mouseEvent(false, wParam); 11958 break; 11959 case WM_MBUTTONDOWN: 11960 case WM_MBUTTONDBLCLK: 11961 mouse.type = cast(MouseEventType) 1; 11962 mouse.button = MouseButton.middle; 11963 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 11964 mouseEvent(false, wParam); 11965 break; 11966 case WM_MBUTTONUP: 11967 mouse.type = cast(MouseEventType) 2; 11968 mouse.button = MouseButton.middle; 11969 mouseEvent(false, wParam); 11970 break; 11971 case WM_XBUTTONDOWN: 11972 case WM_XBUTTONDBLCLK: 11973 mouse.type = cast(MouseEventType) 1; 11974 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 11975 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 11976 mouseEvent(false, wParam); 11977 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 11978 case WM_XBUTTONUP: 11979 mouse.type = cast(MouseEventType) 2; 11980 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 11981 mouseEvent(false, wParam); 11982 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 11983 11984 default: return 1; 11985 } 11986 return 0; 11987 } 11988 11989 HWND hwnd; 11990 private int oldWidth; 11991 private int oldHeight; 11992 private bool inSizeMove; 11993 11994 /++ 11995 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. 11996 11997 History: 11998 Added November 23, 2021 11999 12000 Not fully stable, may be moved out of the impl struct. 12001 12002 Default value changed to `true` on February 15, 2021 12003 +/ 12004 bool doLiveResizing = true; 12005 12006 package int bmpWidth; 12007 package int bmpHeight; 12008 12009 // the extern(Windows) wndproc should just forward to this 12010 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 12011 try { 12012 assert(hwnd is this.hwnd); 12013 12014 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 12015 switch(msg) { 12016 case WM_MENUCHAR: // menu active but key not associated with a thing. 12017 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 12018 // The main things we can do are select, execute, close, or ignore 12019 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 12020 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 12021 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 12022 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 12023 12024 // returns the value in the *high order word* of the return value 12025 // hence the << 16 12026 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 12027 case WM_SETCURSOR: 12028 if(cast(HWND) wParam !is hwnd) 12029 return 0; // further processing elsewhere 12030 12031 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 12032 SetCursor(this.curHidden > 0 ? null : currentCursor); 12033 return 1; 12034 } else { 12035 return DefWindowProc(hwnd, msg, wParam, lParam); 12036 } 12037 //break; 12038 12039 case WM_CLOSE: 12040 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12041 break; 12042 case WM_DESTROY: 12043 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12044 SimpleWindow.nativeMapping.remove(hwnd); 12045 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12046 12047 bool anyImportant = false; 12048 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12049 if(w.beingOpenKeepsAppOpen) { 12050 anyImportant = true; 12051 break; 12052 } 12053 if(!anyImportant) { 12054 PostQuitMessage(0); 12055 } 12056 break; 12057 case 0x02E0 /*WM_DPICHANGED*/: 12058 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12059 12060 RECT* prcNewWindow = cast(RECT*)lParam; 12061 // docs say this is the recommended position and we should honor it 12062 SetWindowPos(hwnd, 12063 null, 12064 prcNewWindow.left, 12065 prcNewWindow.top, 12066 prcNewWindow.right - prcNewWindow.left, 12067 prcNewWindow.bottom - prcNewWindow.top, 12068 SWP_NOZORDER | SWP_NOACTIVATE); 12069 12070 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12071 // im not sure it is completely correct 12072 // but without it the tabs and such do look weird as things change. 12073 if(SystemParametersInfoForDpi) { 12074 LOGFONT lfText; 12075 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12076 HFONT hFontNew = CreateFontIndirect(&lfText); 12077 if (hFontNew) 12078 { 12079 //DeleteObject(hFontOld); 12080 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12081 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12082 return TRUE; 12083 } 12084 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12085 } 12086 } 12087 12088 if(this.onDpiChanged) 12089 this.onDpiChanged(); 12090 break; 12091 case WM_ENTERIDLE: 12092 // when a menu is up, it stops normal event processing (modal message loop) 12093 // but this at least gives us a chance to SOMETIMES catch up 12094 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12095 SimpleWindow.processAllCustomEvents; 12096 SimpleWindow.processAllCustomEvents; 12097 SleepEx(0, true); 12098 break; 12099 case WM_SIZE: 12100 if(wParam == 1 /* SIZE_MINIMIZED */) 12101 break; 12102 _width = LOWORD(lParam); 12103 _height = HIWORD(lParam); 12104 12105 // I want to avoid tearing in the windows (my code is inefficient 12106 // so this is a hack around that) so while sizing, we don't trigger, 12107 // but we do want to trigger on events like mazimize. 12108 if(!inSizeMove || doLiveResizing) 12109 goto size_changed; 12110 break; 12111 /+ 12112 case WM_SIZING: 12113 writeln("size"); 12114 break; 12115 +/ 12116 // I don't like the tearing I get when redrawing on WM_SIZE 12117 // (I know there's other ways to fix that but I don't like that behavior anyway) 12118 // so instead it is going to redraw only at the end of a size. 12119 case 0x0231: /* WM_ENTERSIZEMOVE */ 12120 inSizeMove = true; 12121 break; 12122 case 0x0232: /* WM_EXITSIZEMOVE */ 12123 inSizeMove = false; 12124 12125 size_changed: 12126 12127 // nothing relevant changed, don't bother redrawing 12128 if(oldWidth == _width && oldHeight == _height) { 12129 break; 12130 } 12131 12132 // note: OpenGL windows don't use a backing bmp, so no need to change them 12133 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12134 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12135 // gotta get the double buffer bmp to match the window 12136 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12137 12138 // if it is auto-scaled, we keep the backing bitmap the same size all the time 12139 if(resizability != Resizability.automaticallyScaleIfPossible) 12140 if(_width > bmpWidth || _height > bmpHeight) { 12141 auto hdc = GetDC(hwnd); 12142 auto oldBuffer = buffer; 12143 buffer = CreateCompatibleBitmap(hdc, _width, _height); 12144 12145 auto hdcBmp = CreateCompatibleDC(hdc); 12146 auto oldBmp = SelectObject(hdcBmp, buffer); 12147 12148 auto hdcOldBmp = CreateCompatibleDC(hdc); 12149 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12150 12151 /+ 12152 RECT r; 12153 r.left = 0; 12154 r.top = 0; 12155 r.right = width; 12156 r.bottom = height; 12157 auto c = Color.green; 12158 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12159 FillRect(hdcBmp, &r, brush); 12160 DeleteObject(brush); 12161 +/ 12162 12163 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12164 12165 bmpWidth = _width; 12166 bmpHeight = _height; 12167 12168 SelectObject(hdcOldBmp, oldOldBmp); 12169 DeleteDC(hdcOldBmp); 12170 12171 SelectObject(hdcBmp, oldBmp); 12172 DeleteDC(hdcBmp); 12173 12174 ReleaseDC(hwnd, hdc); 12175 12176 DeleteObject(oldBuffer); 12177 } 12178 } 12179 12180 updateOpenglViewportIfNeeded(_width, _height); 12181 12182 if(resizability != Resizability.automaticallyScaleIfPossible) 12183 if(windowResized !is null) 12184 windowResized(_width, _height); 12185 12186 if(inSizeMove) { 12187 SimpleWindow.processAllCustomEvents(); 12188 SimpleWindow.processAllCustomEvents(); 12189 } else { 12190 // when it is all done, make sure everything is freshly drawn or there might be 12191 // weird bugs left. 12192 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12193 } 12194 12195 oldWidth = this._width; 12196 oldHeight = this._height; 12197 break; 12198 case WM_ERASEBKGND: 12199 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12200 if (!this._visibleForTheFirstTimeCalled) { 12201 this._visibleForTheFirstTimeCalled = true; 12202 if (this.visibleForTheFirstTime !is null) { 12203 this.visibleForTheFirstTime(); 12204 } 12205 } 12206 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12207 version(without_opengl) {} else { 12208 if (openglMode == OpenGlOptions.yes) return 1; 12209 } 12210 // call windows default handler, so it can paint standard controls 12211 goto default; 12212 case WM_CTLCOLORBTN: 12213 case WM_CTLCOLORSTATIC: 12214 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12215 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12216 GetSysColorBrush(COLOR_3DFACE); 12217 //break; 12218 case WM_SHOWWINDOW: 12219 this._visible = (wParam != 0); 12220 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12221 this._visibleForTheFirstTimeCalled = true; 12222 if (this.visibleForTheFirstTime !is null) { 12223 this.visibleForTheFirstTime(); 12224 } 12225 } 12226 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12227 break; 12228 case WM_PAINT: { 12229 if (!this._visibleForTheFirstTimeCalled) { 12230 this._visibleForTheFirstTimeCalled = true; 12231 if (this.visibleForTheFirstTime !is null) { 12232 this.visibleForTheFirstTime(); 12233 } 12234 } 12235 12236 BITMAP bm; 12237 PAINTSTRUCT ps; 12238 12239 HDC hdc = BeginPaint(hwnd, &ps); 12240 12241 if(openglMode == OpenGlOptions.no) { 12242 12243 HDC hdcMem = CreateCompatibleDC(hdc); 12244 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12245 12246 GetObject(buffer, bm.sizeof, &bm); 12247 12248 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12249 if(resizability == Resizability.automaticallyScaleIfPossible) 12250 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12251 else 12252 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12253 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12254 12255 SelectObject(hdcMem, hbmOld); 12256 DeleteDC(hdcMem); 12257 EndPaint(hwnd, &ps); 12258 } else { 12259 EndPaint(hwnd, &ps); 12260 version(without_opengl) {} else 12261 redrawOpenGlSceneSoon(); 12262 } 12263 } break; 12264 default: 12265 return DefWindowProc(hwnd, msg, wParam, lParam); 12266 } 12267 return 0; 12268 12269 } 12270 catch(Throwable t) { 12271 sdpyPrintDebugString(t.toString); 12272 return 0; 12273 } 12274 } 12275 } 12276 12277 mixin template NativeImageImplementation() { 12278 HBITMAP handle; 12279 ubyte* rawData; 12280 12281 final: 12282 12283 Color getPixel(int x, int y) { 12284 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12285 // remember, bmps are upside down 12286 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12287 12288 Color c; 12289 if(enableAlpha) 12290 c.a = rawData[offset + 3]; 12291 else 12292 c.a = 255; 12293 c.b = rawData[offset + 0]; 12294 c.g = rawData[offset + 1]; 12295 c.r = rawData[offset + 2]; 12296 c.unPremultiply(); 12297 return c; 12298 } 12299 12300 void setPixel(int x, int y, Color c) { 12301 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12302 // remember, bmps are upside down 12303 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12304 12305 if(enableAlpha) 12306 c.premultiply(); 12307 12308 rawData[offset + 0] = c.b; 12309 rawData[offset + 1] = c.g; 12310 rawData[offset + 2] = c.r; 12311 if(enableAlpha) 12312 rawData[offset + 3] = c.a; 12313 } 12314 12315 void convertToRgbaBytes(ubyte[] where) { 12316 assert(where.length == this.width * this.height * 4); 12317 12318 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12319 int idx = 0; 12320 int offset = itemsPerLine * (height - 1); 12321 // remember, bmps are upside down 12322 for(int y = height - 1; y >= 0; y--) { 12323 auto offsetStart = offset; 12324 for(int x = 0; x < width; x++) { 12325 where[idx + 0] = rawData[offset + 2]; // r 12326 where[idx + 1] = rawData[offset + 1]; // g 12327 where[idx + 2] = rawData[offset + 0]; // b 12328 if(enableAlpha) { 12329 where[idx + 3] = rawData[offset + 3]; // a 12330 unPremultiplyRgba(where[idx .. idx + 4]); 12331 offset++; 12332 } else 12333 where[idx + 3] = 255; // a 12334 idx += 4; 12335 offset += 3; 12336 } 12337 12338 offset = offsetStart - itemsPerLine; 12339 } 12340 } 12341 12342 void setFromRgbaBytes(in ubyte[] what) { 12343 assert(what.length == this.width * this.height * 4); 12344 12345 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 12346 int idx = 0; 12347 int offset = itemsPerLine * (height - 1); 12348 // remember, bmps are upside down 12349 for(int y = height - 1; y >= 0; y--) { 12350 auto offsetStart = offset; 12351 for(int x = 0; x < width; x++) { 12352 if(enableAlpha) { 12353 auto a = what[idx + 3]; 12354 12355 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 12356 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 12357 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 12358 rawData[offset + 3] = a; // a 12359 //premultiplyBgra(rawData[offset .. offset + 4]); 12360 offset++; 12361 } else { 12362 rawData[offset + 2] = what[idx + 0]; // r 12363 rawData[offset + 1] = what[idx + 1]; // g 12364 rawData[offset + 0] = what[idx + 2]; // b 12365 } 12366 idx += 4; 12367 offset += 3; 12368 } 12369 12370 offset = offsetStart - itemsPerLine; 12371 } 12372 } 12373 12374 12375 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 12376 BITMAPINFO infoheader; 12377 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 12378 infoheader.bmiHeader.biWidth = width; 12379 infoheader.bmiHeader.biHeight = height; 12380 infoheader.bmiHeader.biPlanes = 1; 12381 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 12382 infoheader.bmiHeader.biCompression = BI_RGB; 12383 12384 handle = CreateDIBSection( 12385 null, 12386 &infoheader, 12387 DIB_RGB_COLORS, 12388 cast(void**) &rawData, 12389 null, 12390 0); 12391 if(handle is null) 12392 throw new WindowsApiException("create image failed", GetLastError()); 12393 12394 } 12395 12396 void dispose() { 12397 DeleteObject(handle); 12398 } 12399 } 12400 12401 enum KEY_ESCAPE = 27; 12402 } 12403 version(X11) { 12404 /// This is the default font used. You might change this before doing anything else with 12405 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 12406 /// for cross-platform compatibility. 12407 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12408 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12409 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 12410 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 12411 12412 alias int delegate(XEvent) NativeEventHandler; 12413 alias Window NativeWindowHandle; 12414 12415 enum KEY_ESCAPE = 9; 12416 12417 mixin template NativeScreenPainterImplementation() { 12418 Display* display; 12419 Drawable d; 12420 Drawable destiny; 12421 12422 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 12423 GC gc; 12424 12425 __gshared bool fontAttempted; 12426 12427 __gshared XFontStruct* defaultfont; 12428 __gshared XFontSet defaultfontset; 12429 12430 XFontStruct* font; 12431 XFontSet fontset; 12432 12433 void create(NativeWindowHandle window) { 12434 this.display = XDisplayConnection.get(); 12435 12436 Drawable buffer = None; 12437 if(auto sw = cast(SimpleWindow) this.window) { 12438 buffer = sw.impl.buffer; 12439 this.destiny = cast(Drawable) window; 12440 } else { 12441 buffer = cast(Drawable) window; 12442 this.destiny = None; 12443 } 12444 12445 this.d = cast(Drawable) buffer; 12446 12447 auto dgc = DefaultGC(display, DefaultScreen(display)); 12448 12449 this.gc = XCreateGC(display, d, 0, null); 12450 12451 XCopyGC(display, dgc, 0xffffffff, this.gc); 12452 12453 ensureDefaultFontLoaded(); 12454 12455 font = defaultfont; 12456 fontset = defaultfontset; 12457 12458 if(font) { 12459 XSetFont(display, gc, font.fid); 12460 } 12461 } 12462 12463 static void ensureDefaultFontLoaded() { 12464 if(!fontAttempted) { 12465 auto display = XDisplayConnection.get; 12466 auto font = XLoadQueryFont(display, xfontstr.ptr); 12467 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 12468 if(font is null) { 12469 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 12470 font = XLoadQueryFont(display, xfontstr.ptr); 12471 } 12472 12473 char** lol; 12474 int lol2; 12475 char* lol3; 12476 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 12477 12478 fontAttempted = true; 12479 12480 defaultfont = font; 12481 defaultfontset = fontset; 12482 } 12483 } 12484 12485 arsd.color.Rectangle _clipRectangle; 12486 void setClipRectangle(int x, int y, int width, int height) { 12487 auto old = _clipRectangle; 12488 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12489 if(old == _clipRectangle) 12490 return; 12491 12492 if(width == 0 || height == 0) { 12493 XSetClipMask(display, gc, None); 12494 12495 if(xrenderPicturePainter) { 12496 12497 XRectangle[1] rects; 12498 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 12499 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12500 } 12501 12502 version(with_xft) { 12503 if(xftFont is null || xftDraw is null) 12504 return; 12505 XftDrawSetClip(xftDraw, null); 12506 } 12507 } else { 12508 XRectangle[1] rects; 12509 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 12510 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 12511 12512 if(xrenderPicturePainter) 12513 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12514 12515 version(with_xft) { 12516 if(xftFont is null || xftDraw is null) 12517 return; 12518 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 12519 } 12520 } 12521 } 12522 12523 version(with_xft) { 12524 XftFont* xftFont; 12525 XftDraw* xftDraw; 12526 12527 XftColor xftColor; 12528 12529 void updateXftColor() { 12530 if(xftFont is null) 12531 return; 12532 12533 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 12534 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 12535 12536 XftColorAllocValue( 12537 display, 12538 DefaultVisual(display, DefaultScreen(display)), 12539 DefaultColormap(display, 0), 12540 &colorIn, 12541 &xftColor 12542 ); 12543 } 12544 } 12545 12546 private OperatingSystemFont _activeFont; 12547 void setFont(OperatingSystemFont font) { 12548 _activeFont = font; 12549 version(with_xft) { 12550 if(font && font.isXft && font.xftFont) 12551 this.xftFont = font.xftFont; 12552 else 12553 this.xftFont = null; 12554 12555 if(this.xftFont) { 12556 if(xftDraw is null) { 12557 xftDraw = XftDrawCreate( 12558 display, 12559 d, 12560 DefaultVisual(display, DefaultScreen(display)), 12561 DefaultColormap(display, 0) 12562 ); 12563 12564 updateXftColor(); 12565 } 12566 12567 return; 12568 } 12569 } 12570 12571 if(font && font.font) { 12572 this.font = font.font; 12573 this.fontset = font.fontset; 12574 XSetFont(display, gc, font.font.fid); 12575 } else { 12576 this.font = defaultfont; 12577 this.fontset = defaultfontset; 12578 } 12579 12580 } 12581 12582 private Picture xrenderPicturePainter; 12583 12584 bool manualInvalidations; 12585 void invalidateRect(Rectangle invalidRect) { 12586 // FIXME if manualInvalidations 12587 } 12588 12589 void dispose() { 12590 this.rasterOp = RasterOp.normal; 12591 12592 if(xrenderPicturePainter) { 12593 XRenderFreePicture(display, xrenderPicturePainter); 12594 xrenderPicturePainter = None; 12595 } 12596 12597 // FIXME: this.window.width/height is probably wrong 12598 12599 // src x,y then dest x, y 12600 if(destiny != None) { 12601 // FIXME: if manual invalidations we can actually only copy some of the area. 12602 // if(manualInvalidations) 12603 XSetClipMask(display, gc, None); 12604 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 12605 } 12606 12607 XFreeGC(display, gc); 12608 12609 version(with_xft) 12610 if(xftDraw) { 12611 XftDrawDestroy(xftDraw); 12612 xftDraw = null; 12613 } 12614 12615 /+ 12616 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 12617 if(font && font !is defaultfont) { 12618 XFreeFont(display, font); 12619 font = null; 12620 } 12621 if(fontset && fontset !is defaultfontset) { 12622 XFreeFontSet(display, fontset); 12623 fontset = null; 12624 } 12625 +/ 12626 XFlush(display); 12627 12628 if(window.paintingFinishedDg !is null) 12629 window.paintingFinishedDg()(); 12630 } 12631 12632 bool backgroundIsNotTransparent = true; 12633 bool foregroundIsNotTransparent = true; 12634 12635 bool _penInitialized = false; 12636 Pen _activePen; 12637 12638 Color _outlineColor; 12639 Color _fillColor; 12640 12641 @property void pen(Pen p) { 12642 if(_penInitialized && p == _activePen) { 12643 return; 12644 } 12645 _penInitialized = true; 12646 _activePen = p; 12647 _outlineColor = p.color; 12648 12649 int style; 12650 12651 byte dashLength; 12652 12653 final switch(p.style) { 12654 case Pen.Style.Solid: 12655 style = 0 /*LineSolid*/; 12656 break; 12657 case Pen.Style.Dashed: 12658 style = 1 /*LineOnOffDash*/; 12659 dashLength = 4; 12660 break; 12661 case Pen.Style.Dotted: 12662 style = 1 /*LineOnOffDash*/; 12663 dashLength = 1; 12664 break; 12665 } 12666 12667 XSetLineAttributes(display, gc, p.width, style, 0, 0); 12668 if(dashLength) 12669 XSetDashes(display, gc, 0, &dashLength, 1); 12670 12671 if(p.color.a == 0) { 12672 foregroundIsNotTransparent = false; 12673 return; 12674 } 12675 12676 foregroundIsNotTransparent = true; 12677 12678 XSetForeground(display, gc, colorToX(p.color, display)); 12679 12680 version(with_xft) 12681 updateXftColor(); 12682 } 12683 12684 RasterOp _currentRasterOp; 12685 bool _currentRasterOpInitialized = false; 12686 @property void rasterOp(RasterOp op) { 12687 if(_currentRasterOpInitialized && _currentRasterOp == op) 12688 return; 12689 _currentRasterOp = op; 12690 _currentRasterOpInitialized = true; 12691 int mode; 12692 final switch(op) { 12693 case RasterOp.normal: 12694 mode = GXcopy; 12695 break; 12696 case RasterOp.xor: 12697 mode = GXxor; 12698 break; 12699 } 12700 XSetFunction(display, gc, mode); 12701 } 12702 12703 12704 bool _fillColorInitialized = false; 12705 12706 @property void fillColor(Color c) { 12707 if(_fillColorInitialized && _fillColor == c) 12708 return; // already good, no need to waste time calling it 12709 _fillColor = c; 12710 _fillColorInitialized = true; 12711 if(c.a == 0) { 12712 backgroundIsNotTransparent = false; 12713 return; 12714 } 12715 12716 backgroundIsNotTransparent = true; 12717 12718 XSetBackground(display, gc, colorToX(c, display)); 12719 12720 } 12721 12722 void swapColors() { 12723 auto tmp = _fillColor; 12724 fillColor = _outlineColor; 12725 auto newPen = _activePen; 12726 newPen.color = tmp; 12727 pen(newPen); 12728 } 12729 12730 uint colorToX(Color c, Display* display) { 12731 auto visual = DefaultVisual(display, DefaultScreen(display)); 12732 import core.bitop; 12733 uint color = 0; 12734 { 12735 auto startBit = bsf(visual.red_mask); 12736 auto lastBit = bsr(visual.red_mask); 12737 auto r = cast(uint) c.r; 12738 r >>= 7 - (lastBit - startBit); 12739 r <<= startBit; 12740 color |= r; 12741 } 12742 { 12743 auto startBit = bsf(visual.green_mask); 12744 auto lastBit = bsr(visual.green_mask); 12745 auto g = cast(uint) c.g; 12746 g >>= 7 - (lastBit - startBit); 12747 g <<= startBit; 12748 color |= g; 12749 } 12750 { 12751 auto startBit = bsf(visual.blue_mask); 12752 auto lastBit = bsr(visual.blue_mask); 12753 auto b = cast(uint) c.b; 12754 b >>= 7 - (lastBit - startBit); 12755 b <<= startBit; 12756 color |= b; 12757 } 12758 12759 12760 12761 return color; 12762 } 12763 12764 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12765 // source x, source y 12766 if(ix >= i.width) return; 12767 if(iy >= i.height) return; 12768 if(ix + w > i.width) w = i.width - ix; 12769 if(iy + h > i.height) h = i.height - iy; 12770 if(i.usingXshm) 12771 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 12772 else 12773 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 12774 } 12775 12776 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12777 if(s.enableAlpha) { 12778 // the Sprite must be created first, meaning if we're here, XRender is already loaded 12779 if(this.xrenderPicturePainter == None) { 12780 XRenderPictureAttributes attrs; 12781 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 12782 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 12783 12784 // need to initialize the clip 12785 XRectangle[1] rects; 12786 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 12787 12788 if(_clipRectangle != Rectangle.init) 12789 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12790 } 12791 12792 XRenderComposite( 12793 display, 12794 3, // PicOpOver 12795 s.xrenderPicture, 12796 None, 12797 this.xrenderPicturePainter, 12798 ix, 12799 iy, 12800 0, 12801 0, 12802 x, 12803 y, 12804 w ? w : s.width, 12805 h ? h : s.height 12806 ); 12807 } else { 12808 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 12809 } 12810 } 12811 12812 int fontHeight() { 12813 version(with_xft) 12814 if(xftFont !is null) 12815 return xftFont.height; 12816 if(font) 12817 return font.max_bounds.ascent + font.max_bounds.descent; 12818 return 12; // pretty common default... 12819 } 12820 12821 int textWidth(in char[] line) { 12822 version(with_xft) 12823 if(xftFont) { 12824 if(line.length == 0) 12825 return 0; 12826 XGlyphInfo extents; 12827 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 12828 return extents.width; 12829 } 12830 12831 if(fontset) { 12832 if(line.length == 0) 12833 return 0; 12834 XRectangle rect; 12835 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 12836 12837 return rect.width; 12838 } 12839 12840 if(font) 12841 // FIXME: unicode 12842 return XTextWidth( font, line.ptr, cast(int) line.length); 12843 else 12844 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 12845 } 12846 12847 Size textSize(in char[] text) { 12848 auto maxWidth = 0; 12849 auto lineHeight = fontHeight; 12850 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 12851 foreach(line; text.split('\n')) { 12852 int textWidth = this.textWidth(line); 12853 if(textWidth > maxWidth) 12854 maxWidth = textWidth; 12855 h += lineHeight + 4; 12856 } 12857 return Size(maxWidth, h); 12858 } 12859 12860 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 12861 const(char)[] text; 12862 version(with_xft) 12863 if(xftFont) { 12864 text = originalText; 12865 goto loaded; 12866 } 12867 12868 if(fontset) 12869 text = originalText; 12870 else { 12871 text.reserve(originalText.length); 12872 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 12873 // then strip the rest so there isn't garbage 12874 foreach(dchar ch; originalText) 12875 if(ch < 256) 12876 text ~= cast(ubyte) ch; 12877 else 12878 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 12879 } 12880 loaded: 12881 if(text.length == 0) 12882 return; 12883 12884 // FIXME: should we clip it to the bounding box? 12885 int textHeight = fontHeight; 12886 12887 auto lines = text.split('\n'); 12888 12889 const lineHeight = textHeight; 12890 textHeight *= lines.length; 12891 12892 int cy = y; 12893 12894 if(alignment & TextAlignment.VerticalBottom) { 12895 if(y2 <= 0) 12896 return; 12897 auto h = y2 - y; 12898 if(h > textHeight) { 12899 cy += h - textHeight; 12900 cy -= lineHeight / 2; 12901 } 12902 } else if(alignment & TextAlignment.VerticalCenter) { 12903 if(y2 <= 0) 12904 return; 12905 auto h = y2 - y; 12906 if(textHeight < h) { 12907 cy += (h - textHeight) / 2; 12908 //cy -= lineHeight / 4; 12909 } 12910 } 12911 12912 foreach(line; text.split('\n')) { 12913 int textWidth = this.textWidth(line); 12914 12915 int px = x, py = cy; 12916 12917 if(alignment & TextAlignment.Center) { 12918 if(x2 <= 0) 12919 return; 12920 auto w = x2 - x; 12921 if(w > textWidth) 12922 px += (w - textWidth) / 2; 12923 } else if(alignment & TextAlignment.Right) { 12924 if(x2 <= 0) 12925 return; 12926 auto pos = x2 - textWidth; 12927 if(pos > x) 12928 px = pos; 12929 } 12930 12931 version(with_xft) 12932 if(xftFont) { 12933 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 12934 12935 goto carry_on; 12936 } 12937 12938 if(fontset) 12939 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 12940 else 12941 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 12942 carry_on: 12943 cy += lineHeight + 4; 12944 } 12945 } 12946 12947 void drawPixel(int x, int y) { 12948 XDrawPoint(display, d, gc, x, y); 12949 } 12950 12951 // The basic shapes, outlined 12952 12953 void drawLine(int x1, int y1, int x2, int y2) { 12954 if(foregroundIsNotTransparent) 12955 XDrawLine(display, d, gc, x1, y1, x2, y2); 12956 } 12957 12958 void drawRectangle(int x, int y, int width, int height) { 12959 if(backgroundIsNotTransparent) { 12960 swapColors(); 12961 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 12962 swapColors(); 12963 } 12964 // 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 12965 if(foregroundIsNotTransparent) 12966 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 12967 } 12968 12969 /// Arguments are the points of the bounding rectangle 12970 void drawEllipse(int x1, int y1, int x2, int y2) { 12971 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 12972 } 12973 12974 // NOTE: start and finish are in units of degrees * 64 12975 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 12976 if(backgroundIsNotTransparent) { 12977 swapColors(); 12978 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 12979 swapColors(); 12980 } 12981 if(foregroundIsNotTransparent) { 12982 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 12983 // Windows draws the straight lines on the edges too so FIXME sort of 12984 } 12985 } 12986 12987 void drawPolygon(Point[] vertexes) { 12988 XPoint[16] pointsBuffer; 12989 XPoint[] points; 12990 if(vertexes.length <= pointsBuffer.length) 12991 points = pointsBuffer[0 .. vertexes.length]; 12992 else 12993 points.length = vertexes.length; 12994 12995 foreach(i, p; vertexes) { 12996 points[i].x = cast(short) p.x; 12997 points[i].y = cast(short) p.y; 12998 } 12999 13000 if(backgroundIsNotTransparent) { 13001 swapColors(); 13002 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 13003 swapColors(); 13004 } 13005 if(foregroundIsNotTransparent) { 13006 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 13007 } 13008 } 13009 } 13010 13011 /* XRender { */ 13012 13013 struct XRenderColor { 13014 ushort red; 13015 ushort green; 13016 ushort blue; 13017 ushort alpha; 13018 } 13019 13020 alias Picture = XID; 13021 alias PictFormat = XID; 13022 13023 struct XGlyphInfo { 13024 ushort width; 13025 ushort height; 13026 short x; 13027 short y; 13028 short xOff; 13029 short yOff; 13030 } 13031 13032 struct XRenderDirectFormat { 13033 short red; 13034 short redMask; 13035 short green; 13036 short greenMask; 13037 short blue; 13038 short blueMask; 13039 short alpha; 13040 short alphaMask; 13041 } 13042 13043 struct XRenderPictFormat { 13044 PictFormat id; 13045 int type; 13046 int depth; 13047 XRenderDirectFormat direct; 13048 Colormap colormap; 13049 } 13050 13051 enum PictFormatID = (1 << 0); 13052 enum PictFormatType = (1 << 1); 13053 enum PictFormatDepth = (1 << 2); 13054 enum PictFormatRed = (1 << 3); 13055 enum PictFormatRedMask =(1 << 4); 13056 enum PictFormatGreen = (1 << 5); 13057 enum PictFormatGreenMask=(1 << 6); 13058 enum PictFormatBlue = (1 << 7); 13059 enum PictFormatBlueMask =(1 << 8); 13060 enum PictFormatAlpha = (1 << 9); 13061 enum PictFormatAlphaMask=(1 << 10); 13062 enum PictFormatColormap =(1 << 11); 13063 13064 struct XRenderPictureAttributes { 13065 int repeat; 13066 Picture alpha_map; 13067 int alpha_x_origin; 13068 int alpha_y_origin; 13069 int clip_x_origin; 13070 int clip_y_origin; 13071 Pixmap clip_mask; 13072 Bool graphics_exposures; 13073 int subwindow_mode; 13074 int poly_edge; 13075 int poly_mode; 13076 Atom dither; 13077 Bool component_alpha; 13078 } 13079 13080 alias int XFixed; 13081 13082 struct XPointFixed { 13083 XFixed x, y; 13084 } 13085 13086 struct XCircle { 13087 XFixed x; 13088 XFixed y; 13089 XFixed radius; 13090 } 13091 13092 struct XTransform { 13093 XFixed[3][3] matrix; 13094 } 13095 13096 struct XFilters { 13097 int nfilter; 13098 char **filter; 13099 int nalias; 13100 short *alias_; 13101 } 13102 13103 struct XIndexValue { 13104 c_ulong pixel; 13105 ushort red, green, blue, alpha; 13106 } 13107 13108 struct XAnimCursor { 13109 Cursor cursor; 13110 c_ulong delay; 13111 } 13112 13113 struct XLinearGradient { 13114 XPointFixed p1; 13115 XPointFixed p2; 13116 } 13117 13118 struct XRadialGradient { 13119 XCircle inner; 13120 XCircle outer; 13121 } 13122 13123 struct XConicalGradient { 13124 XPointFixed center; 13125 XFixed angle; /* in degrees */ 13126 } 13127 13128 enum PictStandardARGB32 = 0; 13129 enum PictStandardRGB24 = 1; 13130 enum PictStandardA8 = 2; 13131 enum PictStandardA4 = 3; 13132 enum PictStandardA1 = 4; 13133 enum PictStandardNUM = 5; 13134 13135 interface XRender { 13136 extern(C) @nogc: 13137 13138 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13139 13140 Status XRenderQueryVersion (Display *dpy, 13141 int *major_versionp, 13142 int *minor_versionp); 13143 13144 Status XRenderQueryFormats (Display *dpy); 13145 13146 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13147 13148 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13149 13150 XRenderPictFormat * 13151 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13152 13153 XRenderPictFormat * 13154 XRenderFindFormat (Display *dpy, 13155 c_ulong mask, 13156 const XRenderPictFormat *templ, 13157 int count); 13158 XRenderPictFormat * 13159 XRenderFindStandardFormat (Display *dpy, 13160 int format); 13161 13162 XIndexValue * 13163 XRenderQueryPictIndexValues(Display *dpy, 13164 const XRenderPictFormat *format, 13165 int *num); 13166 13167 Picture XRenderCreatePicture( 13168 Display *dpy, 13169 Drawable drawable, 13170 const XRenderPictFormat *format, 13171 c_ulong valuemask, 13172 const XRenderPictureAttributes *attributes); 13173 13174 void XRenderChangePicture (Display *dpy, 13175 Picture picture, 13176 c_ulong valuemask, 13177 const XRenderPictureAttributes *attributes); 13178 13179 void 13180 XRenderSetPictureClipRectangles (Display *dpy, 13181 Picture picture, 13182 int xOrigin, 13183 int yOrigin, 13184 const XRectangle *rects, 13185 int n); 13186 13187 void 13188 XRenderSetPictureClipRegion (Display *dpy, 13189 Picture picture, 13190 Region r); 13191 13192 void 13193 XRenderSetPictureTransform (Display *dpy, 13194 Picture picture, 13195 XTransform *transform); 13196 13197 void 13198 XRenderFreePicture (Display *dpy, 13199 Picture picture); 13200 13201 void 13202 XRenderComposite (Display *dpy, 13203 int op, 13204 Picture src, 13205 Picture mask, 13206 Picture dst, 13207 int src_x, 13208 int src_y, 13209 int mask_x, 13210 int mask_y, 13211 int dst_x, 13212 int dst_y, 13213 uint width, 13214 uint height); 13215 13216 13217 Picture XRenderCreateSolidFill (Display *dpy, 13218 const XRenderColor *color); 13219 13220 Picture XRenderCreateLinearGradient (Display *dpy, 13221 const XLinearGradient *gradient, 13222 const XFixed *stops, 13223 const XRenderColor *colors, 13224 int nstops); 13225 13226 Picture XRenderCreateRadialGradient (Display *dpy, 13227 const XRadialGradient *gradient, 13228 const XFixed *stops, 13229 const XRenderColor *colors, 13230 int nstops); 13231 13232 Picture XRenderCreateConicalGradient (Display *dpy, 13233 const XConicalGradient *gradient, 13234 const XFixed *stops, 13235 const XRenderColor *colors, 13236 int nstops); 13237 13238 13239 13240 Cursor 13241 XRenderCreateCursor (Display *dpy, 13242 Picture source, 13243 uint x, 13244 uint y); 13245 13246 XFilters * 13247 XRenderQueryFilters (Display *dpy, Drawable drawable); 13248 13249 void 13250 XRenderSetPictureFilter (Display *dpy, 13251 Picture picture, 13252 const char *filter, 13253 XFixed *params, 13254 int nparams); 13255 13256 Cursor 13257 XRenderCreateAnimCursor (Display *dpy, 13258 int ncursor, 13259 XAnimCursor *cursors); 13260 } 13261 13262 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 13263 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 13264 13265 /* XRender } */ 13266 13267 /* Xrandr { */ 13268 13269 struct XRRMonitorInfo { 13270 Atom name; 13271 Bool primary; 13272 Bool automatic; 13273 int noutput; 13274 int x; 13275 int y; 13276 int width; 13277 int height; 13278 int mwidth; 13279 int mheight; 13280 /*RROutput*/ void *outputs; 13281 } 13282 13283 struct XRRScreenChangeNotifyEvent { 13284 int type; /* event base */ 13285 c_ulong serial; /* # of last request processed by server */ 13286 Bool send_event; /* true if this came from a SendEvent request */ 13287 Display *display; /* Display the event was read from */ 13288 Window window; /* window which selected for this event */ 13289 Window root; /* Root window for changed screen */ 13290 Time timestamp; /* when the screen change occurred */ 13291 Time config_timestamp; /* when the last configuration change */ 13292 ushort/*SizeID*/ size_index; 13293 ushort/*SubpixelOrder*/ subpixel_order; 13294 ushort/*Rotation*/ rotation; 13295 int width; 13296 int height; 13297 int mwidth; 13298 int mheight; 13299 } 13300 13301 enum RRScreenChangeNotify = 0; 13302 13303 enum RRScreenChangeNotifyMask = 1; 13304 13305 __gshared int xrrEventBase = -1; 13306 13307 13308 interface XRandr { 13309 extern(C) @nogc: 13310 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 13311 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 13312 13313 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 13314 void XRRFreeMonitors(XRRMonitorInfo *monitors); 13315 13316 void XRRSelectInput(Display *dpy, Window window, int mask); 13317 } 13318 13319 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 13320 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 13321 /* Xrandr } */ 13322 13323 /* Xft { */ 13324 13325 // actually freetype 13326 alias void FT_Face; 13327 13328 // actually fontconfig 13329 private alias FcBool = int; 13330 alias void FcCharSet; 13331 alias void FcPattern; 13332 alias void FcResult; 13333 enum FcEndian { FcEndianBig, FcEndianLittle } 13334 struct FcFontSet { 13335 int nfont; 13336 int sfont; 13337 FcPattern** fonts; 13338 } 13339 13340 // actually XRegion 13341 struct BOX { 13342 short x1, x2, y1, y2; 13343 } 13344 struct _XRegion { 13345 c_long size; 13346 c_long numRects; 13347 BOX* rects; 13348 BOX extents; 13349 } 13350 13351 alias Region = _XRegion*; 13352 13353 // ok actually Xft 13354 13355 struct XftFontInfo; 13356 13357 struct XftFont { 13358 int ascent; 13359 int descent; 13360 int height; 13361 int max_advance_width; 13362 FcCharSet* charset; 13363 FcPattern* pattern; 13364 } 13365 13366 struct XftDraw; 13367 13368 struct XftColor { 13369 c_ulong pixel; 13370 XRenderColor color; 13371 } 13372 13373 struct XftCharSpec { 13374 dchar ucs4; 13375 short x; 13376 short y; 13377 } 13378 13379 struct XftCharFontSpec { 13380 XftFont *font; 13381 dchar ucs4; 13382 short x; 13383 short y; 13384 } 13385 13386 struct XftGlyphSpec { 13387 uint glyph; 13388 short x; 13389 short y; 13390 } 13391 13392 struct XftGlyphFontSpec { 13393 XftFont *font; 13394 uint glyph; 13395 short x; 13396 short y; 13397 } 13398 13399 interface Xft { 13400 extern(C) @nogc pure: 13401 13402 Bool XftColorAllocName (Display *dpy, 13403 const Visual *visual, 13404 Colormap cmap, 13405 const char *name, 13406 XftColor *result); 13407 13408 Bool XftColorAllocValue (Display *dpy, 13409 Visual *visual, 13410 Colormap cmap, 13411 const XRenderColor *color, 13412 XftColor *result); 13413 13414 void XftColorFree (Display *dpy, 13415 Visual *visual, 13416 Colormap cmap, 13417 XftColor *color); 13418 13419 Bool XftDefaultHasRender (Display *dpy); 13420 13421 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 13422 13423 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 13424 13425 XftDraw * XftDrawCreate (Display *dpy, 13426 Drawable drawable, 13427 Visual *visual, 13428 Colormap colormap); 13429 13430 XftDraw * XftDrawCreateBitmap (Display *dpy, 13431 Pixmap bitmap); 13432 13433 XftDraw * XftDrawCreateAlpha (Display *dpy, 13434 Pixmap pixmap, 13435 int depth); 13436 13437 void XftDrawChange (XftDraw *draw, 13438 Drawable drawable); 13439 13440 Display * XftDrawDisplay (XftDraw *draw); 13441 13442 Drawable XftDrawDrawable (XftDraw *draw); 13443 13444 Colormap XftDrawColormap (XftDraw *draw); 13445 13446 Visual * XftDrawVisual (XftDraw *draw); 13447 13448 void XftDrawDestroy (XftDraw *draw); 13449 13450 Picture XftDrawPicture (XftDraw *draw); 13451 13452 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 13453 13454 void XftDrawGlyphs (XftDraw *draw, 13455 const XftColor *color, 13456 XftFont *pub, 13457 int x, 13458 int y, 13459 const uint *glyphs, 13460 int nglyphs); 13461 13462 void XftDrawString8 (XftDraw *draw, 13463 const XftColor *color, 13464 XftFont *pub, 13465 int x, 13466 int y, 13467 const char *string, 13468 int len); 13469 13470 void XftDrawString16 (XftDraw *draw, 13471 const XftColor *color, 13472 XftFont *pub, 13473 int x, 13474 int y, 13475 const wchar *string, 13476 int len); 13477 13478 void XftDrawString32 (XftDraw *draw, 13479 const XftColor *color, 13480 XftFont *pub, 13481 int x, 13482 int y, 13483 const dchar *string, 13484 int len); 13485 13486 void XftDrawStringUtf8 (XftDraw *draw, 13487 const XftColor *color, 13488 XftFont *pub, 13489 int x, 13490 int y, 13491 const char *string, 13492 int len); 13493 void XftDrawStringUtf16 (XftDraw *draw, 13494 const XftColor *color, 13495 XftFont *pub, 13496 int x, 13497 int y, 13498 const char *string, 13499 FcEndian endian, 13500 int len); 13501 13502 void XftDrawCharSpec (XftDraw *draw, 13503 const XftColor *color, 13504 XftFont *pub, 13505 const XftCharSpec *chars, 13506 int len); 13507 13508 void XftDrawCharFontSpec (XftDraw *draw, 13509 const XftColor *color, 13510 const XftCharFontSpec *chars, 13511 int len); 13512 13513 void XftDrawGlyphSpec (XftDraw *draw, 13514 const XftColor *color, 13515 XftFont *pub, 13516 const XftGlyphSpec *glyphs, 13517 int len); 13518 13519 void XftDrawGlyphFontSpec (XftDraw *draw, 13520 const XftColor *color, 13521 const XftGlyphFontSpec *glyphs, 13522 int len); 13523 13524 void XftDrawRect (XftDraw *draw, 13525 const XftColor *color, 13526 int x, 13527 int y, 13528 uint width, 13529 uint height); 13530 13531 Bool XftDrawSetClip (XftDraw *draw, 13532 Region r); 13533 13534 13535 Bool XftDrawSetClipRectangles (XftDraw *draw, 13536 int xOrigin, 13537 int yOrigin, 13538 const XRectangle *rects, 13539 int n); 13540 13541 void XftDrawSetSubwindowMode (XftDraw *draw, 13542 int mode); 13543 13544 void XftGlyphExtents (Display *dpy, 13545 XftFont *pub, 13546 const uint *glyphs, 13547 int nglyphs, 13548 XGlyphInfo *extents); 13549 13550 void XftTextExtents8 (Display *dpy, 13551 XftFont *pub, 13552 const char *string, 13553 int len, 13554 XGlyphInfo *extents); 13555 13556 void XftTextExtents16 (Display *dpy, 13557 XftFont *pub, 13558 const wchar *string, 13559 int len, 13560 XGlyphInfo *extents); 13561 13562 void XftTextExtents32 (Display *dpy, 13563 XftFont *pub, 13564 const dchar *string, 13565 int len, 13566 XGlyphInfo *extents); 13567 13568 void XftTextExtentsUtf8 (Display *dpy, 13569 XftFont *pub, 13570 const char *string, 13571 int len, 13572 XGlyphInfo *extents); 13573 13574 void XftTextExtentsUtf16 (Display *dpy, 13575 XftFont *pub, 13576 const char *string, 13577 FcEndian endian, 13578 int len, 13579 XGlyphInfo *extents); 13580 13581 FcPattern * XftFontMatch (Display *dpy, 13582 int screen, 13583 const FcPattern *pattern, 13584 FcResult *result); 13585 13586 XftFont * XftFontOpen (Display *dpy, int screen, ...); 13587 13588 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 13589 13590 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 13591 13592 FT_Face XftLockFace (XftFont *pub); 13593 13594 void XftUnlockFace (XftFont *pub); 13595 13596 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 13597 13598 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 13599 13600 dchar XftFontInfoHash (const XftFontInfo *fi); 13601 13602 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 13603 13604 XftFont * XftFontOpenInfo (Display *dpy, 13605 FcPattern *pattern, 13606 XftFontInfo *fi); 13607 13608 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 13609 13610 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 13611 13612 void XftFontClose (Display *dpy, XftFont *pub); 13613 13614 FcBool XftInitFtLibrary(); 13615 void XftFontLoadGlyphs (Display *dpy, 13616 XftFont *pub, 13617 FcBool need_bitmaps, 13618 const uint *glyphs, 13619 int nglyph); 13620 13621 void XftFontUnloadGlyphs (Display *dpy, 13622 XftFont *pub, 13623 const uint *glyphs, 13624 int nglyph); 13625 13626 FcBool XftFontCheckGlyph (Display *dpy, 13627 XftFont *pub, 13628 FcBool need_bitmaps, 13629 uint glyph, 13630 uint *missing, 13631 int *nmissing); 13632 13633 FcBool XftCharExists (Display *dpy, 13634 XftFont *pub, 13635 dchar ucs4); 13636 13637 uint XftCharIndex (Display *dpy, 13638 XftFont *pub, 13639 dchar ucs4); 13640 FcBool XftInit (const char *config); 13641 13642 int XftGetVersion (); 13643 13644 FcFontSet * XftListFonts (Display *dpy, 13645 int screen, 13646 ...); 13647 13648 FcPattern *XftNameParse (const char *name); 13649 13650 void XftGlyphRender (Display *dpy, 13651 int op, 13652 Picture src, 13653 XftFont *pub, 13654 Picture dst, 13655 int srcx, 13656 int srcy, 13657 int x, 13658 int y, 13659 const uint *glyphs, 13660 int nglyphs); 13661 13662 void XftGlyphSpecRender (Display *dpy, 13663 int op, 13664 Picture src, 13665 XftFont *pub, 13666 Picture dst, 13667 int srcx, 13668 int srcy, 13669 const XftGlyphSpec *glyphs, 13670 int nglyphs); 13671 13672 void XftCharSpecRender (Display *dpy, 13673 int op, 13674 Picture src, 13675 XftFont *pub, 13676 Picture dst, 13677 int srcx, 13678 int srcy, 13679 const XftCharSpec *chars, 13680 int len); 13681 void XftGlyphFontSpecRender (Display *dpy, 13682 int op, 13683 Picture src, 13684 Picture dst, 13685 int srcx, 13686 int srcy, 13687 const XftGlyphFontSpec *glyphs, 13688 int nglyphs); 13689 13690 void XftCharFontSpecRender (Display *dpy, 13691 int op, 13692 Picture src, 13693 Picture dst, 13694 int srcx, 13695 int srcy, 13696 const XftCharFontSpec *chars, 13697 int len); 13698 13699 void XftTextRender8 (Display *dpy, 13700 int op, 13701 Picture src, 13702 XftFont *pub, 13703 Picture dst, 13704 int srcx, 13705 int srcy, 13706 int x, 13707 int y, 13708 const char *string, 13709 int len); 13710 void XftTextRender16 (Display *dpy, 13711 int op, 13712 Picture src, 13713 XftFont *pub, 13714 Picture dst, 13715 int srcx, 13716 int srcy, 13717 int x, 13718 int y, 13719 const wchar *string, 13720 int len); 13721 13722 void XftTextRender16BE (Display *dpy, 13723 int op, 13724 Picture src, 13725 XftFont *pub, 13726 Picture dst, 13727 int srcx, 13728 int srcy, 13729 int x, 13730 int y, 13731 const char *string, 13732 int len); 13733 13734 void XftTextRender16LE (Display *dpy, 13735 int op, 13736 Picture src, 13737 XftFont *pub, 13738 Picture dst, 13739 int srcx, 13740 int srcy, 13741 int x, 13742 int y, 13743 const char *string, 13744 int len); 13745 13746 void XftTextRender32 (Display *dpy, 13747 int op, 13748 Picture src, 13749 XftFont *pub, 13750 Picture dst, 13751 int srcx, 13752 int srcy, 13753 int x, 13754 int y, 13755 const dchar *string, 13756 int len); 13757 13758 void XftTextRender32BE (Display *dpy, 13759 int op, 13760 Picture src, 13761 XftFont *pub, 13762 Picture dst, 13763 int srcx, 13764 int srcy, 13765 int x, 13766 int y, 13767 const char *string, 13768 int len); 13769 13770 void XftTextRender32LE (Display *dpy, 13771 int op, 13772 Picture src, 13773 XftFont *pub, 13774 Picture dst, 13775 int srcx, 13776 int srcy, 13777 int x, 13778 int y, 13779 const char *string, 13780 int len); 13781 13782 void XftTextRenderUtf8 (Display *dpy, 13783 int op, 13784 Picture src, 13785 XftFont *pub, 13786 Picture dst, 13787 int srcx, 13788 int srcy, 13789 int x, 13790 int y, 13791 const char *string, 13792 int len); 13793 13794 void XftTextRenderUtf16 (Display *dpy, 13795 int op, 13796 Picture src, 13797 XftFont *pub, 13798 Picture dst, 13799 int srcx, 13800 int srcy, 13801 int x, 13802 int y, 13803 const char *string, 13804 FcEndian endian, 13805 int len); 13806 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 13807 13808 } 13809 13810 interface FontConfig { 13811 extern(C) @nogc pure: 13812 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 13813 void FcFontSetDestroy(FcFontSet*); 13814 char* FcNameUnparse(const FcPattern *); 13815 } 13816 13817 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 13818 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 13819 13820 13821 /* Xft } */ 13822 13823 class XDisconnectException : Exception { 13824 bool userRequested; 13825 this(bool userRequested = true) { 13826 this.userRequested = userRequested; 13827 super("X disconnected"); 13828 } 13829 } 13830 13831 /++ 13832 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 13833 13834 Please note that it returns 13835 +/ 13836 XErrorEvent[] trapXErrors(scope void delegate() dg) { 13837 13838 static XErrorEvent[] errorBuffer; 13839 13840 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 13841 errorBuffer ~= *evt; 13842 return 0; 13843 } 13844 13845 auto savedErrorHandler = XSetErrorHandler(&handler); 13846 13847 try { 13848 dg(); 13849 } finally { 13850 XSync(XDisplayConnection.get, 0/*False*/); 13851 XSetErrorHandler(savedErrorHandler); 13852 } 13853 13854 auto bfr = errorBuffer; 13855 errorBuffer = null; 13856 13857 return bfr; 13858 } 13859 13860 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 13861 class XDisplayConnection { 13862 private __gshared Display* display; 13863 private __gshared XIM xim; 13864 private __gshared char* displayName; 13865 13866 private __gshared int connectionSequence_; 13867 private __gshared bool isLocal_; 13868 13869 /// use this for lazy caching when reconnection 13870 static int connectionSequenceNumber() { return connectionSequence_; } 13871 13872 /++ 13873 Guesses if the connection appears to be local. 13874 13875 History: 13876 Added June 3, 2021 13877 +/ 13878 static @property bool isLocal() nothrow @trusted @nogc { 13879 return isLocal_; 13880 } 13881 13882 /// Attempts recreation of state, may require application assistance 13883 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 13884 /// then call this, and if successful, reenter the loop. 13885 static void discardAndRecreate(string newDisplayString = null) { 13886 if(insideXEventLoop) 13887 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 13888 13889 // 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 13890 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 13891 13892 foreach(handle; chnenhm) { 13893 handle.discardConnectionState(); 13894 } 13895 13896 discardState(); 13897 13898 if(newDisplayString !is null) 13899 setDisplayName(newDisplayString); 13900 13901 auto display = get(); 13902 13903 foreach(handle; chnenhm) { 13904 handle.recreateAfterDisconnect(); 13905 } 13906 } 13907 13908 private __gshared EventMask rootEventMask; 13909 13910 /++ 13911 Requests the specified input from the root window on the connection, in addition to any other request. 13912 13913 13914 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. 13915 13916 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 13917 +/ 13918 static void addRootInput(EventMask mask) { 13919 auto old = rootEventMask; 13920 rootEventMask |= mask; 13921 get(); // to ensure display connected 13922 if(display !is null && rootEventMask != old) 13923 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 13924 } 13925 13926 static void discardState() { 13927 freeImages(); 13928 13929 foreach(atomPtr; interredAtoms) 13930 *atomPtr = 0; 13931 interredAtoms = null; 13932 interredAtoms.assumeSafeAppend(); 13933 13934 ScreenPainterImplementation.fontAttempted = false; 13935 ScreenPainterImplementation.defaultfont = null; 13936 ScreenPainterImplementation.defaultfontset = null; 13937 13938 Image.impl.xshmQueryCompleted = false; 13939 Image.impl._xshmAvailable = false; 13940 13941 SimpleWindow.nativeMapping = null; 13942 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 13943 // GlobalHotkeyManager 13944 13945 display = null; 13946 xim = null; 13947 } 13948 13949 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 13950 private static void createXIM () { 13951 import core.stdc.locale : setlocale, LC_ALL; 13952 import core.stdc.stdio : stderr, fprintf; 13953 import core.stdc.stdlib : free; 13954 import core.stdc.string : strdup; 13955 13956 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 13957 13958 auto olocale = strdup(setlocale(LC_ALL, null)); 13959 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 13960 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 13961 13962 //fprintf(stderr, "opening IM...\n"); 13963 foreach (string s; mtry) { 13964 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 13965 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 13966 } 13967 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 13968 } 13969 13970 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 13971 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 13972 static struct ImgList { 13973 size_t img; // class; hide it from GC 13974 ImgList* next; 13975 } 13976 13977 static __gshared ImgList* imglist = null; 13978 static __gshared bool imglistLocked = false; // true: don't register and unregister images 13979 13980 static void registerImage (Image img) { 13981 if (!imglistLocked && img !is null) { 13982 import core.stdc.stdlib : malloc; 13983 auto it = cast(ImgList*)malloc(ImgList.sizeof); 13984 assert(it !is null); // do proper checks 13985 it.img = cast(size_t)cast(void*)img; 13986 it.next = imglist; 13987 imglist = it; 13988 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 13989 } 13990 } 13991 13992 static void unregisterImage (Image img) { 13993 if (!imglistLocked && img !is null) { 13994 import core.stdc.stdlib : free; 13995 ImgList* prev = null; 13996 ImgList* cur = imglist; 13997 while (cur !is null) { 13998 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 13999 prev = cur; 14000 cur = cur.next; 14001 } 14002 if (cur !is null) { 14003 if (prev is null) imglist = cur.next; else prev.next = cur.next; 14004 free(cur); 14005 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 14006 } else { 14007 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 14008 } 14009 } 14010 } 14011 14012 static void freeImages () { // needed for discardAndRecreate 14013 imglistLocked = true; 14014 scope(exit) imglistLocked = false; 14015 ImgList* cur = imglist; 14016 ImgList* next = null; 14017 while (cur !is null) { 14018 import core.stdc.stdlib : free; 14019 next = cur.next; 14020 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 14021 (cast(Image)cast(void*)cur.img).dispose(); 14022 free(cur); 14023 cur = next; 14024 } 14025 imglist = null; 14026 } 14027 14028 /// can be used to override normal handling of display name 14029 /// from environment and/or command line 14030 static setDisplayName(string newDisplayName) { 14031 displayName = cast(char*) (newDisplayName ~ '\0'); 14032 } 14033 14034 /// resets to the default display string 14035 static resetDisplayName() { 14036 displayName = null; 14037 } 14038 14039 /// 14040 static Display* get() { 14041 if(display is null) { 14042 if(!librariesSuccessfullyLoaded) 14043 throw new Exception("Unable to load X11 client libraries"); 14044 display = XOpenDisplay(displayName); 14045 14046 isLocal_ = false; 14047 14048 connectionSequence_++; 14049 if(display is null) 14050 throw new Exception("Unable to open X display"); 14051 14052 auto str = display.display_name; 14053 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 14054 // and otherwise it probably isn't 14055 if(str is null || (str[0] != ':' && str[0] != '/')) 14056 isLocal_ = false; 14057 else 14058 isLocal_ = true; 14059 14060 debug(sdpy_x_errors) { 14061 XSetErrorHandler(&adrlogger); 14062 XSynchronize(display, true); 14063 14064 extern(C) int wtf() { 14065 if(errorHappened) { 14066 asm { int 3; } 14067 errorHappened = false; 14068 } 14069 return 0; 14070 } 14071 XSetAfterFunction(display, &wtf); 14072 } 14073 14074 14075 XSetIOErrorHandler(&x11ioerrCB); 14076 Bool sup; 14077 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 14078 createXIM(); 14079 version(with_eventloop) { 14080 import arsd.eventloop; 14081 addFileEventListeners(display.fd, &eventListener, null, null); 14082 } 14083 } 14084 14085 return display; 14086 } 14087 14088 extern(C) 14089 static int x11ioerrCB(Display* dpy) { 14090 throw new XDisconnectException(false); 14091 } 14092 14093 version(with_eventloop) { 14094 import arsd.eventloop; 14095 static void eventListener(OsFileHandle fd) { 14096 //this.mtLock(); 14097 //scope(exit) this.mtUnlock(); 14098 while(XPending(display)) 14099 doXNextEvent(display); 14100 } 14101 } 14102 14103 // close connection on program exit -- we need this to properly free all images 14104 static ~this () { 14105 // the gui thread must clean up after itself or else Xlib might deadlock 14106 // using this flag on any thread destruction is the easiest way i know of 14107 // (shared static this is run by the LAST thread to exit, which may not be 14108 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 14109 if(thisIsGuiThread) 14110 close(); 14111 } 14112 14113 /// 14114 static void close() { 14115 if(display is null) 14116 return; 14117 14118 version(with_eventloop) { 14119 import arsd.eventloop; 14120 removeFileEventListeners(display.fd); 14121 } 14122 14123 // now remove all registered images to prevent shared memory leaks 14124 freeImages(); 14125 14126 // tbh I don't know why it is doing this but like if this happens to run 14127 // from the other thread there's frequent hanging inside here. 14128 if(thisIsGuiThread) 14129 XCloseDisplay(display); 14130 display = null; 14131 } 14132 } 14133 14134 mixin template NativeImageImplementation() { 14135 XImage* handle; 14136 ubyte* rawData; 14137 14138 XShmSegmentInfo shminfo; 14139 14140 __gshared bool xshmQueryCompleted; 14141 __gshared bool _xshmAvailable; 14142 public static @property bool xshmAvailable() { 14143 if(!xshmQueryCompleted) { 14144 int i1, i2, i3; 14145 xshmQueryCompleted = true; 14146 14147 if(!XDisplayConnection.isLocal) 14148 _xshmAvailable = false; 14149 else 14150 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14151 } 14152 return _xshmAvailable; 14153 } 14154 14155 bool usingXshm; 14156 final: 14157 14158 private __gshared bool xshmfailed; 14159 14160 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14161 auto display = XDisplayConnection.get(); 14162 assert(display !is null); 14163 auto screen = DefaultScreen(display); 14164 14165 // it will only use shared memory for somewhat largish images, 14166 // since otherwise we risk wasting shared memory handles on a lot of little ones 14167 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14168 14169 14170 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14171 // the actual use still fails. For example, if the program is in a container and permission denied 14172 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14173 // 14174 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14175 14176 14177 // synchronize so preexisting buffers are clear 14178 XSync(display, false); 14179 xshmfailed = false; 14180 14181 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14182 14183 14184 usingXshm = true; 14185 handle = XShmCreateImage( 14186 display, 14187 DefaultVisual(display, screen), 14188 enableAlpha ? 32: 24, 14189 ImageFormat.ZPixmap, 14190 null, 14191 &shminfo, 14192 width, height); 14193 if(handle is null) 14194 goto abortXshm1; 14195 14196 if(handle.bytes_per_line != 4 * width) 14197 goto abortXshm2; 14198 14199 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14200 if(shminfo.shmid < 0) 14201 goto abortXshm3; 14202 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14203 if(rawData == cast(ubyte*) -1) 14204 goto abortXshm4; 14205 shminfo.readOnly = 0; 14206 XShmAttach(display, &shminfo); 14207 14208 // and now to the final error check to ensure it actually worked. 14209 XSync(display, false); 14210 if(xshmfailed) 14211 goto abortXshm5; 14212 14213 XSetErrorHandler(oldErrorHandler); 14214 14215 XDisplayConnection.registerImage(this); 14216 // if I don't flush here there's a chance the dtor will run before the 14217 // ctor and lead to a bad value X error. While this hurts the efficiency 14218 // it is local anyway so prolly better to keep it simple 14219 XFlush(display); 14220 14221 return; 14222 14223 abortXshm5: 14224 shmdt(shminfo.shmaddr); 14225 rawData = null; 14226 14227 abortXshm4: 14228 shmctl(shminfo.shmid, IPC_RMID, null); 14229 14230 abortXshm3: 14231 // nothing needed, the shmget failed so there's nothing to free 14232 14233 abortXshm2: 14234 XDestroyImage(handle); 14235 handle = null; 14236 14237 abortXshm1: 14238 XSetErrorHandler(oldErrorHandler); 14239 usingXshm = false; 14240 handle = null; 14241 14242 shminfo = typeof(shminfo).init; 14243 14244 _xshmAvailable = false; // don't try again in the future 14245 14246 // writeln("fallingback"); 14247 14248 goto fallback; 14249 14250 } else { 14251 fallback: 14252 14253 if (forcexshm) throw new Exception("can't create XShm Image"); 14254 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 14255 import core.stdc.stdlib : malloc; 14256 rawData = cast(ubyte*) malloc(width * height * 4); 14257 14258 handle = XCreateImage( 14259 display, 14260 DefaultVisual(display, screen), 14261 enableAlpha ? 32 : 24, // bpp 14262 ImageFormat.ZPixmap, 14263 0, // offset 14264 rawData, 14265 width, height, 14266 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 14267 } 14268 } 14269 14270 void dispose() { 14271 // note: this calls free(rawData) for us 14272 if(handle) { 14273 if (usingXshm) { 14274 XDisplayConnection.unregisterImage(this); 14275 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 14276 } 14277 XDestroyImage(handle); 14278 if(usingXshm) { 14279 shmdt(shminfo.shmaddr); 14280 shmctl(shminfo.shmid, IPC_RMID, null); 14281 } 14282 handle = null; 14283 } 14284 } 14285 14286 Color getPixel(int x, int y) { 14287 auto offset = (y * width + x) * 4; 14288 Color c; 14289 c.a = enableAlpha ? rawData[offset + 3] : 255; 14290 c.b = rawData[offset + 0]; 14291 c.g = rawData[offset + 1]; 14292 c.r = rawData[offset + 2]; 14293 if(enableAlpha) 14294 c.unPremultiply; 14295 return c; 14296 } 14297 14298 void setPixel(int x, int y, Color c) { 14299 if(enableAlpha) 14300 c.premultiply(); 14301 auto offset = (y * width + x) * 4; 14302 rawData[offset + 0] = c.b; 14303 rawData[offset + 1] = c.g; 14304 rawData[offset + 2] = c.r; 14305 if(enableAlpha) 14306 rawData[offset + 3] = c.a; 14307 } 14308 14309 void convertToRgbaBytes(ubyte[] where) { 14310 assert(where.length == this.width * this.height * 4); 14311 14312 // if rawData had a length.... 14313 //assert(rawData.length == where.length); 14314 for(int idx = 0; idx < where.length; idx += 4) { 14315 where[idx + 0] = rawData[idx + 2]; // r 14316 where[idx + 1] = rawData[idx + 1]; // g 14317 where[idx + 2] = rawData[idx + 0]; // b 14318 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 14319 14320 if(enableAlpha) 14321 unPremultiplyRgba(where[idx .. idx + 4]); 14322 } 14323 } 14324 14325 void setFromRgbaBytes(in ubyte[] where) { 14326 assert(where.length == this.width * this.height * 4); 14327 14328 // if rawData had a length.... 14329 //assert(rawData.length == where.length); 14330 for(int idx = 0; idx < where.length; idx += 4) { 14331 rawData[idx + 2] = where[idx + 0]; // r 14332 rawData[idx + 1] = where[idx + 1]; // g 14333 rawData[idx + 0] = where[idx + 2]; // b 14334 if(enableAlpha) { 14335 rawData[idx + 3] = where[idx + 3]; // a 14336 premultiplyBgra(rawData[idx .. idx + 4]); 14337 } 14338 } 14339 } 14340 14341 } 14342 14343 mixin template NativeSimpleWindowImplementation() { 14344 GC gc; 14345 Window window; 14346 Display* display; 14347 14348 Pixmap buffer; 14349 int bufferw, bufferh; // size of the buffer; can be bigger than window 14350 XIC xic; // input context 14351 int curHidden = 0; // counter 14352 Cursor blankCurPtr = 0; 14353 int cursorSequenceNumber = 0; 14354 int warpEventCount = 0; // number of mouse movement events to eat 14355 14356 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 14357 X11GetSelectionHandler[Atom] getSelectionHandlers; 14358 14359 version(without_opengl) {} else 14360 GLXContext glc; 14361 14362 private void fixFixedSize(bool forced=false) (int width, int height) { 14363 if (forced || this.resizability == Resizability.fixedSize) { 14364 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 14365 XSizeHints sh; 14366 static if (!forced) { 14367 c_long spr; 14368 XGetWMNormalHints(display, window, &sh, &spr); 14369 sh.flags |= PMaxSize | PMinSize; 14370 } else { 14371 sh.flags = PMaxSize | PMinSize; 14372 } 14373 sh.min_width = width; 14374 sh.min_height = height; 14375 sh.max_width = width; 14376 sh.max_height = height; 14377 XSetWMNormalHints(display, window, &sh); 14378 //XFlush(display); 14379 } 14380 } 14381 14382 ScreenPainter getPainter(bool manualInvalidations) { 14383 return ScreenPainter(this, window, manualInvalidations); 14384 } 14385 14386 void move(int x, int y) { 14387 XMoveWindow(display, window, x, y); 14388 } 14389 14390 void resize(int w, int h) { 14391 if (w < 1) w = 1; 14392 if (h < 1) h = 1; 14393 XResizeWindow(display, window, w, h); 14394 14395 // calling this now to avoid waiting for the server to 14396 // acknowledge the resize; draws without returning to the 14397 // event loop will thus actually work. the server's event 14398 // btw might overrule this and resize it again 14399 recordX11Resize(display, this, w, h); 14400 14401 updateOpenglViewportIfNeeded(w, h); 14402 } 14403 14404 void moveResize (int x, int y, int w, int h) { 14405 if (w < 1) w = 1; 14406 if (h < 1) h = 1; 14407 XMoveResizeWindow(display, window, x, y, w, h); 14408 updateOpenglViewportIfNeeded(w, h); 14409 } 14410 14411 void hideCursor () { 14412 if (curHidden++ == 0) { 14413 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 14414 static const(char)[1] cmbmp = 0; 14415 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 14416 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 14417 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 14418 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 14419 XFreePixmap(display, pm); 14420 } 14421 XDefineCursor(display, window, blankCurPtr); 14422 } 14423 } 14424 14425 void showCursor () { 14426 if (--curHidden == 0) XUndefineCursor(display, window); 14427 } 14428 14429 void warpMouse (int x, int y) { 14430 // here i will send dummy "ignore next mouse motion" event, 14431 // 'cause `XWarpPointer()` sends synthesised mouse motion, 14432 // and we don't need to report it to the user (as warping is 14433 // used when the user needs movement deltas). 14434 //XClientMessageEvent xclient; 14435 XEvent e; 14436 e.xclient.type = EventType.ClientMessage; 14437 e.xclient.window = window; 14438 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14439 e.xclient.format = 32; 14440 e.xclient.data.l[0] = 0; 14441 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 14442 //{ 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]); } 14443 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14444 // now warp pointer... 14445 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 14446 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 14447 // ...and flush 14448 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 14449 XFlush(display); 14450 } 14451 14452 void sendDummyEvent () { 14453 // here i will send dummy event to ping event queue 14454 XEvent e; 14455 e.xclient.type = EventType.ClientMessage; 14456 e.xclient.window = window; 14457 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14458 e.xclient.format = 32; 14459 e.xclient.data.l[0] = 0; 14460 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14461 XFlush(display); 14462 } 14463 14464 void setTitle(string title) { 14465 if (title.ptr is null) title = ""; 14466 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14467 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14468 XTextProperty windowName; 14469 windowName.value = title.ptr; 14470 windowName.encoding = XA_UTF8; //XA_STRING; 14471 windowName.format = 8; 14472 windowName.nitems = cast(uint)title.length; 14473 XSetWMName(display, window, &windowName); 14474 char[1024] namebuf = 0; 14475 auto maxlen = namebuf.length-1; 14476 if (maxlen > title.length) maxlen = title.length; 14477 namebuf[0..maxlen] = title[0..maxlen]; 14478 XStoreName(display, window, namebuf.ptr); 14479 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 14480 flushGui(); // without this OpenGL windows has a LONG delay before changing title 14481 } 14482 14483 string[] getTitles() { 14484 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14485 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14486 XTextProperty textProp; 14487 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 14488 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 14489 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 14490 } else 14491 return []; 14492 } else 14493 return null; 14494 } 14495 14496 string getTitle() { 14497 auto titles = getTitles(); 14498 return titles.length ? titles[0] : null; 14499 } 14500 14501 void setMinSize (int minwidth, int minheight) { 14502 import core.stdc.config : c_long; 14503 if (minwidth < 1) minwidth = 1; 14504 if (minheight < 1) minheight = 1; 14505 XSizeHints sh; 14506 c_long spr; 14507 XGetWMNormalHints(display, window, &sh, &spr); 14508 sh.min_width = minwidth; 14509 sh.min_height = minheight; 14510 sh.flags |= PMinSize; 14511 XSetWMNormalHints(display, window, &sh); 14512 flushGui(); 14513 } 14514 14515 void setMaxSize (int maxwidth, int maxheight) { 14516 import core.stdc.config : c_long; 14517 if (maxwidth < 1) maxwidth = 1; 14518 if (maxheight < 1) maxheight = 1; 14519 XSizeHints sh; 14520 c_long spr; 14521 XGetWMNormalHints(display, window, &sh, &spr); 14522 sh.max_width = maxwidth; 14523 sh.max_height = maxheight; 14524 sh.flags |= PMaxSize; 14525 XSetWMNormalHints(display, window, &sh); 14526 flushGui(); 14527 } 14528 14529 void setResizeGranularity (int granx, int grany) { 14530 import core.stdc.config : c_long; 14531 if (granx < 1) granx = 1; 14532 if (grany < 1) grany = 1; 14533 XSizeHints sh; 14534 c_long spr; 14535 XGetWMNormalHints(display, window, &sh, &spr); 14536 sh.width_inc = granx; 14537 sh.height_inc = grany; 14538 sh.flags |= PResizeInc; 14539 XSetWMNormalHints(display, window, &sh); 14540 flushGui(); 14541 } 14542 14543 void setOpacity (uint opacity) { 14544 arch_ulong o = opacity; 14545 if (opacity == uint.max) 14546 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 14547 else 14548 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 14549 XA_CARDINAL, 32, PropModeReplace, &o, 1); 14550 } 14551 14552 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 14553 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 14554 display = XDisplayConnection.get(); 14555 auto screen = DefaultScreen(display); 14556 14557 bool overrideRedirect = false; 14558 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 14559 overrideRedirect = true; 14560 14561 version(without_opengl) {} 14562 else { 14563 if(opengl == OpenGlOptions.yes) { 14564 GLXFBConfig fbconf = null; 14565 XVisualInfo* vi = null; 14566 bool useLegacy = false; 14567 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 14568 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 14569 int[23] visualAttribs = [ 14570 GLX_X_RENDERABLE , 1/*True*/, 14571 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 14572 GLX_RENDER_TYPE , GLX_RGBA_BIT, 14573 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 14574 GLX_RED_SIZE , 8, 14575 GLX_GREEN_SIZE , 8, 14576 GLX_BLUE_SIZE , 8, 14577 GLX_ALPHA_SIZE , 8, 14578 GLX_DEPTH_SIZE , 24, 14579 GLX_STENCIL_SIZE , 8, 14580 GLX_DOUBLEBUFFER , 1/*True*/, 14581 0/*None*/, 14582 ]; 14583 int fbcount; 14584 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 14585 if (fbcount == 0) { 14586 useLegacy = true; // try to do at least something 14587 } else { 14588 // pick the FB config/visual with the most samples per pixel 14589 int bestidx = -1, bestns = -1; 14590 foreach (int fbi; 0..fbcount) { 14591 int sb, samples; 14592 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 14593 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 14594 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 14595 } 14596 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 14597 fbconf = fbc[bestidx]; 14598 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 14599 XFree(fbc); 14600 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 14601 } 14602 } 14603 if (vi is null || useLegacy) { 14604 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 14605 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 14606 useLegacy = true; 14607 } 14608 if (vi is null) throw new Exception("no open gl visual found"); 14609 14610 XSetWindowAttributes swa; 14611 auto root = RootWindow(display, screen); 14612 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 14613 14614 swa.override_redirect = overrideRedirect; 14615 14616 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14617 0, 0, width, height, 14618 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 14619 14620 // now try to use `glXCreateContextAttribsARB()` if it's here 14621 if (!useLegacy) { 14622 // request fairly advanced context, even with stencil buffer! 14623 int[9] contextAttribs = [ 14624 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 14625 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 14626 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 14627 // for modern context, set "forward compatibility" flag too 14628 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 14629 0/*None*/, 14630 ]; 14631 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 14632 if (glc is null && sdpyOpenGLContextAllowFallback) { 14633 sdpyOpenGLContextVersion = 0; 14634 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14635 } 14636 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 14637 } else { 14638 // fallback to old GLX call 14639 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 14640 sdpyOpenGLContextVersion = 0; 14641 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14642 } 14643 } 14644 // sync to ensure any errors generated are processed 14645 XSync(display, 0/*False*/); 14646 //{ import core.stdc.stdio; printf("ogl is here\n"); } 14647 if(glc is null) 14648 throw new Exception("glc"); 14649 } 14650 } 14651 14652 if(opengl == OpenGlOptions.no) { 14653 14654 XSetWindowAttributes swa; 14655 swa.background_pixel = WhitePixel(display, screen); 14656 swa.border_pixel = BlackPixel(display, screen); 14657 swa.override_redirect = overrideRedirect; 14658 auto root = RootWindow(display, screen); 14659 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 14660 14661 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14662 0, 0, width, height, 14663 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 14664 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 14665 14666 14667 14668 /* 14669 window = XCreateSimpleWindow( 14670 display, 14671 parent is null ? RootWindow(display, screen) : parent.impl.window, 14672 0, 0, // x, y 14673 width, height, 14674 1, // border width 14675 BlackPixel(display, screen), // border 14676 WhitePixel(display, screen)); // background 14677 */ 14678 14679 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 14680 bufferw = width; 14681 bufferh = height; 14682 14683 gc = DefaultGC(display, screen); 14684 14685 // clear out the buffer to get us started... 14686 XSetForeground(display, gc, WhitePixel(display, screen)); 14687 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 14688 XSetForeground(display, gc, BlackPixel(display, screen)); 14689 } 14690 14691 // input context 14692 //TODO: create this only for top-level windows, and reuse that? 14693 populateXic(); 14694 14695 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 14696 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 14697 // window class 14698 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 14699 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 14700 XClassHint klass; 14701 XWMHints wh; 14702 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14703 wh.input = true; 14704 wh.flags |= InputHint; 14705 } 14706 XSizeHints size; 14707 klass.res_name = sdpyWindowClassStr; 14708 klass.res_class = sdpyWindowClassStr; 14709 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 14710 } 14711 14712 setTitle(title); 14713 SimpleWindow.nativeMapping[window] = this; 14714 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 14715 14716 // This gives our window a close button 14717 if (windowType != WindowTypes.eventOnly) { 14718 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 14719 int useAtoms; 14720 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14721 useAtoms = 2; 14722 } else { 14723 useAtoms = 1; 14724 } 14725 assert(useAtoms <= atoms.length); 14726 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 14727 } 14728 14729 // FIXME: windowType and customizationFlags 14730 Atom[8] wsatoms; // here, due to goto 14731 int wmsacount = 0; // here, due to goto 14732 14733 try 14734 final switch(windowType) { 14735 case WindowTypes.normal: 14736 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14737 break; 14738 case WindowTypes.undecorated: 14739 motifHideDecorations(); 14740 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14741 break; 14742 case WindowTypes.eventOnly: 14743 _hidden = true; 14744 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 14745 goto hiddenWindow; 14746 //break; 14747 case WindowTypes.nestedChild: 14748 // handled in XCreateWindow calls 14749 break; 14750 14751 case WindowTypes.dropdownMenu: 14752 motifHideDecorations(); 14753 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 14754 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14755 break; 14756 case WindowTypes.popupMenu: 14757 motifHideDecorations(); 14758 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 14759 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14760 break; 14761 case WindowTypes.notification: 14762 motifHideDecorations(); 14763 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 14764 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14765 break; 14766 case WindowTypes.minimallyWrapped: 14767 assert(0, "don't create a minimallyWrapped thing explicitly!"); 14768 /+ 14769 case WindowTypes.menu: 14770 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14771 motifHideDecorations(); 14772 break; 14773 case WindowTypes.desktop: 14774 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 14775 break; 14776 case WindowTypes.dock: 14777 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 14778 break; 14779 case WindowTypes.toolbar: 14780 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 14781 break; 14782 case WindowTypes.menu: 14783 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14784 break; 14785 case WindowTypes.utility: 14786 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 14787 break; 14788 case WindowTypes.splash: 14789 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 14790 break; 14791 case WindowTypes.dialog: 14792 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 14793 break; 14794 case WindowTypes.tooltip: 14795 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 14796 break; 14797 case WindowTypes.notification: 14798 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 14799 break; 14800 case WindowTypes.combo: 14801 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 14802 break; 14803 case WindowTypes.dnd: 14804 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 14805 break; 14806 +/ 14807 } 14808 catch(Exception e) { 14809 // XInternAtom failed, prolly a WM 14810 // that doesn't support these things 14811 } 14812 14813 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 14814 // the two following flags may be ignored by WM 14815 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 14816 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 14817 14818 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 14819 14820 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 14821 14822 // What would be ideal here is if they only were 14823 // selected if there was actually an event handler 14824 // for them... 14825 14826 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 14827 14828 hiddenWindow: 14829 14830 // set the pid property for lookup later by window managers 14831 // a standard convenience 14832 import core.sys.posix.unistd; 14833 arch_ulong pid = getpid(); 14834 14835 XChangeProperty( 14836 display, 14837 impl.window, 14838 GetAtom!("_NET_WM_PID", true)(display), 14839 XA_CARDINAL, 14840 32 /* bits */, 14841 0 /*PropModeReplace*/, 14842 &pid, 14843 1); 14844 14845 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 14846 if(parent is null) assert(0); 14847 XChangeProperty( 14848 display, 14849 impl.window, 14850 GetAtom!("WM_TRANSIENT_FOR", true)(display), 14851 XA_WINDOW, 14852 32 /* bits */, 14853 0 /*PropModeReplace*/, 14854 &parent.impl.window, 14855 1); 14856 14857 } 14858 14859 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 14860 XMapWindow(display, window); 14861 } else { 14862 _hidden = true; 14863 } 14864 } 14865 14866 void populateXic() { 14867 if (XDisplayConnection.xim !is null) { 14868 xic = XCreateIC(XDisplayConnection.xim, 14869 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 14870 /*XNClientWindow*/"clientWindow".ptr, window, 14871 /*XNFocusWindow*/"focusWindow".ptr, window, 14872 null); 14873 if (xic is null) { 14874 import core.stdc.stdio : stderr, fprintf; 14875 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 14876 } 14877 } 14878 } 14879 14880 void selectDefaultInput(bool forceIncludeMouseMotion) { 14881 auto mask = EventMask.ExposureMask | 14882 EventMask.KeyPressMask | 14883 EventMask.KeyReleaseMask | 14884 EventMask.PropertyChangeMask | 14885 EventMask.FocusChangeMask | 14886 EventMask.StructureNotifyMask | 14887 EventMask.SubstructureNotifyMask | 14888 EventMask.VisibilityChangeMask 14889 | EventMask.ButtonPressMask 14890 | EventMask.ButtonReleaseMask 14891 ; 14892 14893 // xshm is our shortcut for local connections 14894 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 14895 mask |= EventMask.PointerMotionMask; 14896 else 14897 mask |= EventMask.ButtonMotionMask; 14898 14899 XSelectInput(display, window, mask); 14900 } 14901 14902 14903 void setNetWMWindowType(Atom type) { 14904 Atom[2] atoms; 14905 14906 atoms[0] = type; 14907 // generic fallback 14908 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 14909 14910 XChangeProperty( 14911 display, 14912 impl.window, 14913 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 14914 XA_ATOM, 14915 32 /* bits */, 14916 0 /*PropModeReplace*/, 14917 atoms.ptr, 14918 cast(int) atoms.length); 14919 } 14920 14921 void motifHideDecorations(bool hide = true) { 14922 MwmHints hints; 14923 hints.flags = MWM_HINTS_DECORATIONS; 14924 hints.decorations = hide ? 0 : 1; 14925 14926 XChangeProperty( 14927 display, 14928 impl.window, 14929 GetAtom!"_MOTIF_WM_HINTS"(display), 14930 GetAtom!"_MOTIF_WM_HINTS"(display), 14931 32 /* bits */, 14932 0 /*PropModeReplace*/, 14933 &hints, 14934 hints.sizeof / 4); 14935 } 14936 14937 /*k8: unused 14938 void createOpenGlContext() { 14939 14940 } 14941 */ 14942 14943 void closeWindow() { 14944 // I can't close this or a child window closing will 14945 // break events for everyone. So I'm just leaking it right 14946 // now and that is probably perfectly fine... 14947 version(none) 14948 if (customEventFDRead != -1) { 14949 import core.sys.posix.unistd : close; 14950 auto same = customEventFDRead == customEventFDWrite; 14951 14952 close(customEventFDRead); 14953 if(!same) 14954 close(customEventFDWrite); 14955 customEventFDRead = -1; 14956 customEventFDWrite = -1; 14957 } 14958 14959 if(glc !is null) { 14960 glXDestroyContext(display, glc); 14961 glc = null; 14962 } 14963 14964 if(buffer) 14965 XFreePixmap(display, buffer); 14966 bufferw = bufferh = 0; 14967 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 14968 XDestroyWindow(display, window); 14969 XFlush(display); 14970 } 14971 14972 void dispose() { 14973 } 14974 14975 bool destroyed = false; 14976 } 14977 14978 bool insideXEventLoop; 14979 } 14980 14981 version(X11) { 14982 14983 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 14984 14985 private class ResizeEvent { 14986 int width, height; 14987 } 14988 14989 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 14990 if(win.windowType == WindowTypes.minimallyWrapped) 14991 return; 14992 14993 if(win.pendingResizeEvent is null) { 14994 win.pendingResizeEvent = new ResizeEvent(); 14995 win.addEventListener((ResizeEvent re) { 14996 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 14997 }); 14998 } 14999 win.pendingResizeEvent.width = width; 15000 win.pendingResizeEvent.height = height; 15001 if(!win.eventQueued!ResizeEvent) { 15002 win.postEvent(win.pendingResizeEvent); 15003 } 15004 } 15005 15006 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 15007 if(win.windowType == WindowTypes.minimallyWrapped) 15008 return; 15009 if(win.closed) 15010 return; 15011 15012 if(width != win.width || height != win.height) { 15013 15014 // writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 15015 win._width = width; 15016 win._height = height; 15017 15018 if(win.openglMode == OpenGlOptions.no) { 15019 // FIXME: could this be more efficient? 15020 15021 if (win.bufferw < width || win.bufferh < height) { 15022 //{ 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); } 15023 // grow the internal buffer to match the window... 15024 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 15025 { 15026 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15027 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15028 scope(exit) XFreeGC(win.display, xgc); 15029 XSetClipMask(win.display, xgc, None); 15030 XSetForeground(win.display, xgc, 0); 15031 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 15032 } 15033 XCopyArea(display, 15034 cast(Drawable) win.buffer, 15035 cast(Drawable) newPixmap, 15036 win.gc, 0, 0, 15037 win.bufferw < width ? win.bufferw : win.width, 15038 win.bufferh < height ? win.bufferh : win.height, 15039 0, 0); 15040 15041 XFreePixmap(display, win.buffer); 15042 win.buffer = newPixmap; 15043 win.bufferw = width; 15044 win.bufferh = height; 15045 } 15046 15047 // clear unused parts of the buffer 15048 if (win.bufferw > width || win.bufferh > height) { 15049 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15050 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15051 scope(exit) XFreeGC(win.display, xgc); 15052 XSetClipMask(win.display, xgc, None); 15053 XSetForeground(win.display, xgc, 0); 15054 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 15055 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 15056 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 15057 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 15058 } 15059 15060 } 15061 15062 win.updateOpenglViewportIfNeeded(width, height); 15063 15064 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 15065 15066 if(win.resizability != Resizability.automaticallyScaleIfPossible) 15067 if(win.windowResized !is null) { 15068 XUnlockDisplay(display); 15069 scope(exit) XLockDisplay(display); 15070 win.windowResized(width, height); 15071 } 15072 } 15073 } 15074 15075 15076 /// Platform-specific, you might use it when doing a custom event loop. 15077 bool doXNextEvent(Display* display) { 15078 bool done; 15079 XEvent e; 15080 XNextEvent(display, &e); 15081 version(sddddd) { 15082 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15083 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 15084 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 15085 } 15086 } 15087 15088 // filter out compose events 15089 if (XFilterEvent(&e, None)) { 15090 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 15091 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 15092 return false; 15093 } 15094 // process keyboard mapping changes 15095 if (e.type == EventType.KeymapNotify) { 15096 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 15097 XRefreshKeyboardMapping(&e.xmapping); 15098 return false; 15099 } 15100 15101 version(with_eventloop) 15102 import arsd.eventloop; 15103 15104 if(SimpleWindow.handleNativeGlobalEvent !is null) { 15105 // see windows impl's comments 15106 XUnlockDisplay(display); 15107 scope(exit) XLockDisplay(display); 15108 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 15109 if(ret == 0) 15110 return done; 15111 } 15112 15113 15114 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15115 if(win.getNativeEventHandler !is null) { 15116 XUnlockDisplay(display); 15117 scope(exit) XLockDisplay(display); 15118 auto ret = win.getNativeEventHandler()(e); 15119 if(ret == 0) 15120 return done; 15121 } 15122 } 15123 15124 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 15125 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 15126 // we get this because of the RRScreenChangeNotifyMask 15127 15128 // this isn't actually an ideal way to do it since it wastes time 15129 // but meh it is simple and it works. 15130 win.actualDpiLoadAttempted = false; 15131 SimpleWindow.xRandrInfoLoadAttemped = false; 15132 win.updateActualDpi(); // trigger a reload 15133 } 15134 } 15135 15136 switch(e.type) { 15137 case EventType.SelectionClear: 15138 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15139 // FIXME so it is supposed to finish any in progress transfers... but idk... 15140 // writeln("SelectionClear"); 15141 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15142 } 15143 break; 15144 case EventType.SelectionRequest: 15145 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15146 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15147 // printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15148 XUnlockDisplay(display); 15149 scope(exit) XLockDisplay(display); 15150 (*ssh).handleRequest(e); 15151 } 15152 break; 15153 case EventType.PropertyNotify: 15154 // printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15155 15156 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15157 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15158 ssh.sendMoreIncr(&e.xproperty); 15159 } 15160 15161 15162 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15163 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15164 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15165 Atom target; 15166 int format; 15167 arch_ulong bytesafter, length; 15168 void* value; 15169 15170 ubyte[] s; 15171 Atom targetToKeep; 15172 15173 XGetWindowProperty( 15174 e.xproperty.display, 15175 e.xproperty.window, 15176 e.xproperty.atom, 15177 0, 15178 100000 /* length */, 15179 true, /* erase it to signal we got it and want more */ 15180 0 /*AnyPropertyType*/, 15181 &target, &format, &length, &bytesafter, &value); 15182 15183 if(!targetToKeep) 15184 targetToKeep = target; 15185 15186 auto id = (cast(ubyte*) value)[0 .. length]; 15187 15188 handler.handleIncrData(targetToKeep, id); 15189 15190 XFree(value); 15191 } 15192 } 15193 break; 15194 case EventType.SelectionNotify: 15195 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15196 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15197 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15198 XUnlockDisplay(display); 15199 scope(exit) XLockDisplay(display); 15200 handler.handleData(None, null); 15201 } else { 15202 Atom target; 15203 int format; 15204 arch_ulong bytesafter, length; 15205 void* value; 15206 XGetWindowProperty( 15207 e.xselection.display, 15208 e.xselection.requestor, 15209 e.xselection.property, 15210 0, 15211 100000 /* length */, 15212 //false, /* don't erase it */ 15213 true, /* do erase it lol */ 15214 0 /*AnyPropertyType*/, 15215 &target, &format, &length, &bytesafter, &value); 15216 15217 // FIXME: I don't have to copy it now since it is in char[] instead of string 15218 15219 { 15220 XUnlockDisplay(display); 15221 scope(exit) XLockDisplay(display); 15222 15223 if(target == XA_ATOM) { 15224 // initial request, see what they are able to work with and request the best one 15225 // we can handle, if available 15226 15227 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15228 Atom best = handler.findBestFormat(answer); 15229 15230 /+ 15231 writeln("got ", answer); 15232 foreach(a; answer) 15233 printf("%s\n", XGetAtomName(display, a)); 15234 writeln("best ", best); 15235 +/ 15236 15237 if(best != None) { 15238 // actually request the best format 15239 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15240 } 15241 } else if(target == GetAtom!"INCR"(display)) { 15242 // incremental 15243 15244 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 15245 15246 // signal the sending program that we see 15247 // the incr and are ready to receive more. 15248 XDeleteProperty( 15249 e.xselection.display, 15250 e.xselection.requestor, 15251 e.xselection.property); 15252 } else { 15253 // unsupported type... maybe, forward 15254 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 15255 } 15256 } 15257 XFree(value); 15258 /* 15259 XDeleteProperty( 15260 e.xselection.display, 15261 e.xselection.requestor, 15262 e.xselection.property); 15263 */ 15264 } 15265 } 15266 break; 15267 case EventType.ConfigureNotify: 15268 auto event = e.xconfigure; 15269 if(auto win = event.window in SimpleWindow.nativeMapping) { 15270 if(win.windowType == WindowTypes.minimallyWrapped) 15271 break; 15272 //version(sdddd) { writeln(" w=", event.width, "; h=", event.height); } 15273 15274 /+ 15275 The ICCCM says window managers must send a synthetic event when the window 15276 is moved but NOT when it is resized. In the resize case, an event is sent 15277 with position (0, 0) which can be wrong and break the dpi calculations. 15278 15279 So we only consider the synthetic events from the WM and otherwise 15280 need to wait for some other event to get the position which... sucks. 15281 15282 I'd rather not have windows changing their layout on mouse motion after 15283 switching monitors... might be forced to but for now just ignoring it. 15284 15285 Easiest way to switch monitors without sending a size position is by 15286 maximize or fullscreen in a setup like mine, but on most setups those 15287 work on the monitor it is already living on, so it should be ok most the 15288 time. 15289 +/ 15290 if(event.send_event) { 15291 win.screenPositionKnown = true; 15292 win.screenPositionX = event.x; 15293 win.screenPositionY = event.y; 15294 win.updateActualDpi(); 15295 } 15296 15297 win.updateIMEPopupLocation(); 15298 recordX11ResizeAsync(display, *win, event.width, event.height); 15299 } 15300 break; 15301 case EventType.Expose: 15302 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 15303 if(win.windowType == WindowTypes.minimallyWrapped) 15304 break; 15305 // if it is closing from a popup menu, it can get 15306 // an Expose event right by the end and trigger a 15307 // BadDrawable error ... we'll just check 15308 // closed to handle that. 15309 if((*win).closed) break; 15310 if((*win).openglMode == OpenGlOptions.no) { 15311 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 15312 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 15313 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); 15314 } else { 15315 // need to redraw the scene somehow 15316 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 15317 XUnlockDisplay(display); 15318 scope(exit) XLockDisplay(display); 15319 version(without_opengl) {} else 15320 win.redrawOpenGlSceneSoon(); 15321 } 15322 } 15323 } 15324 break; 15325 case EventType.FocusIn: 15326 case EventType.FocusOut: 15327 15328 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15329 /+ 15330 15331 void info(string detail) { 15332 string s; 15333 // import std.conv; 15334 // import std.datetime; 15335 s ~= to!string(Clock.currTime); 15336 s ~= " "; 15337 s ~= e.type == EventType.FocusIn ? "in " : "out"; 15338 s ~= " "; 15339 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 15340 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 15341 s ~= detail; 15342 s ~= " "; 15343 15344 sdpyPrintDebugString(s); 15345 15346 } 15347 15348 switch(e.xfocus.detail) { 15349 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 15350 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 15351 case NotifyDetail.NotifyInferior: info("Inferior"); break; 15352 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 15353 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 15354 case NotifyDetail.NotifyPointer: info("pointer"); break; 15355 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 15356 case NotifyDetail.NotifyDetailNone: info("none"); break; 15357 default: 15358 15359 } 15360 +/ 15361 15362 15363 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 15364 break; // just ignore these they seem irrelevant 15365 15366 auto old = win._focused; 15367 win._focused = e.type == EventType.FocusIn; 15368 15369 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 15370 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 15371 win._focused = true; 15372 15373 if(win.demandingAttention) 15374 demandAttention(*win, false); 15375 15376 win.updateIMEFocused(); 15377 15378 if(old != win._focused && win.onFocusChange) { 15379 XUnlockDisplay(display); 15380 scope(exit) XLockDisplay(display); 15381 win.onFocusChange(win._focused); 15382 } 15383 } 15384 break; 15385 case EventType.VisibilityNotify: 15386 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15387 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 15388 if (win.visibilityChanged !is null) { 15389 XUnlockDisplay(display); 15390 scope(exit) XLockDisplay(display); 15391 win.visibilityChanged(false); 15392 } 15393 } else { 15394 if (win.visibilityChanged !is null) { 15395 XUnlockDisplay(display); 15396 scope(exit) XLockDisplay(display); 15397 win.visibilityChanged(true); 15398 } 15399 } 15400 } 15401 break; 15402 case EventType.ClientMessage: 15403 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 15404 // "ignore next mouse motion" event, increment ignore counter for teh window 15405 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15406 ++(*win).warpEventCount; 15407 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 15408 } else { 15409 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 15410 } 15411 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 15412 // user clicked the close button on the window manager 15413 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15414 XUnlockDisplay(display); 15415 scope(exit) XLockDisplay(display); 15416 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 15417 } 15418 15419 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 15420 // writeln("HAPPENED"); 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 15426 auto setTo = *win; 15427 15428 if(win.setRequestedInputFocus !is null) { 15429 auto s = win.setRequestedInputFocus(); 15430 if(s !is null) { 15431 setTo = s; 15432 } 15433 } 15434 15435 assert(setTo !is null); 15436 15437 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 15438 15439 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 15440 } 15441 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 15442 foreach(nai; NotificationAreaIcon.activeIcons) 15443 nai.newManager(); 15444 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15445 15446 bool xDragWindow = true; 15447 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 15448 //XDefineCursor(display, xDragWindow.impl.window, 15449 //writeln("XdndStatus ", e.xclient.data.l); 15450 } 15451 if(auto dh = win.dropHandler) { 15452 15453 static Atom[3] xFormatsBuffer; 15454 static Atom[] xFormats; 15455 15456 void resetXFormats() { 15457 xFormatsBuffer[] = 0; 15458 xFormats = xFormatsBuffer[]; 15459 } 15460 15461 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 15462 // on Windows it is supposed to return the effect you actually do FIXME 15463 15464 auto sourceWindow = e.xclient.data.l[0]; 15465 15466 xFormatsBuffer[0] = e.xclient.data.l[2]; 15467 xFormatsBuffer[1] = e.xclient.data.l[3]; 15468 xFormatsBuffer[2] = e.xclient.data.l[4]; 15469 15470 if(e.xclient.data.l[1] & 1) { 15471 // can just grab it all but like we don't necessarily need them... 15472 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 15473 } else { 15474 int len; 15475 foreach(fmt; xFormatsBuffer) 15476 if(fmt) len++; 15477 xFormats = xFormatsBuffer[0 .. len]; 15478 } 15479 15480 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 15481 15482 dh.dragEnter(&pkg); 15483 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 15484 15485 auto pack = e.xclient.data.l[2]; 15486 15487 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 15488 15489 15490 XClientMessageEvent xclient; 15491 15492 xclient.type = EventType.ClientMessage; 15493 xclient.window = e.xclient.data.l[0]; 15494 xclient.message_type = GetAtom!"XdndStatus"(display); 15495 xclient.format = 32; 15496 xclient.data.l[0] = win.impl.window; 15497 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 15498 auto r = result.consistentWithin; 15499 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 15500 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 15501 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 15502 15503 XSendEvent( 15504 display, 15505 e.xclient.data.l[0], 15506 false, 15507 EventMask.NoEventMask, 15508 cast(XEvent*) &xclient 15509 ); 15510 15511 15512 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 15513 //writeln("XdndLeave"); 15514 // drop cancelled. 15515 // data.l[0] is the source window 15516 dh.dragLeave(); 15517 15518 resetXFormats(); 15519 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 15520 // drop happening, should fetch data, then send finished 15521 // writeln("XdndDrop"); 15522 15523 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 15524 15525 dh.drop(&pkg); 15526 15527 resetXFormats(); 15528 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 15529 // writeln("XdndFinished"); 15530 15531 dh.finish(); 15532 } 15533 15534 } 15535 } 15536 break; 15537 case EventType.MapNotify: 15538 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 15539 (*win)._visible = true; 15540 if (!(*win)._visibleForTheFirstTimeCalled) { 15541 (*win)._visibleForTheFirstTimeCalled = true; 15542 if ((*win).visibleForTheFirstTime !is null) { 15543 XUnlockDisplay(display); 15544 scope(exit) XLockDisplay(display); 15545 (*win).visibleForTheFirstTime(); 15546 } 15547 } 15548 if ((*win).visibilityChanged !is null) { 15549 XUnlockDisplay(display); 15550 scope(exit) XLockDisplay(display); 15551 (*win).visibilityChanged(true); 15552 } 15553 } 15554 break; 15555 case EventType.UnmapNotify: 15556 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 15557 win._visible = false; 15558 if (win.visibilityChanged !is null) { 15559 XUnlockDisplay(display); 15560 scope(exit) XLockDisplay(display); 15561 win.visibilityChanged(false); 15562 } 15563 } 15564 break; 15565 case EventType.DestroyNotify: 15566 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 15567 if(win.destroyed) 15568 break; // might get a notification both for itself and from its parent 15569 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 15570 win._closed = true; // just in case 15571 win.destroyed = true; 15572 if (win.xic !is null) { 15573 XDestroyIC(win.xic); 15574 win.xic = null; // just in case 15575 } 15576 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 15577 bool anyImportant = false; 15578 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 15579 if(w.beingOpenKeepsAppOpen) { 15580 anyImportant = true; 15581 break; 15582 } 15583 if(!anyImportant) { 15584 EventLoop.quitApplication(); 15585 done = true; 15586 } 15587 } 15588 auto window = e.xdestroywindow.window; 15589 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 15590 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 15591 15592 version(with_eventloop) { 15593 if(done) exit(); 15594 } 15595 break; 15596 15597 case EventType.MotionNotify: 15598 MouseEvent mouse; 15599 auto event = e.xmotion; 15600 15601 mouse.type = MouseEventType.motion; 15602 mouse.x = event.x; 15603 mouse.y = event.y; 15604 mouse.modifierState = event.state; 15605 15606 mouse.timestamp = event.time; 15607 15608 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 15609 mouse.window = *win; 15610 if (win.warpEventCount > 0) { 15611 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 15612 --(*win).warpEventCount; 15613 (*win).mdx(mouse); // so deltas will be correctly updated 15614 } else { 15615 win.warpEventCount = 0; // just in case 15616 (*win).mdx(mouse); 15617 if((*win).handleMouseEvent) { 15618 XUnlockDisplay(display); 15619 scope(exit) XLockDisplay(display); 15620 (*win).handleMouseEvent(mouse); 15621 } 15622 } 15623 } 15624 15625 version(with_eventloop) 15626 send(mouse); 15627 break; 15628 case EventType.ButtonPress: 15629 case EventType.ButtonRelease: 15630 MouseEvent mouse; 15631 auto event = e.xbutton; 15632 15633 mouse.timestamp = event.time; 15634 15635 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 15636 mouse.x = event.x; 15637 mouse.y = event.y; 15638 15639 static Time lastMouseDownTime = 0; 15640 static int lastMouseDownButton = -1; 15641 15642 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 15643 if(e.type == EventType.ButtonPress) { 15644 lastMouseDownTime = event.time; 15645 lastMouseDownButton = event.button; 15646 } 15647 15648 switch(event.button) { 15649 case 1: mouse.button = MouseButton.left; break; // left 15650 case 2: mouse.button = MouseButton.middle; break; // middle 15651 case 3: mouse.button = MouseButton.right; break; // right 15652 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 15653 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 15654 case 6: break; // idk 15655 case 7: break; // idk 15656 case 8: mouse.button = MouseButton.backButton; break; 15657 case 9: mouse.button = MouseButton.forwardButton; break; 15658 default: 15659 } 15660 15661 // FIXME: double check this 15662 mouse.modifierState = event.state; 15663 15664 //mouse.modifierState = event.detail; 15665 15666 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 15667 mouse.window = *win; 15668 (*win).mdx(mouse); 15669 if((*win).handleMouseEvent) { 15670 XUnlockDisplay(display); 15671 scope(exit) XLockDisplay(display); 15672 (*win).handleMouseEvent(mouse); 15673 } 15674 } 15675 version(with_eventloop) 15676 send(mouse); 15677 break; 15678 15679 case EventType.KeyPress: 15680 case EventType.KeyRelease: 15681 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 15682 KeyEvent ke; 15683 ke.pressed = e.type == EventType.KeyPress; 15684 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 15685 15686 auto sym = XKeycodeToKeysym( 15687 XDisplayConnection.get(), 15688 e.xkey.keycode, 15689 0); 15690 15691 ke.key = cast(Key) sym;//e.xkey.keycode; 15692 15693 ke.modifierState = e.xkey.state; 15694 15695 // writefln("%x", sym); 15696 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 15697 int charbuflen = 0; // return value of XwcLookupString 15698 if (ke.pressed) { 15699 auto win = e.xkey.window in SimpleWindow.nativeMapping; 15700 if (win !is null && win.xic !is null) { 15701 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 15702 Status status; 15703 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 15704 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 15705 } else { 15706 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 15707 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 15708 char[16] buffer; 15709 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 15710 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 15711 } 15712 } 15713 15714 // if there's no char, subst one 15715 if (charbuflen == 0) { 15716 switch (sym) { 15717 case 0xff09: charbuf[charbuflen++] = '\t'; break; 15718 case 0xff8d: // keypad enter 15719 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 15720 default : // ignore 15721 } 15722 } 15723 15724 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 15725 ke.window = *win; 15726 15727 15728 if(win.inputProxy) 15729 win = &win.inputProxy; 15730 15731 // char events are separate since they are on Windows too 15732 // also, xcompose can generate long char sequences 15733 // don't send char events if Meta and/or Hyper is pressed 15734 // TODO: ctrl+char should only send control chars; not yet 15735 if ((e.xkey.state&ModifierState.ctrl) != 0) { 15736 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 15737 } 15738 15739 dchar[32] charsComingBuffer; 15740 int charsComingPosition; 15741 dchar[] charsComing = charsComingBuffer[]; 15742 15743 if (ke.pressed && charbuflen > 0) { 15744 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 15745 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 15746 if(charsComingPosition >= charsComing.length) 15747 charsComing.length = charsComingPosition + 8; 15748 15749 charsComing[charsComingPosition++] = ch; 15750 } 15751 15752 charsComing = charsComing[0 .. charsComingPosition]; 15753 } else { 15754 charsComing = null; 15755 } 15756 15757 ke.charsPossible = charsComing; 15758 15759 if (win.handleKeyEvent) { 15760 XUnlockDisplay(display); 15761 scope(exit) XLockDisplay(display); 15762 win.handleKeyEvent(ke); 15763 } 15764 15765 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 15766 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 15767 XUnlockDisplay(display); 15768 scope(exit) XLockDisplay(display); 15769 foreach(ch; charsComing) 15770 win.handleCharEvent(ch); 15771 } 15772 } 15773 15774 version(with_eventloop) 15775 send(ke); 15776 break; 15777 default: 15778 } 15779 15780 return done; 15781 } 15782 } 15783 15784 /* *************************************** */ 15785 /* Done with simpledisplay stuff */ 15786 /* *************************************** */ 15787 15788 // Necessary C library bindings follow 15789 version(Windows) {} else 15790 version(X11) { 15791 15792 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 15793 15794 // X11 bindings needed here 15795 /* 15796 A little of this is from the bindings project on 15797 D Source and some of it is copy/paste from the C 15798 header. 15799 15800 The DSource listing consistently used D's long 15801 where C used long. That's wrong - C long is 32 bit, so 15802 it should be int in D. I changed that here. 15803 15804 Note: 15805 This isn't complete, just took what I needed for myself. 15806 */ 15807 15808 import core.stdc.stddef : wchar_t; 15809 15810 interface XLib { 15811 extern(C) nothrow @nogc { 15812 char* XResourceManagerString(Display*); 15813 void XrmInitialize(); 15814 XrmDatabase XrmGetStringDatabase(char* data); 15815 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 15816 15817 Cursor XCreateFontCursor(Display*, uint shape); 15818 int XDefineCursor(Display* display, Window w, Cursor cursor); 15819 int XUndefineCursor(Display* display, Window w); 15820 15821 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 15822 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 15823 int XFreeCursor(Display* display, Cursor cursor); 15824 15825 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 15826 15827 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 15828 15829 XVaNestedList XVaCreateNestedList(int unused, ...); 15830 15831 char *XKeysymToString(KeySym keysym); 15832 KeySym XKeycodeToKeysym( 15833 Display* /* display */, 15834 KeyCode /* keycode */, 15835 int /* index */ 15836 ); 15837 15838 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 15839 15840 int XFree(void*); 15841 int XDeleteProperty(Display *display, Window w, Atom property); 15842 15843 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, scope const void *data, int nelements); 15844 15845 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 15846 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 15847 *actual_type_return, int *actual_format_return, arch_ulong 15848 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 15849 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 15850 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 15851 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 15852 15853 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 15854 15855 Window XGetSelectionOwner(Display *display, Atom selection); 15856 15857 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 15858 15859 char** XListFonts(Display*, const char*, int, int*); 15860 void XFreeFontNames(char**); 15861 15862 Display* XOpenDisplay(const char*); 15863 int XCloseDisplay(Display*); 15864 15865 int function() XSynchronize(Display*, bool); 15866 int function() XSetAfterFunction(Display*, int function() proc); 15867 15868 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 15869 15870 Bool XSupportsLocale(); 15871 char* XSetLocaleModifiers(const(char)* modifier_list); 15872 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 15873 Status XCloseOM(XOM om); 15874 15875 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 15876 Status XCloseIM(XIM im); 15877 15878 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 15879 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 15880 Display* XDisplayOfIM(XIM im); 15881 char* XLocaleOfIM(XIM im); 15882 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 15883 void XDestroyIC(XIC ic); 15884 void XSetICFocus(XIC ic); 15885 void XUnsetICFocus(XIC ic); 15886 //wchar_t* XwcResetIC(XIC ic); 15887 char* XmbResetIC(XIC ic); 15888 char* Xutf8ResetIC(XIC ic); 15889 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 15890 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 15891 XIM XIMOfIC(XIC ic); 15892 15893 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 15894 15895 15896 XFontStruct *XLoadQueryFont(Display *display, scope const char *name); 15897 int XFreeFont(Display *display, XFontStruct *font_struct); 15898 int XSetFont(Display* display, GC gc, Font font); 15899 int XTextWidth(XFontStruct*, scope const char*, int); 15900 15901 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 15902 int XSetDashes(Display *display, GC gc, int dash_offset, scope const byte* dash_list, int n); 15903 15904 Window XCreateSimpleWindow( 15905 Display* /* display */, 15906 Window /* parent */, 15907 int /* x */, 15908 int /* y */, 15909 uint /* width */, 15910 uint /* height */, 15911 uint /* border_width */, 15912 uint /* border */, 15913 uint /* background */ 15914 ); 15915 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); 15916 15917 int XReparentWindow(Display*, Window, Window, int, int); 15918 int XClearWindow(Display*, Window); 15919 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 15920 int XMoveWindow(Display*, Window, int, int); 15921 int XResizeWindow(Display *display, Window w, uint width, uint height); 15922 15923 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 15924 15925 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 15926 15927 XImage *XCreateImage( 15928 Display* /* display */, 15929 Visual* /* visual */, 15930 uint /* depth */, 15931 int /* format */, 15932 int /* offset */, 15933 ubyte* /* data */, 15934 uint /* width */, 15935 uint /* height */, 15936 int /* bitmap_pad */, 15937 int /* bytes_per_line */ 15938 ); 15939 15940 Status XInitImage (XImage* image); 15941 15942 Atom XInternAtom( 15943 Display* /* display */, 15944 const char* /* atom_name */, 15945 Bool /* only_if_exists */ 15946 ); 15947 15948 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 15949 char* XGetAtomName(Display*, Atom); 15950 Status XGetAtomNames(Display*, Atom*, int count, char**); 15951 15952 int XPutImage( 15953 Display* /* display */, 15954 Drawable /* d */, 15955 GC /* gc */, 15956 XImage* /* image */, 15957 int /* src_x */, 15958 int /* src_y */, 15959 int /* dest_x */, 15960 int /* dest_y */, 15961 uint /* width */, 15962 uint /* height */ 15963 ); 15964 15965 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 15966 15967 15968 int XDestroyWindow( 15969 Display* /* display */, 15970 Window /* w */ 15971 ); 15972 15973 int XDestroyImage(XImage*); 15974 15975 int XSelectInput( 15976 Display* /* display */, 15977 Window /* w */, 15978 EventMask /* event_mask */ 15979 ); 15980 15981 int XMapWindow( 15982 Display* /* display */, 15983 Window /* w */ 15984 ); 15985 15986 Status XIconifyWindow(Display*, Window, int); 15987 int XMapRaised(Display*, Window); 15988 int XMapSubwindows(Display*, Window); 15989 15990 int XNextEvent( 15991 Display* /* display */, 15992 XEvent* /* event_return */ 15993 ); 15994 15995 int XMaskEvent(Display*, arch_long, XEvent*); 15996 15997 Bool XFilterEvent(XEvent *event, Window window); 15998 int XRefreshKeyboardMapping(XMappingEvent *event_map); 15999 16000 Status XSetWMProtocols( 16001 Display* /* display */, 16002 Window /* w */, 16003 Atom* /* protocols */, 16004 int /* count */ 16005 ); 16006 16007 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 16008 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 16009 16010 16011 Status XInitThreads(); 16012 void XLockDisplay (Display* display); 16013 void XUnlockDisplay (Display* display); 16014 16015 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 16016 16017 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 16018 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 16019 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 16020 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 16021 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 16022 16023 16024 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 16025 int XDrawString(Display*, Drawable, GC, int, int, scope const char*, int); 16026 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 16027 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 16028 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16029 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 16030 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 16031 int XDrawPoint(Display*, Drawable, GC, int, int); 16032 int XSetForeground(Display*, GC, uint); 16033 int XSetBackground(Display*, GC, uint); 16034 16035 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 16036 void XFreeFontSet(Display*, XFontSet); 16037 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, scope const char*, int); 16038 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 16039 16040 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 16041 16042 16043 //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); 16044 16045 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 16046 int XSetFunction(Display*, GC, int); 16047 16048 GC XCreateGC(Display*, Drawable, uint, void*); 16049 int XCopyGC(Display*, GC, uint, GC); 16050 int XFreeGC(Display*, GC); 16051 16052 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 16053 bool XCheckMaskEvent(Display*, int, XEvent*); 16054 16055 int XPending(Display*); 16056 int XEventsQueued(Display* display, int mode); 16057 16058 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 16059 int XFreePixmap(Display*, Pixmap); 16060 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 16061 int XFlush(Display*); 16062 int XBell(Display*, int); 16063 int XSync(Display*, bool); 16064 16065 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 16066 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 16067 16068 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 16069 int XUngrabKeyboard(Display*, Time); 16070 16071 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 16072 16073 KeySym XStringToKeysym(const char *string); 16074 16075 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 16076 16077 Window XDefaultRootWindow(Display*); 16078 16079 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); 16080 16081 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 16082 16083 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 16084 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 16085 16086 Status XAllocColor(Display*, Colormap, XColor*); 16087 16088 int XWithdrawWindow(Display*, Window, int); 16089 int XUnmapWindow(Display*, Window); 16090 int XLowerWindow(Display*, Window); 16091 int XRaiseWindow(Display*, Window); 16092 16093 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); 16094 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); 16095 16096 int XGetInputFocus(Display*, Window*, int*); 16097 int XSetInputFocus(Display*, Window, int, Time); 16098 16099 XErrorHandler XSetErrorHandler(XErrorHandler); 16100 16101 int XGetErrorText(Display*, int, char*, int); 16102 16103 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 16104 16105 16106 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); 16107 int XUngrabPointer(Display *display, Time time); 16108 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 16109 16110 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 16111 16112 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 16113 int XSetClipMask(Display*, GC, Pixmap); 16114 int XSetClipOrigin(Display*, GC, int, int); 16115 16116 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 16117 16118 void XSetWMName(Display*, Window, XTextProperty*); 16119 Status XGetWMName(Display*, Window, XTextProperty*); 16120 int XStoreName(Display* display, Window w, const(char)* window_name); 16121 16122 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 16123 16124 } 16125 } 16126 16127 interface Xext { 16128 extern(C) nothrow @nogc { 16129 Status XShmAttach(Display*, XShmSegmentInfo*); 16130 Status XShmDetach(Display*, XShmSegmentInfo*); 16131 Status XShmPutImage( 16132 Display* /* dpy */, 16133 Drawable /* d */, 16134 GC /* gc */, 16135 XImage* /* image */, 16136 int /* src_x */, 16137 int /* src_y */, 16138 int /* dst_x */, 16139 int /* dst_y */, 16140 uint /* src_width */, 16141 uint /* src_height */, 16142 Bool /* send_event */ 16143 ); 16144 16145 Status XShmQueryExtension(Display*); 16146 16147 XImage *XShmCreateImage( 16148 Display* /* dpy */, 16149 Visual* /* visual */, 16150 uint /* depth */, 16151 int /* format */, 16152 char* /* data */, 16153 XShmSegmentInfo* /* shminfo */, 16154 uint /* width */, 16155 uint /* height */ 16156 ); 16157 16158 Pixmap XShmCreatePixmap( 16159 Display* /* dpy */, 16160 Drawable /* d */, 16161 char* /* data */, 16162 XShmSegmentInfo* /* shminfo */, 16163 uint /* width */, 16164 uint /* height */, 16165 uint /* depth */ 16166 ); 16167 16168 } 16169 } 16170 16171 // this requires -lXpm 16172 //int XpmCreatePixmapFromData(Display*, Drawable, scope const char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16173 16174 16175 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16176 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16177 shared static this() { 16178 xlib.loadDynamicLibrary(); 16179 xext.loadDynamicLibrary(); 16180 } 16181 16182 16183 extern(C) nothrow @nogc { 16184 16185 alias XrmDatabase = void*; 16186 struct XrmValue { 16187 uint size; 16188 void* addr; 16189 } 16190 16191 struct XVisualInfo { 16192 Visual* visual; 16193 VisualID visualid; 16194 int screen; 16195 uint depth; 16196 int c_class; 16197 c_ulong red_mask; 16198 c_ulong green_mask; 16199 c_ulong blue_mask; 16200 int colormap_size; 16201 int bits_per_rgb; 16202 } 16203 16204 enum VisualNoMask= 0x0; 16205 enum VisualIDMask= 0x1; 16206 enum VisualScreenMask=0x2; 16207 enum VisualDepthMask= 0x4; 16208 enum VisualClassMask= 0x8; 16209 enum VisualRedMaskMask=0x10; 16210 enum VisualGreenMaskMask=0x20; 16211 enum VisualBlueMaskMask=0x40; 16212 enum VisualColormapSizeMask=0x80; 16213 enum VisualBitsPerRGBMask=0x100; 16214 enum VisualAllMask= 0x1FF; 16215 16216 enum AnyKey = 0; 16217 enum AnyModifier = 1 << 15; 16218 16219 // XIM and other crap 16220 struct _XOM {} 16221 struct _XIM {} 16222 struct _XIC {} 16223 alias XOM = _XOM*; 16224 alias XIM = _XIM*; 16225 alias XIC = _XIC*; 16226 16227 alias XVaNestedList = void*; 16228 16229 alias XIMStyle = arch_ulong; 16230 enum : arch_ulong { 16231 XIMPreeditArea = 0x0001, 16232 XIMPreeditCallbacks = 0x0002, 16233 XIMPreeditPosition = 0x0004, 16234 XIMPreeditNothing = 0x0008, 16235 XIMPreeditNone = 0x0010, 16236 XIMStatusArea = 0x0100, 16237 XIMStatusCallbacks = 0x0200, 16238 XIMStatusNothing = 0x0400, 16239 XIMStatusNone = 0x0800, 16240 } 16241 16242 16243 /* X Shared Memory Extension functions */ 16244 //pragma(lib, "Xshm"); 16245 alias arch_ulong ShmSeg; 16246 struct XShmSegmentInfo { 16247 ShmSeg shmseg; 16248 int shmid; 16249 ubyte* shmaddr; 16250 Bool readOnly; 16251 } 16252 16253 // and the necessary OS functions 16254 int shmget(int, size_t, int); 16255 void* shmat(int, scope const void*, int); 16256 int shmdt(scope const void*); 16257 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 16258 16259 enum IPC_PRIVATE = 0; 16260 enum IPC_CREAT = 512; 16261 enum IPC_RMID = 0; 16262 16263 /* MIT-SHM end */ 16264 16265 16266 enum MappingType:int { 16267 MappingModifier =0, 16268 MappingKeyboard =1, 16269 MappingPointer =2 16270 } 16271 16272 /* ImageFormat -- PutImage, GetImage */ 16273 enum ImageFormat:int { 16274 XYBitmap =0, /* depth 1, XYFormat */ 16275 XYPixmap =1, /* depth == drawable depth */ 16276 ZPixmap =2 /* depth == drawable depth */ 16277 } 16278 16279 enum ModifierName:int { 16280 ShiftMapIndex =0, 16281 LockMapIndex =1, 16282 ControlMapIndex =2, 16283 Mod1MapIndex =3, 16284 Mod2MapIndex =4, 16285 Mod3MapIndex =5, 16286 Mod4MapIndex =6, 16287 Mod5MapIndex =7 16288 } 16289 16290 enum ButtonMask:int { 16291 Button1Mask =1<<8, 16292 Button2Mask =1<<9, 16293 Button3Mask =1<<10, 16294 Button4Mask =1<<11, 16295 Button5Mask =1<<12, 16296 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16297 } 16298 16299 enum KeyOrButtonMask:uint { 16300 ShiftMask =1<<0, 16301 LockMask =1<<1, 16302 ControlMask =1<<2, 16303 Mod1Mask =1<<3, 16304 Mod2Mask =1<<4, 16305 Mod3Mask =1<<5, 16306 Mod4Mask =1<<6, 16307 Mod5Mask =1<<7, 16308 Button1Mask =1<<8, 16309 Button2Mask =1<<9, 16310 Button3Mask =1<<10, 16311 Button4Mask =1<<11, 16312 Button5Mask =1<<12, 16313 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16314 } 16315 16316 enum ButtonName:int { 16317 Button1 =1, 16318 Button2 =2, 16319 Button3 =3, 16320 Button4 =4, 16321 Button5 =5 16322 } 16323 16324 /* Notify modes */ 16325 enum NotifyModes:int 16326 { 16327 NotifyNormal =0, 16328 NotifyGrab =1, 16329 NotifyUngrab =2, 16330 NotifyWhileGrabbed =3 16331 } 16332 enum NotifyHint = 1; /* for MotionNotify events */ 16333 16334 /* Notify detail */ 16335 enum NotifyDetail:int 16336 { 16337 NotifyAncestor =0, 16338 NotifyVirtual =1, 16339 NotifyInferior =2, 16340 NotifyNonlinear =3, 16341 NotifyNonlinearVirtual =4, 16342 NotifyPointer =5, 16343 NotifyPointerRoot =6, 16344 NotifyDetailNone =7 16345 } 16346 16347 /* Visibility notify */ 16348 16349 enum VisibilityNotify:int 16350 { 16351 VisibilityUnobscured =0, 16352 VisibilityPartiallyObscured =1, 16353 VisibilityFullyObscured =2 16354 } 16355 16356 16357 enum WindowStackingMethod:int 16358 { 16359 Above =0, 16360 Below =1, 16361 TopIf =2, 16362 BottomIf =3, 16363 Opposite =4 16364 } 16365 16366 /* Circulation request */ 16367 enum CirculationRequest:int 16368 { 16369 PlaceOnTop =0, 16370 PlaceOnBottom =1 16371 } 16372 16373 enum PropertyNotification:int 16374 { 16375 PropertyNewValue =0, 16376 PropertyDelete =1 16377 } 16378 16379 enum ColorMapNotification:int 16380 { 16381 ColormapUninstalled =0, 16382 ColormapInstalled =1 16383 } 16384 16385 16386 struct _XPrivate {} 16387 struct _XrmHashBucketRec {} 16388 16389 alias void* XPointer; 16390 alias void* XExtData; 16391 16392 version( X86_64 ) { 16393 alias ulong XID; 16394 alias ulong arch_ulong; 16395 alias long arch_long; 16396 } else version (AArch64) { 16397 alias ulong XID; 16398 alias ulong arch_ulong; 16399 alias long arch_long; 16400 } else { 16401 alias uint XID; 16402 alias uint arch_ulong; 16403 alias int arch_long; 16404 } 16405 16406 alias XID Window; 16407 alias XID Drawable; 16408 alias XID Pixmap; 16409 16410 alias arch_ulong Atom; 16411 alias int Bool; 16412 alias Display XDisplay; 16413 16414 alias int ByteOrder; 16415 alias arch_ulong Time; 16416 alias void ScreenFormat; 16417 16418 struct XImage { 16419 int width, height; /* size of image */ 16420 int xoffset; /* number of pixels offset in X direction */ 16421 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 16422 void *data; /* pointer to image data */ 16423 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 16424 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 16425 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 16426 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 16427 int depth; /* depth of image */ 16428 int bytes_per_line; /* accelarator to next line */ 16429 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 16430 arch_ulong red_mask; /* bits in z arrangment */ 16431 arch_ulong green_mask; 16432 arch_ulong blue_mask; 16433 XPointer obdata; /* hook for the object routines to hang on */ 16434 static struct F { /* image manipulation routines */ 16435 XImage* function( 16436 XDisplay* /* display */, 16437 Visual* /* visual */, 16438 uint /* depth */, 16439 int /* format */, 16440 int /* offset */, 16441 ubyte* /* data */, 16442 uint /* width */, 16443 uint /* height */, 16444 int /* bitmap_pad */, 16445 int /* bytes_per_line */) create_image; 16446 int function(XImage *) destroy_image; 16447 arch_ulong function(XImage *, int, int) get_pixel; 16448 int function(XImage *, int, int, arch_ulong) put_pixel; 16449 XImage* function(XImage *, int, int, uint, uint) sub_image; 16450 int function(XImage *, arch_long) add_pixel; 16451 } 16452 F f; 16453 } 16454 version(X86_64) static assert(XImage.sizeof == 136); 16455 else version(X86) static assert(XImage.sizeof == 88); 16456 16457 struct XCharStruct { 16458 short lbearing; /* origin to left edge of raster */ 16459 short rbearing; /* origin to right edge of raster */ 16460 short width; /* advance to next char's origin */ 16461 short ascent; /* baseline to top edge of raster */ 16462 short descent; /* baseline to bottom edge of raster */ 16463 ushort attributes; /* per char flags (not predefined) */ 16464 } 16465 16466 /* 16467 * To allow arbitrary information with fonts, there are additional properties 16468 * returned. 16469 */ 16470 struct XFontProp { 16471 Atom name; 16472 arch_ulong card32; 16473 } 16474 16475 alias Atom Font; 16476 16477 struct XFontStruct { 16478 XExtData *ext_data; /* Hook for extension to hang data */ 16479 Font fid; /* Font ID for this font */ 16480 uint direction; /* Direction the font is painted */ 16481 uint min_char_or_byte2; /* First character */ 16482 uint max_char_or_byte2; /* Last character */ 16483 uint min_byte1; /* First row that exists (for two-byte fonts) */ 16484 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 16485 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 16486 uint default_char; /* Char to print for undefined character */ 16487 int n_properties; /* How many properties there are */ 16488 XFontProp *properties; /* Pointer to array of additional properties*/ 16489 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 16490 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 16491 XCharStruct *per_char; /* first_char to last_char information */ 16492 int ascent; /* Max extent above baseline for spacing */ 16493 int descent; /* Max descent below baseline for spacing */ 16494 } 16495 16496 16497 /* 16498 * Definitions of specific events. 16499 */ 16500 struct XKeyEvent 16501 { 16502 int type; /* of event */ 16503 arch_ulong serial; /* # of last request processed by server */ 16504 Bool send_event; /* true if this came from a SendEvent request */ 16505 Display *display; /* Display the event was read from */ 16506 Window window; /* "event" window it is reported relative to */ 16507 Window root; /* root window that the event occurred on */ 16508 Window subwindow; /* child window */ 16509 Time time; /* milliseconds */ 16510 int x, y; /* pointer x, y coordinates in event window */ 16511 int x_root, y_root; /* coordinates relative to root */ 16512 KeyOrButtonMask state; /* key or button mask */ 16513 uint keycode; /* detail */ 16514 Bool same_screen; /* same screen flag */ 16515 } 16516 version(X86_64) static assert(XKeyEvent.sizeof == 96); 16517 alias XKeyEvent XKeyPressedEvent; 16518 alias XKeyEvent XKeyReleasedEvent; 16519 16520 struct XButtonEvent 16521 { 16522 int type; /* of event */ 16523 arch_ulong serial; /* # of last request processed by server */ 16524 Bool send_event; /* true if this came from a SendEvent request */ 16525 Display *display; /* Display the event was read from */ 16526 Window window; /* "event" window it is reported relative to */ 16527 Window root; /* root window that the event occurred on */ 16528 Window subwindow; /* child window */ 16529 Time time; /* milliseconds */ 16530 int x, y; /* pointer x, y coordinates in event window */ 16531 int x_root, y_root; /* coordinates relative to root */ 16532 KeyOrButtonMask state; /* key or button mask */ 16533 uint button; /* detail */ 16534 Bool same_screen; /* same screen flag */ 16535 } 16536 alias XButtonEvent XButtonPressedEvent; 16537 alias XButtonEvent XButtonReleasedEvent; 16538 16539 struct XMotionEvent{ 16540 int type; /* of event */ 16541 arch_ulong serial; /* # of last request processed by server */ 16542 Bool send_event; /* true if this came from a SendEvent request */ 16543 Display *display; /* Display the event was read from */ 16544 Window window; /* "event" window reported relative to */ 16545 Window root; /* root window that the event occurred on */ 16546 Window subwindow; /* child window */ 16547 Time time; /* milliseconds */ 16548 int x, y; /* pointer x, y coordinates in event window */ 16549 int x_root, y_root; /* coordinates relative to root */ 16550 KeyOrButtonMask state; /* key or button mask */ 16551 byte is_hint; /* detail */ 16552 Bool same_screen; /* same screen flag */ 16553 } 16554 alias XMotionEvent XPointerMovedEvent; 16555 16556 struct XCrossingEvent{ 16557 int type; /* of event */ 16558 arch_ulong serial; /* # of last request processed by server */ 16559 Bool send_event; /* true if this came from a SendEvent request */ 16560 Display *display; /* Display the event was read from */ 16561 Window window; /* "event" window reported relative to */ 16562 Window root; /* root window that the event occurred on */ 16563 Window subwindow; /* child window */ 16564 Time time; /* milliseconds */ 16565 int x, y; /* pointer x, y coordinates in event window */ 16566 int x_root, y_root; /* coordinates relative to root */ 16567 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 16568 NotifyDetail detail; 16569 /* 16570 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16571 * NotifyNonlinear,NotifyNonlinearVirtual 16572 */ 16573 Bool same_screen; /* same screen flag */ 16574 Bool focus; /* Boolean focus */ 16575 KeyOrButtonMask state; /* key or button mask */ 16576 } 16577 alias XCrossingEvent XEnterWindowEvent; 16578 alias XCrossingEvent XLeaveWindowEvent; 16579 16580 struct XFocusChangeEvent{ 16581 int type; /* FocusIn or FocusOut */ 16582 arch_ulong serial; /* # of last request processed by server */ 16583 Bool send_event; /* true if this came from a SendEvent request */ 16584 Display *display; /* Display the event was read from */ 16585 Window window; /* window of event */ 16586 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 16587 NotifyGrab, NotifyUngrab */ 16588 NotifyDetail detail; 16589 /* 16590 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16591 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 16592 * NotifyPointerRoot, NotifyDetailNone 16593 */ 16594 } 16595 alias XFocusChangeEvent XFocusInEvent; 16596 alias XFocusChangeEvent XFocusOutEvent; 16597 16598 enum CWBackPixmap = (1L<<0); 16599 enum CWBackPixel = (1L<<1); 16600 enum CWBorderPixmap = (1L<<2); 16601 enum CWBorderPixel = (1L<<3); 16602 enum CWBitGravity = (1L<<4); 16603 enum CWWinGravity = (1L<<5); 16604 enum CWBackingStore = (1L<<6); 16605 enum CWBackingPlanes = (1L<<7); 16606 enum CWBackingPixel = (1L<<8); 16607 enum CWOverrideRedirect = (1L<<9); 16608 enum CWSaveUnder = (1L<<10); 16609 enum CWEventMask = (1L<<11); 16610 enum CWDontPropagate = (1L<<12); 16611 enum CWColormap = (1L<<13); 16612 enum CWCursor = (1L<<14); 16613 16614 struct XWindowAttributes { 16615 int x, y; /* location of window */ 16616 int width, height; /* width and height of window */ 16617 int border_width; /* border width of window */ 16618 int depth; /* depth of window */ 16619 Visual *visual; /* the associated visual structure */ 16620 Window root; /* root of screen containing window */ 16621 int class_; /* InputOutput, InputOnly*/ 16622 int bit_gravity; /* one of the bit gravity values */ 16623 int win_gravity; /* one of the window gravity values */ 16624 int backing_store; /* NotUseful, WhenMapped, Always */ 16625 arch_ulong backing_planes; /* planes to be preserved if possible */ 16626 arch_ulong backing_pixel; /* value to be used when restoring planes */ 16627 Bool save_under; /* boolean, should bits under be saved? */ 16628 Colormap colormap; /* color map to be associated with window */ 16629 Bool map_installed; /* boolean, is color map currently installed*/ 16630 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 16631 arch_long all_event_masks; /* set of events all people have interest in*/ 16632 arch_long your_event_mask; /* my event mask */ 16633 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 16634 Bool override_redirect; /* boolean value for override-redirect */ 16635 Screen *screen; /* back pointer to correct screen */ 16636 } 16637 16638 enum IsUnmapped = 0; 16639 enum IsUnviewable = 1; 16640 enum IsViewable = 2; 16641 16642 struct XSetWindowAttributes { 16643 Pixmap background_pixmap;/* background, None, or ParentRelative */ 16644 arch_ulong background_pixel;/* background pixel */ 16645 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 16646 arch_ulong border_pixel;/* border pixel value */ 16647 int bit_gravity; /* one of bit gravity values */ 16648 int win_gravity; /* one of the window gravity values */ 16649 int backing_store; /* NotUseful, WhenMapped, Always */ 16650 arch_ulong backing_planes;/* planes to be preserved if possible */ 16651 arch_ulong backing_pixel;/* value to use in restoring planes */ 16652 Bool save_under; /* should bits under be saved? (popups) */ 16653 arch_long event_mask; /* set of events that should be saved */ 16654 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 16655 Bool override_redirect; /* boolean value for override_redirect */ 16656 Colormap colormap; /* color map to be associated with window */ 16657 Cursor cursor; /* cursor to be displayed (or None) */ 16658 } 16659 16660 16661 alias int Status; 16662 16663 16664 enum EventMask:int 16665 { 16666 NoEventMask =0, 16667 KeyPressMask =1<<0, 16668 KeyReleaseMask =1<<1, 16669 ButtonPressMask =1<<2, 16670 ButtonReleaseMask =1<<3, 16671 EnterWindowMask =1<<4, 16672 LeaveWindowMask =1<<5, 16673 PointerMotionMask =1<<6, 16674 PointerMotionHintMask =1<<7, 16675 Button1MotionMask =1<<8, 16676 Button2MotionMask =1<<9, 16677 Button3MotionMask =1<<10, 16678 Button4MotionMask =1<<11, 16679 Button5MotionMask =1<<12, 16680 ButtonMotionMask =1<<13, 16681 KeymapStateMask =1<<14, 16682 ExposureMask =1<<15, 16683 VisibilityChangeMask =1<<16, 16684 StructureNotifyMask =1<<17, 16685 ResizeRedirectMask =1<<18, 16686 SubstructureNotifyMask =1<<19, 16687 SubstructureRedirectMask=1<<20, 16688 FocusChangeMask =1<<21, 16689 PropertyChangeMask =1<<22, 16690 ColormapChangeMask =1<<23, 16691 OwnerGrabButtonMask =1<<24 16692 } 16693 16694 struct MwmHints { 16695 c_ulong flags; 16696 c_ulong functions; 16697 c_ulong decorations; 16698 c_long input_mode; 16699 c_ulong status; 16700 } 16701 16702 enum { 16703 MWM_HINTS_FUNCTIONS = (1L << 0), 16704 MWM_HINTS_DECORATIONS = (1L << 1), 16705 16706 MWM_FUNC_ALL = (1L << 0), 16707 MWM_FUNC_RESIZE = (1L << 1), 16708 MWM_FUNC_MOVE = (1L << 2), 16709 MWM_FUNC_MINIMIZE = (1L << 3), 16710 MWM_FUNC_MAXIMIZE = (1L << 4), 16711 MWM_FUNC_CLOSE = (1L << 5), 16712 16713 MWM_DECOR_ALL = (1L << 0), 16714 MWM_DECOR_BORDER = (1L << 1), 16715 MWM_DECOR_RESIZEH = (1L << 2), 16716 MWM_DECOR_TITLE = (1L << 3), 16717 MWM_DECOR_MENU = (1L << 4), 16718 MWM_DECOR_MINIMIZE = (1L << 5), 16719 MWM_DECOR_MAXIMIZE = (1L << 6), 16720 } 16721 16722 import core.stdc.config : c_long, c_ulong; 16723 16724 /* Size hints mask bits */ 16725 16726 enum USPosition = (1L << 0) /* user specified x, y */; 16727 enum USSize = (1L << 1) /* user specified width, height */; 16728 enum PPosition = (1L << 2) /* program specified position */; 16729 enum PSize = (1L << 3) /* program specified size */; 16730 enum PMinSize = (1L << 4) /* program specified minimum size */; 16731 enum PMaxSize = (1L << 5) /* program specified maximum size */; 16732 enum PResizeInc = (1L << 6) /* program specified resize increments */; 16733 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 16734 enum PBaseSize = (1L << 8); 16735 enum PWinGravity = (1L << 9); 16736 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 16737 struct XSizeHints { 16738 arch_long flags; /* marks which fields in this structure are defined */ 16739 int x, y; /* Obsolete */ 16740 int width, height; /* Obsolete */ 16741 int min_width, min_height; 16742 int max_width, max_height; 16743 int width_inc, height_inc; 16744 struct Aspect { 16745 int x; /* numerator */ 16746 int y; /* denominator */ 16747 } 16748 16749 Aspect min_aspect; 16750 Aspect max_aspect; 16751 int base_width, base_height; 16752 int win_gravity; 16753 /* this structure may be extended in the future */ 16754 } 16755 16756 16757 16758 enum EventType:int 16759 { 16760 KeyPress =2, 16761 KeyRelease =3, 16762 ButtonPress =4, 16763 ButtonRelease =5, 16764 MotionNotify =6, 16765 EnterNotify =7, 16766 LeaveNotify =8, 16767 FocusIn =9, 16768 FocusOut =10, 16769 KeymapNotify =11, 16770 Expose =12, 16771 GraphicsExpose =13, 16772 NoExpose =14, 16773 VisibilityNotify =15, 16774 CreateNotify =16, 16775 DestroyNotify =17, 16776 UnmapNotify =18, 16777 MapNotify =19, 16778 MapRequest =20, 16779 ReparentNotify =21, 16780 ConfigureNotify =22, 16781 ConfigureRequest =23, 16782 GravityNotify =24, 16783 ResizeRequest =25, 16784 CirculateNotify =26, 16785 CirculateRequest =27, 16786 PropertyNotify =28, 16787 SelectionClear =29, 16788 SelectionRequest =30, 16789 SelectionNotify =31, 16790 ColormapNotify =32, 16791 ClientMessage =33, 16792 MappingNotify =34, 16793 LASTEvent =35 /* must be bigger than any event # */ 16794 } 16795 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 16796 struct XKeymapEvent 16797 { 16798 int type; 16799 arch_ulong serial; /* # of last request processed by server */ 16800 Bool send_event; /* true if this came from a SendEvent request */ 16801 Display *display; /* Display the event was read from */ 16802 Window window; 16803 byte[32] key_vector; 16804 } 16805 16806 struct XExposeEvent 16807 { 16808 int type; 16809 arch_ulong serial; /* # of last request processed by server */ 16810 Bool send_event; /* true if this came from a SendEvent request */ 16811 Display *display; /* Display the event was read from */ 16812 Window window; 16813 int x, y; 16814 int width, height; 16815 int count; /* if non-zero, at least this many more */ 16816 } 16817 16818 struct XGraphicsExposeEvent{ 16819 int type; 16820 arch_ulong serial; /* # of last request processed by server */ 16821 Bool send_event; /* true if this came from a SendEvent request */ 16822 Display *display; /* Display the event was read from */ 16823 Drawable drawable; 16824 int x, y; 16825 int width, height; 16826 int count; /* if non-zero, at least this many more */ 16827 int major_code; /* core is CopyArea or CopyPlane */ 16828 int minor_code; /* not defined in the core */ 16829 } 16830 16831 struct XNoExposeEvent{ 16832 int type; 16833 arch_ulong serial; /* # of last request processed by server */ 16834 Bool send_event; /* true if this came from a SendEvent request */ 16835 Display *display; /* Display the event was read from */ 16836 Drawable drawable; 16837 int major_code; /* core is CopyArea or CopyPlane */ 16838 int minor_code; /* not defined in the core */ 16839 } 16840 16841 struct XVisibilityEvent{ 16842 int type; 16843 arch_ulong serial; /* # of last request processed by server */ 16844 Bool send_event; /* true if this came from a SendEvent request */ 16845 Display *display; /* Display the event was read from */ 16846 Window window; 16847 VisibilityNotify state; /* Visibility state */ 16848 } 16849 16850 struct XCreateWindowEvent{ 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 parent; /* parent of the window */ 16856 Window window; /* window id of window created */ 16857 int x, y; /* window location */ 16858 int width, height; /* size of window */ 16859 int border_width; /* border width */ 16860 Bool override_redirect; /* creation should be overridden */ 16861 } 16862 16863 struct XDestroyWindowEvent 16864 { 16865 int type; 16866 arch_ulong serial; /* # of last request processed by server */ 16867 Bool send_event; /* true if this came from a SendEvent request */ 16868 Display *display; /* Display the event was read from */ 16869 Window event; 16870 Window window; 16871 } 16872 16873 struct XUnmapEvent 16874 { 16875 int type; 16876 arch_ulong serial; /* # of last request processed by server */ 16877 Bool send_event; /* true if this came from a SendEvent request */ 16878 Display *display; /* Display the event was read from */ 16879 Window event; 16880 Window window; 16881 Bool from_configure; 16882 } 16883 16884 struct XMapEvent 16885 { 16886 int type; 16887 arch_ulong serial; /* # of last request processed by server */ 16888 Bool send_event; /* true if this came from a SendEvent request */ 16889 Display *display; /* Display the event was read from */ 16890 Window event; 16891 Window window; 16892 Bool override_redirect; /* Boolean, is override set... */ 16893 } 16894 16895 struct XMapRequestEvent 16896 { 16897 int type; 16898 arch_ulong serial; /* # of last request processed by server */ 16899 Bool send_event; /* true if this came from a SendEvent request */ 16900 Display *display; /* Display the event was read from */ 16901 Window parent; 16902 Window window; 16903 } 16904 16905 struct XReparentEvent 16906 { 16907 int type; 16908 arch_ulong serial; /* # of last request processed by server */ 16909 Bool send_event; /* true if this came from a SendEvent request */ 16910 Display *display; /* Display the event was read from */ 16911 Window event; 16912 Window window; 16913 Window parent; 16914 int x, y; 16915 Bool override_redirect; 16916 } 16917 16918 struct XConfigureEvent 16919 { 16920 int type; 16921 arch_ulong serial; /* # of last request processed by server */ 16922 Bool send_event; /* true if this came from a SendEvent request */ 16923 Display *display; /* Display the event was read from */ 16924 Window event; 16925 Window window; 16926 int x, y; 16927 int width, height; 16928 int border_width; 16929 Window above; 16930 Bool override_redirect; 16931 } 16932 16933 struct XGravityEvent 16934 { 16935 int type; 16936 arch_ulong serial; /* # of last request processed by server */ 16937 Bool send_event; /* true if this came from a SendEvent request */ 16938 Display *display; /* Display the event was read from */ 16939 Window event; 16940 Window window; 16941 int x, y; 16942 } 16943 16944 struct XResizeRequestEvent 16945 { 16946 int type; 16947 arch_ulong serial; /* # of last request processed by server */ 16948 Bool send_event; /* true if this came from a SendEvent request */ 16949 Display *display; /* Display the event was read from */ 16950 Window window; 16951 int width, height; 16952 } 16953 16954 struct XConfigureRequestEvent 16955 { 16956 int type; 16957 arch_ulong serial; /* # of last request processed by server */ 16958 Bool send_event; /* true if this came from a SendEvent request */ 16959 Display *display; /* Display the event was read from */ 16960 Window parent; 16961 Window window; 16962 int x, y; 16963 int width, height; 16964 int border_width; 16965 Window above; 16966 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 16967 arch_ulong value_mask; 16968 } 16969 16970 struct XCirculateEvent 16971 { 16972 int type; 16973 arch_ulong serial; /* # of last request processed by server */ 16974 Bool send_event; /* true if this came from a SendEvent request */ 16975 Display *display; /* Display the event was read from */ 16976 Window event; 16977 Window window; 16978 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 16979 } 16980 16981 struct XCirculateRequestEvent 16982 { 16983 int type; 16984 arch_ulong serial; /* # of last request processed by server */ 16985 Bool send_event; /* true if this came from a SendEvent request */ 16986 Display *display; /* Display the event was read from */ 16987 Window parent; 16988 Window window; 16989 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 16990 } 16991 16992 struct XPropertyEvent 16993 { 16994 int type; 16995 arch_ulong serial; /* # of last request processed by server */ 16996 Bool send_event; /* true if this came from a SendEvent request */ 16997 Display *display; /* Display the event was read from */ 16998 Window window; 16999 Atom atom; 17000 Time time; 17001 PropertyNotification state; /* NewValue, Deleted */ 17002 } 17003 17004 struct XSelectionClearEvent 17005 { 17006 int type; 17007 arch_ulong serial; /* # of last request processed by server */ 17008 Bool send_event; /* true if this came from a SendEvent request */ 17009 Display *display; /* Display the event was read from */ 17010 Window window; 17011 Atom selection; 17012 Time time; 17013 } 17014 17015 struct XSelectionRequestEvent 17016 { 17017 int type; 17018 arch_ulong serial; /* # of last request processed by server */ 17019 Bool send_event; /* true if this came from a SendEvent request */ 17020 Display *display; /* Display the event was read from */ 17021 Window owner; 17022 Window requestor; 17023 Atom selection; 17024 Atom target; 17025 Atom property; 17026 Time time; 17027 } 17028 17029 struct XSelectionEvent 17030 { 17031 int type; 17032 arch_ulong serial; /* # of last request processed by server */ 17033 Bool send_event; /* true if this came from a SendEvent request */ 17034 Display *display; /* Display the event was read from */ 17035 Window requestor; 17036 Atom selection; 17037 Atom target; 17038 Atom property; /* ATOM or None */ 17039 Time time; 17040 } 17041 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 17042 17043 struct XColormapEvent 17044 { 17045 int type; 17046 arch_ulong serial; /* # of last request processed by server */ 17047 Bool send_event; /* true if this came from a SendEvent request */ 17048 Display *display; /* Display the event was read from */ 17049 Window window; 17050 Colormap colormap; /* COLORMAP or None */ 17051 Bool new_; /* C++ */ 17052 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 17053 } 17054 version(X86_64) static assert(XColormapEvent.sizeof == 56); 17055 17056 struct XClientMessageEvent 17057 { 17058 int type; 17059 arch_ulong serial; /* # of last request processed by server */ 17060 Bool send_event; /* true if this came from a SendEvent request */ 17061 Display *display; /* Display the event was read from */ 17062 Window window; 17063 Atom message_type; 17064 int format; 17065 union Data{ 17066 byte[20] b; 17067 short[10] s; 17068 arch_ulong[5] l; 17069 } 17070 Data data; 17071 17072 } 17073 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 17074 17075 struct XMappingEvent 17076 { 17077 int type; 17078 arch_ulong serial; /* # of last request processed by server */ 17079 Bool send_event; /* true if this came from a SendEvent request */ 17080 Display *display; /* Display the event was read from */ 17081 Window window; /* unused */ 17082 MappingType request; /* one of MappingModifier, MappingKeyboard, 17083 MappingPointer */ 17084 int first_keycode; /* first keycode */ 17085 int count; /* defines range of change w. first_keycode*/ 17086 } 17087 17088 struct XErrorEvent 17089 { 17090 int type; 17091 Display *display; /* Display the event was read from */ 17092 XID resourceid; /* resource id */ 17093 arch_ulong serial; /* serial number of failed request */ 17094 ubyte error_code; /* error code of failed request */ 17095 ubyte request_code; /* Major op-code of failed request */ 17096 ubyte minor_code; /* Minor op-code of failed request */ 17097 } 17098 17099 struct XAnyEvent 17100 { 17101 int type; 17102 arch_ulong serial; /* # of last request processed by server */ 17103 Bool send_event; /* true if this came from a SendEvent request */ 17104 Display *display;/* Display the event was read from */ 17105 Window window; /* window on which event was requested in event mask */ 17106 } 17107 17108 union XEvent{ 17109 int type; /* must not be changed; first element */ 17110 XAnyEvent xany; 17111 XKeyEvent xkey; 17112 XButtonEvent xbutton; 17113 XMotionEvent xmotion; 17114 XCrossingEvent xcrossing; 17115 XFocusChangeEvent xfocus; 17116 XExposeEvent xexpose; 17117 XGraphicsExposeEvent xgraphicsexpose; 17118 XNoExposeEvent xnoexpose; 17119 XVisibilityEvent xvisibility; 17120 XCreateWindowEvent xcreatewindow; 17121 XDestroyWindowEvent xdestroywindow; 17122 XUnmapEvent xunmap; 17123 XMapEvent xmap; 17124 XMapRequestEvent xmaprequest; 17125 XReparentEvent xreparent; 17126 XConfigureEvent xconfigure; 17127 XGravityEvent xgravity; 17128 XResizeRequestEvent xresizerequest; 17129 XConfigureRequestEvent xconfigurerequest; 17130 XCirculateEvent xcirculate; 17131 XCirculateRequestEvent xcirculaterequest; 17132 XPropertyEvent xproperty; 17133 XSelectionClearEvent xselectionclear; 17134 XSelectionRequestEvent xselectionrequest; 17135 XSelectionEvent xselection; 17136 XColormapEvent xcolormap; 17137 XClientMessageEvent xclient; 17138 XMappingEvent xmapping; 17139 XErrorEvent xerror; 17140 XKeymapEvent xkeymap; 17141 arch_ulong[24] pad; 17142 } 17143 17144 17145 struct Display { 17146 XExtData *ext_data; /* hook for extension to hang data */ 17147 _XPrivate *private1; 17148 int fd; /* Network socket. */ 17149 int private2; 17150 int proto_major_version;/* major version of server's X protocol */ 17151 int proto_minor_version;/* minor version of servers X protocol */ 17152 char *vendor; /* vendor of the server hardware */ 17153 XID private3; 17154 XID private4; 17155 XID private5; 17156 int private6; 17157 XID function(Display*)resource_alloc;/* allocator function */ 17158 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17159 int bitmap_unit; /* padding and data requirements */ 17160 int bitmap_pad; /* padding requirements on bitmaps */ 17161 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17162 int nformats; /* number of pixmap formats in list */ 17163 ScreenFormat *pixmap_format; /* pixmap format list */ 17164 int private8; 17165 int release; /* release of the server */ 17166 _XPrivate *private9; 17167 _XPrivate *private10; 17168 int qlen; /* Length of input event queue */ 17169 arch_ulong last_request_read; /* seq number of last event read */ 17170 arch_ulong request; /* sequence number of last request. */ 17171 XPointer private11; 17172 XPointer private12; 17173 XPointer private13; 17174 XPointer private14; 17175 uint max_request_size; /* maximum number 32 bit words in request*/ 17176 _XrmHashBucketRec *db; 17177 int function (Display*)private15; 17178 char *display_name; /* "host:display" string used on this connect*/ 17179 int default_screen; /* default screen for operations */ 17180 int nscreens; /* number of screens on this server*/ 17181 Screen *screens; /* pointer to list of screens */ 17182 arch_ulong motion_buffer; /* size of motion buffer */ 17183 arch_ulong private16; 17184 int min_keycode; /* minimum defined keycode */ 17185 int max_keycode; /* maximum defined keycode */ 17186 XPointer private17; 17187 XPointer private18; 17188 int private19; 17189 byte *xdefaults; /* contents of defaults from server */ 17190 /* there is more to this structure, but it is private to Xlib */ 17191 } 17192 17193 // I got these numbers from a C program as a sanity test 17194 version(X86_64) { 17195 static assert(Display.sizeof == 296); 17196 static assert(XPointer.sizeof == 8); 17197 static assert(XErrorEvent.sizeof == 40); 17198 static assert(XAnyEvent.sizeof == 40); 17199 static assert(XMappingEvent.sizeof == 56); 17200 static assert(XEvent.sizeof == 192); 17201 } else version (AArch64) { 17202 // omit check for aarch64 17203 } else { 17204 static assert(Display.sizeof == 176); 17205 static assert(XPointer.sizeof == 4); 17206 static assert(XEvent.sizeof == 96); 17207 } 17208 17209 struct Depth 17210 { 17211 int depth; /* this depth (Z) of the depth */ 17212 int nvisuals; /* number of Visual types at this depth */ 17213 Visual *visuals; /* list of visuals possible at this depth */ 17214 } 17215 17216 alias void* GC; 17217 alias c_ulong VisualID; 17218 alias XID Colormap; 17219 alias XID Cursor; 17220 alias XID KeySym; 17221 alias uint KeyCode; 17222 enum None = 0; 17223 } 17224 17225 version(without_opengl) {} 17226 else { 17227 extern(C) nothrow @nogc { 17228 17229 17230 static if(!SdpyIsUsingIVGLBinds) { 17231 enum GLX_USE_GL= 1; /* support GLX rendering */ 17232 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17233 enum GLX_LEVEL= 3; /* level in plane stacking */ 17234 enum GLX_RGBA= 4; /* true if RGBA mode */ 17235 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17236 enum GLX_STEREO= 6; /* stereo buffering supported */ 17237 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 17238 enum GLX_RED_SIZE= 8; /* number of red component bits */ 17239 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 17240 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 17241 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 17242 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 17243 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 17244 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 17245 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 17246 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 17247 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 17248 17249 17250 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 17251 17252 17253 17254 enum GL_TRUE = 1; 17255 enum GL_FALSE = 0; 17256 alias int GLint; 17257 } 17258 17259 alias XID GLXContextID; 17260 alias XID GLXPixmap; 17261 alias XID GLXDrawable; 17262 alias XID GLXPbuffer; 17263 alias XID GLXWindow; 17264 alias XID GLXFBConfigID; 17265 alias void* GLXContext; 17266 17267 } 17268 } 17269 17270 enum AllocNone = 0; 17271 17272 extern(C) { 17273 /* WARNING, this type not in Xlib spec */ 17274 extern(C) alias XIOErrorHandler = int function (Display* display); 17275 } 17276 17277 extern(C) nothrow 17278 alias XErrorHandler = int function(Display*, XErrorEvent*); 17279 17280 extern(C) nothrow @nogc { 17281 struct Screen{ 17282 XExtData *ext_data; /* hook for extension to hang data */ 17283 Display *display; /* back pointer to display structure */ 17284 Window root; /* Root window id. */ 17285 int width, height; /* width and height of screen */ 17286 int mwidth, mheight; /* width and height of in millimeters */ 17287 int ndepths; /* number of depths possible */ 17288 Depth *depths; /* list of allowable depths on the screen */ 17289 int root_depth; /* bits per pixel */ 17290 Visual *root_visual; /* root visual */ 17291 GC default_gc; /* GC for the root root visual */ 17292 Colormap cmap; /* default color map */ 17293 uint white_pixel; 17294 uint black_pixel; /* White and Black pixel values */ 17295 int max_maps, min_maps; /* max and min color maps */ 17296 int backing_store; /* Never, WhenMapped, Always */ 17297 bool save_unders; 17298 int root_input_mask; /* initial root input mask */ 17299 } 17300 17301 struct Visual 17302 { 17303 XExtData *ext_data; /* hook for extension to hang data */ 17304 VisualID visualid; /* visual id of this visual */ 17305 int class_; /* class of screen (monochrome, etc.) */ 17306 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 17307 int bits_per_rgb; /* log base 2 of distinct color values */ 17308 int map_entries; /* color map entries */ 17309 } 17310 17311 alias Display* _XPrivDisplay; 17312 17313 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) { 17314 assert(dpy !is null); 17315 return &dpy.screens[scr]; 17316 } 17317 17318 extern(D) Window RootWindow(Display *dpy,int scr) { 17319 return ScreenOfDisplay(dpy,scr).root; 17320 } 17321 17322 struct XWMHints { 17323 arch_long flags; 17324 Bool input; 17325 int initial_state; 17326 Pixmap icon_pixmap; 17327 Window icon_window; 17328 int icon_x, icon_y; 17329 Pixmap icon_mask; 17330 XID window_group; 17331 } 17332 17333 struct XClassHint { 17334 char* res_name; 17335 char* res_class; 17336 } 17337 17338 extern(D) int DefaultScreen(Display *dpy) { 17339 return dpy.default_screen; 17340 } 17341 17342 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 17343 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 17344 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 17345 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 17346 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 17347 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 17348 17349 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 17350 17351 enum int AnyPropertyType = 0; 17352 enum int Success = 0; 17353 17354 enum int RevertToNone = None; 17355 enum int PointerRoot = 1; 17356 enum Time CurrentTime = 0; 17357 enum int RevertToPointerRoot = PointerRoot; 17358 enum int RevertToParent = 2; 17359 17360 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 17361 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 17362 } 17363 17364 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 17365 return ScreenOfDisplay(dpy,scr).root_visual; 17366 } 17367 17368 extern(D) GC DefaultGC(Display *dpy,int scr) { 17369 return ScreenOfDisplay(dpy,scr).default_gc; 17370 } 17371 17372 extern(D) uint BlackPixel(Display *dpy,int scr) { 17373 return ScreenOfDisplay(dpy,scr).black_pixel; 17374 } 17375 17376 extern(D) uint WhitePixel(Display *dpy,int scr) { 17377 return ScreenOfDisplay(dpy,scr).white_pixel; 17378 } 17379 17380 alias void* XFontSet; // i think 17381 struct XmbTextItem { 17382 char* chars; 17383 int nchars; 17384 int delta; 17385 XFontSet font_set; 17386 } 17387 17388 struct XTextItem { 17389 char* chars; 17390 int nchars; 17391 int delta; 17392 Font font; 17393 } 17394 17395 enum { 17396 GXclear = 0x0, /* 0 */ 17397 GXand = 0x1, /* src AND dst */ 17398 GXandReverse = 0x2, /* src AND NOT dst */ 17399 GXcopy = 0x3, /* src */ 17400 GXandInverted = 0x4, /* NOT src AND dst */ 17401 GXnoop = 0x5, /* dst */ 17402 GXxor = 0x6, /* src XOR dst */ 17403 GXor = 0x7, /* src OR dst */ 17404 GXnor = 0x8, /* NOT src AND NOT dst */ 17405 GXequiv = 0x9, /* NOT src XOR dst */ 17406 GXinvert = 0xa, /* NOT dst */ 17407 GXorReverse = 0xb, /* src OR NOT dst */ 17408 GXcopyInverted = 0xc, /* NOT src */ 17409 GXorInverted = 0xd, /* NOT src OR dst */ 17410 GXnand = 0xe, /* NOT src OR NOT dst */ 17411 GXset = 0xf, /* 1 */ 17412 } 17413 enum QueueMode : int { 17414 QueuedAlready, 17415 QueuedAfterReading, 17416 QueuedAfterFlush 17417 } 17418 17419 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 17420 17421 struct XPoint { 17422 short x; 17423 short y; 17424 } 17425 17426 enum CoordMode:int { 17427 CoordModeOrigin = 0, 17428 CoordModePrevious = 1 17429 } 17430 17431 enum PolygonShape:int { 17432 Complex = 0, 17433 Nonconvex = 1, 17434 Convex = 2 17435 } 17436 17437 struct XTextProperty { 17438 const(char)* value; /* same as Property routines */ 17439 Atom encoding; /* prop type */ 17440 int format; /* prop data format: 8, 16, or 32 */ 17441 arch_ulong nitems; /* number of data items in value */ 17442 } 17443 17444 version( X86_64 ) { 17445 static assert(XTextProperty.sizeof == 32); 17446 } 17447 17448 17449 struct XGCValues { 17450 int function_; /* logical operation */ 17451 arch_ulong plane_mask;/* plane mask */ 17452 arch_ulong foreground;/* foreground pixel */ 17453 arch_ulong background;/* background pixel */ 17454 int line_width; /* line width */ 17455 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 17456 int cap_style; /* CapNotLast, CapButt, 17457 CapRound, CapProjecting */ 17458 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 17459 int fill_style; /* FillSolid, FillTiled, 17460 FillStippled, FillOpaeueStippled */ 17461 int fill_rule; /* EvenOddRule, WindingRule */ 17462 int arc_mode; /* ArcChord, ArcPieSlice */ 17463 Pixmap tile; /* tile pixmap for tiling operations */ 17464 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 17465 int ts_x_origin; /* offset for tile or stipple operations */ 17466 int ts_y_origin; 17467 Font font; /* default text font for text operations */ 17468 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 17469 Bool graphics_exposures;/* boolean, should exposures be generated */ 17470 int clip_x_origin; /* origin for clipping */ 17471 int clip_y_origin; 17472 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 17473 int dash_offset; /* patterned/dashed line information */ 17474 char dashes; 17475 } 17476 17477 struct XColor { 17478 arch_ulong pixel; 17479 ushort red, green, blue; 17480 byte flags; 17481 byte pad; 17482 } 17483 17484 struct XRectangle { 17485 short x; 17486 short y; 17487 ushort width; 17488 ushort height; 17489 } 17490 17491 enum ClipByChildren = 0; 17492 enum IncludeInferiors = 1; 17493 17494 enum Atom XA_PRIMARY = 1; 17495 enum Atom XA_SECONDARY = 2; 17496 enum Atom XA_STRING = 31; 17497 enum Atom XA_CARDINAL = 6; 17498 enum Atom XA_WM_NAME = 39; 17499 enum Atom XA_ATOM = 4; 17500 enum Atom XA_WINDOW = 33; 17501 enum Atom XA_WM_HINTS = 35; 17502 enum int PropModeAppend = 2; 17503 enum int PropModeReplace = 0; 17504 enum int PropModePrepend = 1; 17505 17506 enum int CopyFromParent = 0; 17507 enum int InputOutput = 1; 17508 17509 // XWMHints 17510 enum InputHint = 1 << 0; 17511 enum StateHint = 1 << 1; 17512 enum IconPixmapHint = (1L << 2); 17513 enum IconWindowHint = (1L << 3); 17514 enum IconPositionHint = (1L << 4); 17515 enum IconMaskHint = (1L << 5); 17516 enum WindowGroupHint = (1L << 6); 17517 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 17518 enum XUrgencyHint = (1L << 8); 17519 17520 // GC Components 17521 enum GCFunction = (1L<<0); 17522 enum GCPlaneMask = (1L<<1); 17523 enum GCForeground = (1L<<2); 17524 enum GCBackground = (1L<<3); 17525 enum GCLineWidth = (1L<<4); 17526 enum GCLineStyle = (1L<<5); 17527 enum GCCapStyle = (1L<<6); 17528 enum GCJoinStyle = (1L<<7); 17529 enum GCFillStyle = (1L<<8); 17530 enum GCFillRule = (1L<<9); 17531 enum GCTile = (1L<<10); 17532 enum GCStipple = (1L<<11); 17533 enum GCTileStipXOrigin = (1L<<12); 17534 enum GCTileStipYOrigin = (1L<<13); 17535 enum GCFont = (1L<<14); 17536 enum GCSubwindowMode = (1L<<15); 17537 enum GCGraphicsExposures= (1L<<16); 17538 enum GCClipXOrigin = (1L<<17); 17539 enum GCClipYOrigin = (1L<<18); 17540 enum GCClipMask = (1L<<19); 17541 enum GCDashOffset = (1L<<20); 17542 enum GCDashList = (1L<<21); 17543 enum GCArcMode = (1L<<22); 17544 enum GCLastBit = 22; 17545 17546 17547 enum int WithdrawnState = 0; 17548 enum int NormalState = 1; 17549 enum int IconicState = 3; 17550 17551 } 17552 } else version (OSXCocoa) { 17553 private: 17554 alias void* id; 17555 alias void* Class; 17556 alias void* SEL; 17557 alias void* IMP; 17558 alias void* Ivar; 17559 alias byte BOOL; 17560 alias const(void)* CFStringRef; 17561 alias const(void)* CFAllocatorRef; 17562 alias const(void)* CFTypeRef; 17563 alias const(void)* CGContextRef; 17564 alias const(void)* CGColorSpaceRef; 17565 alias const(void)* CGImageRef; 17566 alias ulong CGBitmapInfo; 17567 17568 struct objc_super { 17569 id self; 17570 Class superclass; 17571 } 17572 17573 struct CFRange { 17574 long location, length; 17575 } 17576 17577 struct NSPoint { 17578 double x, y; 17579 17580 static fromTuple(T)(T tupl) { 17581 return NSPoint(tupl.tupleof); 17582 } 17583 } 17584 struct NSSize { 17585 double width, height; 17586 } 17587 struct NSRect { 17588 NSPoint origin; 17589 NSSize size; 17590 } 17591 alias NSPoint CGPoint; 17592 alias NSSize CGSize; 17593 alias NSRect CGRect; 17594 17595 struct CGAffineTransform { 17596 double a, b, c, d, tx, ty; 17597 } 17598 17599 enum NSApplicationActivationPolicyRegular = 0; 17600 enum NSBackingStoreBuffered = 2; 17601 enum kCFStringEncodingUTF8 = 0x08000100; 17602 17603 enum : size_t { 17604 NSBorderlessWindowMask = 0, 17605 NSTitledWindowMask = 1 << 0, 17606 NSClosableWindowMask = 1 << 1, 17607 NSMiniaturizableWindowMask = 1 << 2, 17608 NSResizableWindowMask = 1 << 3, 17609 NSTexturedBackgroundWindowMask = 1 << 8 17610 } 17611 17612 enum : ulong { 17613 kCGImageAlphaNone, 17614 kCGImageAlphaPremultipliedLast, 17615 kCGImageAlphaPremultipliedFirst, 17616 kCGImageAlphaLast, 17617 kCGImageAlphaFirst, 17618 kCGImageAlphaNoneSkipLast, 17619 kCGImageAlphaNoneSkipFirst 17620 } 17621 enum : ulong { 17622 kCGBitmapAlphaInfoMask = 0x1F, 17623 kCGBitmapFloatComponents = (1 << 8), 17624 kCGBitmapByteOrderMask = 0x7000, 17625 kCGBitmapByteOrderDefault = (0 << 12), 17626 kCGBitmapByteOrder16Little = (1 << 12), 17627 kCGBitmapByteOrder32Little = (2 << 12), 17628 kCGBitmapByteOrder16Big = (3 << 12), 17629 kCGBitmapByteOrder32Big = (4 << 12) 17630 } 17631 enum CGPathDrawingMode { 17632 kCGPathFill, 17633 kCGPathEOFill, 17634 kCGPathStroke, 17635 kCGPathFillStroke, 17636 kCGPathEOFillStroke 17637 } 17638 enum objc_AssociationPolicy : size_t { 17639 OBJC_ASSOCIATION_ASSIGN = 0, 17640 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 17641 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 17642 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 17643 OBJC_ASSOCIATION_COPY = 0x303 //01403 17644 } 17645 17646 extern(C) { 17647 id objc_msgSend(id receiver, SEL selector, ...); 17648 id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...); 17649 id objc_getClass(const(char)* name); 17650 SEL sel_registerName(const(char)* str); 17651 Class objc_allocateClassPair(Class superclass, const(char)* name, 17652 size_t extra_bytes); 17653 void objc_registerClassPair(Class cls); 17654 BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types); 17655 id objc_getAssociatedObject(id object, void* key); 17656 void objc_setAssociatedObject(id object, void* key, id value, 17657 objc_AssociationPolicy policy); 17658 Ivar class_getInstanceVariable(Class cls, const(char)* name); 17659 id object_getIvar(id object, Ivar ivar); 17660 void object_setIvar(id object, Ivar ivar, id value); 17661 BOOL class_addIvar(Class cls, const(char)* name, 17662 size_t size, ubyte alignment, const(char)* types); 17663 17664 extern __gshared id NSApp; 17665 17666 void CFRelease(CFTypeRef obj); 17667 17668 CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator, 17669 const(char)* bytes, long numBytes, 17670 long encoding, 17671 BOOL isExternalRepresentation); 17672 long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding, 17673 char lossByte, bool isExternalRepresentation, 17674 char* buffer, long maxBufLen, long* usedBufLen); 17675 long CFStringGetLength(CFStringRef theString); 17676 17677 CGContextRef CGBitmapContextCreate(void* data, 17678 size_t width, size_t height, 17679 size_t bitsPerComponent, 17680 size_t bytesPerRow, 17681 CGColorSpaceRef colorspace, 17682 CGBitmapInfo bitmapInfo); 17683 void CGContextRelease(CGContextRef c); 17684 ubyte* CGBitmapContextGetData(CGContextRef c); 17685 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 17686 size_t CGBitmapContextGetWidth(CGContextRef c); 17687 size_t CGBitmapContextGetHeight(CGContextRef c); 17688 17689 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 17690 void CGColorSpaceRelease(CGColorSpaceRef cs); 17691 17692 void CGContextSetRGBStrokeColor(CGContextRef c, 17693 double red, double green, double blue, 17694 double alpha); 17695 void CGContextSetRGBFillColor(CGContextRef c, 17696 double red, double green, double blue, 17697 double alpha); 17698 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 17699 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, 17700 const(char)* str, size_t length); 17701 void CGContextStrokeLineSegments(CGContextRef c, 17702 const(CGPoint)* points, size_t count); 17703 17704 void CGContextBeginPath(CGContextRef c); 17705 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 17706 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 17707 void CGContextAddArc(CGContextRef c, double x, double y, double radius, 17708 double startAngle, double endAngle, long clockwise); 17709 void CGContextAddRect(CGContextRef c, CGRect rect); 17710 void CGContextAddLines(CGContextRef c, 17711 const(CGPoint)* points, size_t count); 17712 void CGContextSaveGState(CGContextRef c); 17713 void CGContextRestoreGState(CGContextRef c); 17714 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, 17715 ulong textEncoding); 17716 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 17717 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 17718 17719 void CGImageRelease(CGImageRef image); 17720 } 17721 17722 private: 17723 // A convenient method to create a CFString (=NSString) from a D string. 17724 CFStringRef createCFString(string str) { 17725 return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length, 17726 kCFStringEncodingUTF8, false); 17727 } 17728 17729 // Objective-C calls. 17730 RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) { 17731 auto _cmd = sel_registerName(selector.ptr); 17732 alias extern(C) RetType function(id, SEL, T) ExpectedType; 17733 return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args); 17734 } 17735 RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) { 17736 auto _cmd = sel_registerName(selector.ptr); 17737 auto cls = objc_getClass(className); 17738 alias extern(C) RetType function(id, SEL, T) ExpectedType; 17739 return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args); 17740 } 17741 RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) { 17742 return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args); 17743 } 17744 17745 alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay; 17746 alias objc_msgSend_classMethod!("alloc", id) alloc; 17747 alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:", 17748 id, NSRect, size_t, size_t, BOOL) initWithContentRect; 17749 alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle; 17750 alias objc_msgSend_specialized!("center", void) center; 17751 alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame; 17752 alias objc_msgSend_specialized!("setContentView:", void, id) setContentView; 17753 alias objc_msgSend_specialized!("release", void) release; 17754 alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor; 17755 alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor; 17756 alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront; 17757 alias objc_msgSend_specialized!("invalidate", void) invalidate; 17758 alias objc_msgSend_specialized!("close", void) close; 17759 alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:", 17760 id, double, id, SEL, id, BOOL) scheduledTimer; 17761 alias objc_msgSend_specialized!("run", void) run; 17762 alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext", 17763 id) currentNSGraphicsContext; 17764 alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort; 17765 alias objc_msgSend_specialized!("characters", CFStringRef) characters; 17766 alias objc_msgSend_specialized!("superclass", Class) superclass; 17767 alias objc_msgSend_specialized!("init", id) init; 17768 alias objc_msgSend_specialized!("addItem:", void, id) addItem; 17769 alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu; 17770 alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:", 17771 id, CFStringRef, SEL, CFStringRef) initWithTitle; 17772 alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu; 17773 alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate; 17774 alias objc_msgSend_specialized!("activateIgnoringOtherApps:", 17775 void, BOOL) activateIgnoringOtherApps; 17776 alias objc_msgSend_classMethod!("NSApplication", "sharedApplication", 17777 id) sharedNSApplication; 17778 alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy; 17779 } else static assert(0, "Unsupported operating system"); 17780 17781 17782 version(OSXCocoa) { 17783 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 17784 // 17785 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 17786 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 17787 // 17788 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 17789 // Probably won't even fully compile right now 17790 17791 import std.math : PI; // OSX Only 17792 import std.algorithm : map; // OSX Only 17793 import std.array : array; // OSX Only 17794 17795 alias SimpleWindow NativeWindowHandle; 17796 alias void delegate(id) NativeEventHandler; 17797 17798 __gshared Ivar simpleWindowIvar; 17799 17800 enum KEY_ESCAPE = 27; 17801 17802 mixin template NativeImageImplementation() { 17803 CGContextRef context; 17804 ubyte* rawData; 17805 final: 17806 17807 void convertToRgbaBytes(ubyte[] where) { 17808 assert(where.length == this.width * this.height * 4); 17809 17810 // if rawData had a length.... 17811 //assert(rawData.length == where.length); 17812 for(long idx = 0; idx < where.length; idx += 4) { 17813 auto alpha = rawData[idx + 3]; 17814 if(alpha == 255) { 17815 where[idx + 0] = rawData[idx + 0]; // r 17816 where[idx + 1] = rawData[idx + 1]; // g 17817 where[idx + 2] = rawData[idx + 2]; // b 17818 where[idx + 3] = rawData[idx + 3]; // a 17819 } else { 17820 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 17821 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 17822 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 17823 where[idx + 3] = rawData[idx + 3]; // a 17824 17825 } 17826 } 17827 } 17828 17829 void setFromRgbaBytes(in ubyte[] where) { 17830 // FIXME: this is probably wrong 17831 assert(where.length == this.width * this.height * 4); 17832 17833 // if rawData had a length.... 17834 //assert(rawData.length == where.length); 17835 for(long idx = 0; idx < where.length; idx += 4) { 17836 auto alpha = rawData[idx + 3]; 17837 if(alpha == 255) { 17838 rawData[idx + 0] = where[idx + 0]; // r 17839 rawData[idx + 1] = where[idx + 1]; // g 17840 rawData[idx + 2] = where[idx + 2]; // b 17841 rawData[idx + 3] = where[idx + 3]; // a 17842 } else { 17843 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 17844 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 17845 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 17846 rawData[idx + 3] = where[idx + 3]; // a 17847 17848 } 17849 } 17850 } 17851 17852 17853 void createImage(int width, int height, bool forcexshm=false) { 17854 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 17855 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 17856 colorSpace, 17857 kCGImageAlphaPremultipliedLast 17858 |kCGBitmapByteOrder32Big); 17859 CGColorSpaceRelease(colorSpace); 17860 rawData = CGBitmapContextGetData(context); 17861 } 17862 void dispose() { 17863 CGContextRelease(context); 17864 } 17865 17866 void setPixel(int x, int y, Color c) { 17867 auto offset = (y * width + x) * 4; 17868 if (c.a == 255) { 17869 rawData[offset + 0] = c.r; 17870 rawData[offset + 1] = c.g; 17871 rawData[offset + 2] = c.b; 17872 rawData[offset + 3] = c.a; 17873 } else { 17874 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 17875 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 17876 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 17877 rawData[offset + 3] = c.a; 17878 } 17879 } 17880 } 17881 17882 mixin template NativeScreenPainterImplementation() { 17883 CGContextRef context; 17884 ubyte[4] _outlineComponents; 17885 id view; 17886 17887 void create(NativeWindowHandle window) { 17888 context = window.drawingContext; 17889 view = window.view; 17890 } 17891 17892 void dispose() { 17893 setNeedsDisplay(view, true); 17894 } 17895 17896 bool manualInvalidations; 17897 void invalidateRect(Rectangle invalidRect) { } 17898 17899 // NotYetImplementedException 17900 Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); } 17901 void rasterOp(RasterOp op) {} 17902 Pen _activePen; 17903 Color _fillColor; 17904 Rectangle _clipRectangle; 17905 void setClipRectangle(int, int, int, int) {} 17906 void setFont(OperatingSystemFont) {} 17907 int fontHeight() { return 14; } 17908 17909 // end 17910 17911 void pen(Pen pen) { 17912 _activePen = pen; 17913 auto color = pen.color; // FIXME 17914 double alphaComponent = color.a/255.0f; 17915 CGContextSetRGBStrokeColor(context, 17916 color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 17917 17918 if (color.a != 255) { 17919 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 17920 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 17921 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 17922 _outlineComponents[3] = color.a; 17923 } else { 17924 _outlineComponents[0] = color.r; 17925 _outlineComponents[1] = color.g; 17926 _outlineComponents[2] = color.b; 17927 _outlineComponents[3] = color.a; 17928 } 17929 } 17930 17931 @property void fillColor(Color color) { 17932 CGContextSetRGBFillColor(context, 17933 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 17934 } 17935 17936 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 17937 // NotYetImplementedException for upper left/width/height 17938 auto cgImage = CGBitmapContextCreateImage(image.context); 17939 auto size = CGSize(CGBitmapContextGetWidth(image.context), 17940 CGBitmapContextGetHeight(image.context)); 17941 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 17942 CGImageRelease(cgImage); 17943 } 17944 17945 version(OSXCocoa) {} else // NotYetImplementedException 17946 void drawPixmap(Sprite image, int x, int y) { 17947 // FIXME: is this efficient? 17948 auto cgImage = CGBitmapContextCreateImage(image.context); 17949 auto size = CGSize(CGBitmapContextGetWidth(image.context), 17950 CGBitmapContextGetHeight(image.context)); 17951 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 17952 CGImageRelease(cgImage); 17953 } 17954 17955 17956 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 17957 // FIXME: alignment 17958 if (_outlineComponents[3] != 0) { 17959 CGContextSaveGState(context); 17960 auto invAlpha = 1.0f/_outlineComponents[3]; 17961 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 17962 _outlineComponents[1]*invAlpha, 17963 _outlineComponents[2]*invAlpha, 17964 _outlineComponents[3]/255.0f); 17965 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 17966 // auto cfstr = cast(id)createCFString(text); 17967 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 17968 // NSPoint(x, y), null); 17969 // CFRelease(cfstr); 17970 CGContextRestoreGState(context); 17971 } 17972 } 17973 17974 void drawPixel(int x, int y) { 17975 auto rawData = CGBitmapContextGetData(context); 17976 auto width = CGBitmapContextGetWidth(context); 17977 auto height = CGBitmapContextGetHeight(context); 17978 auto offset = ((height - y - 1) * width + x) * 4; 17979 rawData[offset .. offset+4] = _outlineComponents; 17980 } 17981 17982 void drawLine(int x1, int y1, int x2, int y2) { 17983 CGPoint[2] linePoints; 17984 linePoints[0] = CGPoint(x1, y1); 17985 linePoints[1] = CGPoint(x2, y2); 17986 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 17987 } 17988 17989 void drawRectangle(int x, int y, int width, int height) { 17990 CGContextBeginPath(context); 17991 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 17992 CGContextAddRect(context, rect); 17993 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17994 } 17995 17996 void drawEllipse(int x1, int y1, int x2, int y2) { 17997 CGContextBeginPath(context); 17998 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 17999 CGContextAddEllipseInRect(context, rect); 18000 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18001 } 18002 18003 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 18004 // @@@BUG@@@ Does not support elliptic arc (width != height). 18005 CGContextBeginPath(context); 18006 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 18007 start*PI/(180*64), finish*PI/(180*64), 0); 18008 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18009 } 18010 18011 void drawPolygon(Point[] intPoints) { 18012 CGContextBeginPath(context); 18013 auto points = array(map!(CGPoint.fromTuple)(intPoints)); 18014 CGContextAddLines(context, points.ptr, points.length); 18015 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 18016 } 18017 } 18018 18019 mixin template NativeSimpleWindowImplementation() { 18020 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 18021 synchronized { 18022 if (NSApp == null) initializeApp(); 18023 } 18024 18025 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 18026 18027 // create the window. 18028 window = initWithContentRect(alloc("NSWindow"), 18029 contentRect, 18030 NSTitledWindowMask 18031 |NSClosableWindowMask 18032 |NSMiniaturizableWindowMask 18033 |NSResizableWindowMask, 18034 NSBackingStoreBuffered, 18035 true); 18036 18037 // set the title & move the window to center. 18038 auto windowTitle = createCFString(title); 18039 setTitle(window, windowTitle); 18040 CFRelease(windowTitle); 18041 center(window); 18042 18043 // create area to draw on. 18044 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18045 drawingContext = CGBitmapContextCreate(null, width, height, 18046 8, 4*width, colorSpace, 18047 kCGImageAlphaPremultipliedLast 18048 |kCGBitmapByteOrder32Big); 18049 CGColorSpaceRelease(colorSpace); 18050 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 18051 auto matrix = CGContextGetTextMatrix(drawingContext); 18052 matrix.c = -matrix.c; 18053 matrix.d = -matrix.d; 18054 CGContextSetTextMatrix(drawingContext, matrix); 18055 18056 // create the subview that things will be drawn on. 18057 view = initWithFrame(alloc("SDGraphicsView"), contentRect); 18058 setContentView(window, view); 18059 object_setIvar(view, simpleWindowIvar, cast(id)this); 18060 release(view); 18061 18062 setBackgroundColor(window, whiteNSColor); 18063 makeKeyAndOrderFront(window, null); 18064 } 18065 void dispose() { 18066 closeWindow(); 18067 release(window); 18068 } 18069 void closeWindow() { 18070 invalidate(timer); 18071 .close(window); 18072 } 18073 18074 ScreenPainter getPainter(bool manualInvalidations) { 18075 return ScreenPainter(this, this, manualInvalidations); 18076 } 18077 18078 id window; 18079 id timer; 18080 id view; 18081 CGContextRef drawingContext; 18082 } 18083 18084 extern(C) { 18085 private: 18086 BOOL returnTrue3(id self, SEL _cmd, id app) { 18087 return true; 18088 } 18089 BOOL returnTrue2(id self, SEL _cmd) { 18090 return true; 18091 } 18092 18093 void pulse(id self, SEL _cmd) { 18094 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 18095 simpleWindow.handlePulse(); 18096 setNeedsDisplay(self, true); 18097 } 18098 void drawRect(id self, SEL _cmd, NSRect rect) { 18099 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 18100 auto curCtx = graphicsPort(currentNSGraphicsContext); 18101 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 18102 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), 18103 CGBitmapContextGetHeight(simpleWindow.drawingContext)); 18104 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 18105 CGImageRelease(cgImage); 18106 } 18107 void keyDown(id self, SEL _cmd, id event) { 18108 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 18109 18110 // the event may have multiple characters, and we send them all at 18111 // once. 18112 if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) { 18113 auto chars = characters(event); 18114 auto range = CFRange(0, CFStringGetLength(chars)); 18115 auto buffer = new char[range.length*3]; 18116 long actualLength; 18117 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false, 18118 buffer.ptr, cast(int) buffer.length, &actualLength); 18119 foreach (dchar dc; buffer[0..actualLength]) { 18120 if (simpleWindow.handleCharEvent) 18121 simpleWindow.handleCharEvent(dc); 18122 // NotYetImplementedException 18123 //if (simpleWindow.handleKeyEvent) 18124 //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp? 18125 } 18126 } 18127 18128 // the event's 'keyCode' is hardware-dependent. I don't think people 18129 // will like it. Let's leave it to the native handler. 18130 18131 // perform the default action. 18132 18133 // so the default action is to make a bomp sound and i dont want that 18134 // sooooooooo yeah not gonna do that. 18135 18136 //auto superData = objc_super(self, superclass(self)); 18137 //alias extern(C) void function(objc_super*, SEL, id) T; 18138 //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event); 18139 } 18140 } 18141 18142 // initialize the app so that it can be interacted with the user. 18143 // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html 18144 private void initializeApp() { 18145 // push an autorelease pool to avoid leaking. 18146 init(alloc("NSAutoreleasePool")); 18147 18148 // create a new NSApp instance 18149 sharedNSApplication; 18150 setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular); 18151 18152 // create the "Quit" menu. 18153 auto menuBar = init(alloc("NSMenu")); 18154 auto appMenuItem = init(alloc("NSMenuItem")); 18155 addItem(menuBar, appMenuItem); 18156 setMainMenu(NSApp, menuBar); 18157 release(appMenuItem); 18158 release(menuBar); 18159 18160 auto appMenu = init(alloc("NSMenu")); 18161 auto quitTitle = createCFString("Quit"); 18162 auto q = createCFString("q"); 18163 auto quitItem = initWithTitle(alloc("NSMenuItem"), 18164 quitTitle, sel_registerName("terminate:"), q); 18165 addItem(appMenu, quitItem); 18166 setSubmenu(appMenuItem, appMenu); 18167 release(quitItem); 18168 release(appMenu); 18169 CFRelease(q); 18170 CFRelease(quitTitle); 18171 18172 // assign a delegate for the application, allow it to quit when the last 18173 // window is closed. 18174 auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), 18175 "SDWindowCloseDelegate", 0); 18176 class_addMethod(delegateClass, 18177 sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"), 18178 &returnTrue3, "c@:@"); 18179 objc_registerClassPair(delegateClass); 18180 18181 auto appDelegate = init(alloc("SDWindowCloseDelegate")); 18182 setDelegate(NSApp, appDelegate); 18183 activateIgnoringOtherApps(NSApp, true); 18184 18185 // create a new view that draws the graphics and respond to keyDown 18186 // events. 18187 auto viewClass = objc_allocateClassPair(objc_getClass("NSView"), 18188 "SDGraphicsView", (void*).sizeof); 18189 class_addIvar(viewClass, "simpledisplay_simpleWindow", 18190 (void*).sizeof, (void*).alignof, "^v"); 18191 class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"), 18192 &pulse, "v@:"); 18193 class_addMethod(viewClass, sel_registerName("drawRect:"), 18194 &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); 18195 class_addMethod(viewClass, sel_registerName("isFlipped"), 18196 &returnTrue2, "c@:"); 18197 class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"), 18198 &returnTrue2, "c@:"); 18199 class_addMethod(viewClass, sel_registerName("keyDown:"), 18200 &keyDown, "v@:@"); 18201 objc_registerClassPair(viewClass); 18202 simpleWindowIvar = class_getInstanceVariable(viewClass, 18203 "simpledisplay_simpleWindow"); 18204 } 18205 } 18206 18207 version(without_opengl) {} else 18208 extern(System) nothrow @nogc { 18209 //enum uint GL_VERSION = 0x1F02; 18210 //const(char)* glGetString (/*GLenum*/uint); 18211 version(X11) { 18212 static if (!SdpyIsUsingIVGLBinds) { 18213 18214 enum GLX_X_RENDERABLE = 0x8012; 18215 enum GLX_DRAWABLE_TYPE = 0x8010; 18216 enum GLX_RENDER_TYPE = 0x8011; 18217 enum GLX_X_VISUAL_TYPE = 0x22; 18218 enum GLX_TRUE_COLOR = 0x8002; 18219 enum GLX_WINDOW_BIT = 0x00000001; 18220 enum GLX_RGBA_BIT = 0x00000001; 18221 enum GLX_COLOR_INDEX_BIT = 0x00000002; 18222 enum GLX_SAMPLE_BUFFERS = 0x186a0; 18223 enum GLX_SAMPLES = 0x186a1; 18224 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18225 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18226 } 18227 18228 // GLX_EXT_swap_control 18229 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 18230 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 18231 18232 //k8: ugly code to prevent warnings when sdpy is compiled into .a 18233 extern(System) { 18234 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 18235 } 18236 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 18237 18238 // this made public so we don't have to get it again and again 18239 public bool glXCreateContextAttribsARB_present () { 18240 if (glXCreateContextAttribsARBFn is cast(void*)1) { 18241 // get it 18242 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 18243 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 18244 } 18245 return (glXCreateContextAttribsARBFn !is null); 18246 } 18247 18248 // this made public so we don't have to get it again and again 18249 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 18250 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 18251 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 18252 } 18253 18254 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 18255 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 18256 18257 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 18258 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 18259 if (_glx_swapInterval_fn is null) { 18260 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 18261 if (_glx_swapInterval_fn is null) { 18262 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 18263 return; 18264 } 18265 version(sdddd) { debug writeln("glXSwapIntervalEXT found!"); } 18266 } 18267 18268 if(glXSwapIntervalMESA is null) { 18269 // it seems to require both to actually take effect on many computers 18270 // idk why 18271 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 18272 if(glXSwapIntervalMESA is null) 18273 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 18274 } 18275 18276 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 18277 glXSwapIntervalMESA(wait ? 1 : 0); 18278 18279 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 18280 } 18281 } else version(Windows) { 18282 static if (!SdpyIsUsingIVGLBinds) { 18283 enum GL_TRUE = 1; 18284 enum GL_FALSE = 0; 18285 alias int GLint; 18286 18287 public void* glbindGetProcAddress (const(char)* name) { 18288 void* res = wglGetProcAddress(name); 18289 if (res is null) { 18290 /+ 18291 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 18292 import core.sys.windows.windef, core.sys.windows.winbase; 18293 __gshared HINSTANCE dll = null; 18294 if (dll is null) { 18295 dll = LoadLibraryA("opengl32.dll"); 18296 if (dll is null) return null; // <32, but idc 18297 } 18298 res = GetProcAddress(dll, name); 18299 +/ 18300 res = GetProcAddress(gl.libHandle, name); 18301 } 18302 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 18303 return res; 18304 } 18305 } 18306 18307 18308 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 18309 void wglSetVSync(bool wait) { 18310 if(wglSwapIntervalEXT is null) { 18311 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 18312 if(wglSwapIntervalEXT is null) 18313 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 18314 } 18315 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 18316 return; 18317 18318 wglSwapIntervalEXT(wait ? 1 : 0); 18319 } 18320 18321 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18322 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18323 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 18324 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 18325 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 18326 18327 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 18328 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 18329 18330 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 18331 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 18332 18333 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 18334 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 18335 18336 void wglInitOtherFunctions () { 18337 if (wglCreateContextAttribsARB is null) { 18338 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 18339 } 18340 } 18341 } 18342 18343 static if (!SdpyIsUsingIVGLBinds) { 18344 18345 interface GL { 18346 extern(System) @nogc nothrow: 18347 18348 void glGetIntegerv(int, void*); 18349 void glMatrixMode(int); 18350 void glPushMatrix(); 18351 void glLoadIdentity(); 18352 void glOrtho(double, double, double, double, double, double); 18353 void glFrustum(double, double, double, double, double, double); 18354 18355 void glPopMatrix(); 18356 void glEnable(int); 18357 void glDisable(int); 18358 void glClear(int); 18359 void glBegin(int); 18360 void glVertex2f(float, float); 18361 void glVertex3f(float, float, float); 18362 void glEnd(); 18363 void glColor3b(byte, byte, byte); 18364 void glColor3ub(ubyte, ubyte, ubyte); 18365 void glColor4b(byte, byte, byte, byte); 18366 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 18367 void glColor3i(int, int, int); 18368 void glColor3ui(uint, uint, uint); 18369 void glColor4i(int, int, int, int); 18370 void glColor4ui(uint, uint, uint, uint); 18371 void glColor3f(float, float, float); 18372 void glColor4f(float, float, float, float); 18373 void glTranslatef(float, float, float); 18374 void glScalef(float, float, float); 18375 version(X11) { 18376 void glSecondaryColor3b(byte, byte, byte); 18377 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 18378 void glSecondaryColor3i(int, int, int); 18379 void glSecondaryColor3ui(uint, uint, uint); 18380 void glSecondaryColor3f(float, float, float); 18381 } 18382 18383 void glDrawElements(int, int, int, void*); 18384 18385 void glRotatef(float, float, float, float); 18386 18387 uint glGetError(); 18388 18389 void glDeleteTextures(int, uint*); 18390 18391 18392 void glRasterPos2i(int, int); 18393 void glDrawPixels(int, int, uint, uint, void*); 18394 void glClearColor(float, float, float, float); 18395 18396 18397 void glPixelStorei(uint, int); 18398 18399 void glGenTextures(uint, uint*); 18400 void glBindTexture(int, int); 18401 void glTexParameteri(uint, uint, int); 18402 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18403 void glTexImage2D(int, int, int, int, int, int, int, int, scope const void*); 18404 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 18405 /*GLsizei*/int width, /*GLsizei*/int height, 18406 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18407 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18408 18409 void glLineWidth(int); 18410 18411 18412 void glTexCoord2f(float, float); 18413 void glVertex2i(int, int); 18414 void glBlendFunc (int, int); 18415 void glDepthFunc (int); 18416 void glViewport(int, int, int, int); 18417 18418 void glClearDepth(double); 18419 18420 void glReadBuffer(uint); 18421 void glReadPixels(int, int, int, int, int, int, void*); 18422 18423 void glFlush(); 18424 void glFinish(); 18425 18426 version(Windows) { 18427 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 18428 HGLRC wglCreateContext(HDC); 18429 HGLRC wglCreateLayerContext(HDC, int); 18430 BOOL wglDeleteContext(HGLRC); 18431 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 18432 HGLRC wglGetCurrentContext(); 18433 HDC wglGetCurrentDC(); 18434 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 18435 PROC wglGetProcAddress(LPCSTR); 18436 BOOL wglMakeCurrent(HDC, HGLRC); 18437 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 18438 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 18439 BOOL wglShareLists(HGLRC, HGLRC); 18440 BOOL wglSwapLayerBuffers(HDC, UINT); 18441 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 18442 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 18443 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18444 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18445 } 18446 18447 } 18448 18449 interface GL3 { 18450 extern(System) @nogc nothrow: 18451 18452 void glGenVertexArrays(GLsizei, GLuint*); 18453 void glBindVertexArray(GLuint); 18454 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 18455 void glGenerateMipmap(GLenum); 18456 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 18457 void glStencilMask(GLuint); 18458 void glStencilFunc(GLenum, GLint, GLuint); 18459 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18460 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18461 GLuint glCreateProgram(); 18462 GLuint glCreateShader(GLenum); 18463 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 18464 void glCompileShader(GLuint); 18465 void glGetShaderiv(GLuint, GLenum, GLint*); 18466 void glAttachShader(GLuint, GLuint); 18467 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 18468 void glLinkProgram(GLuint); 18469 void glGetProgramiv(GLuint, GLenum, GLint*); 18470 void glDeleteProgram(GLuint); 18471 void glDeleteShader(GLuint); 18472 GLint glGetUniformLocation(GLuint, const(GLchar)*); 18473 void glGenBuffers(GLsizei, GLuint*); 18474 18475 void glUniform1f(GLint location, GLfloat v0); 18476 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 18477 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 18478 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 18479 void glUniform1i(GLint location, GLint v0); 18480 void glUniform2i(GLint location, GLint v0, GLint v1); 18481 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 18482 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 18483 void glUniform1ui(GLint location, GLuint v0); 18484 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 18485 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 18486 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 18487 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 18488 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 18489 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 18490 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 18491 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 18492 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 18493 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 18494 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 18495 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 18496 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 18497 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 18498 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 18499 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18500 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18501 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18502 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18503 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18504 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18505 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18506 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18507 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18508 18509 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 18510 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 18511 void glDrawArrays(GLenum, GLint, GLsizei); 18512 void glStencilOp(GLenum, GLenum, GLenum); 18513 void glUseProgram(GLuint); 18514 void glCullFace(GLenum); 18515 void glFrontFace(GLenum); 18516 void glActiveTexture(GLenum); 18517 void glBindBuffer(GLenum, GLuint); 18518 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 18519 void glEnableVertexAttribArray(GLuint); 18520 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 18521 void glUniform1i(GLint, GLint); 18522 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 18523 void glDisableVertexAttribArray(GLuint); 18524 void glDeleteBuffers(GLsizei, const(GLuint)*); 18525 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 18526 void glLogicOp (GLenum opcode); 18527 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 18528 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 18529 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 18530 GLenum glCheckFramebufferStatus (GLenum target); 18531 void glBindFramebuffer (GLenum target, GLuint framebuffer); 18532 } 18533 18534 interface GL4 { 18535 extern(System) @nogc nothrow: 18536 18537 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 18538 /*GLsizei*/int width, /*GLsizei*/int height, 18539 uint/*GLenum*/ format, uint/*GLenum*/ type, scope const void* pixels); 18540 } 18541 18542 interface GLU { 18543 extern(System) @nogc nothrow: 18544 18545 void gluLookAt(double, double, double, double, double, double, double, double, double); 18546 void gluPerspective(double, double, double, double); 18547 18548 char* gluErrorString(uint); 18549 } 18550 18551 18552 enum GL_RED = 0x1903; 18553 enum GL_ALPHA = 0x1906; 18554 18555 enum uint GL_FRONT = 0x0404; 18556 18557 enum uint GL_BLEND = 0x0be2; 18558 enum uint GL_LEQUAL = 0x0203; 18559 18560 18561 enum uint GL_RGB = 0x1907; 18562 enum uint GL_BGRA = 0x80e1; 18563 enum uint GL_RGBA = 0x1908; 18564 enum uint GL_TEXTURE_2D = 0x0DE1; 18565 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 18566 enum uint GL_NEAREST = 0x2600; 18567 enum uint GL_LINEAR = 0x2601; 18568 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 18569 enum uint GL_TEXTURE_WRAP_S = 0x2802; 18570 enum uint GL_TEXTURE_WRAP_T = 0x2803; 18571 enum uint GL_REPEAT = 0x2901; 18572 enum uint GL_CLAMP = 0x2900; 18573 enum uint GL_CLAMP_TO_EDGE = 0x812F; 18574 enum uint GL_CLAMP_TO_BORDER = 0x812D; 18575 enum uint GL_DECAL = 0x2101; 18576 enum uint GL_MODULATE = 0x2100; 18577 enum uint GL_TEXTURE_ENV = 0x2300; 18578 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 18579 enum uint GL_REPLACE = 0x1E01; 18580 enum uint GL_LIGHTING = 0x0B50; 18581 enum uint GL_DITHER = 0x0BD0; 18582 18583 enum uint GL_NO_ERROR = 0; 18584 18585 18586 18587 enum int GL_VIEWPORT = 0x0BA2; 18588 enum int GL_MODELVIEW = 0x1700; 18589 enum int GL_TEXTURE = 0x1702; 18590 enum int GL_PROJECTION = 0x1701; 18591 enum int GL_DEPTH_TEST = 0x0B71; 18592 18593 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 18594 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 18595 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 18596 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 18597 18598 enum int GL_POINTS = 0x0000; 18599 enum int GL_LINES = 0x0001; 18600 enum int GL_LINE_LOOP = 0x0002; 18601 enum int GL_LINE_STRIP = 0x0003; 18602 enum int GL_TRIANGLES = 0x0004; 18603 enum int GL_TRIANGLE_STRIP = 5; 18604 enum int GL_TRIANGLE_FAN = 6; 18605 enum int GL_QUADS = 7; 18606 enum int GL_QUAD_STRIP = 8; 18607 enum int GL_POLYGON = 9; 18608 18609 alias GLvoid = void; 18610 alias GLboolean = ubyte; 18611 alias GLuint = uint; 18612 alias GLenum = uint; 18613 alias GLchar = char; 18614 alias GLsizei = int; 18615 alias GLfloat = float; 18616 alias GLintptr = size_t; 18617 alias GLsizeiptr = ptrdiff_t; 18618 18619 18620 enum uint GL_INVALID_ENUM = 0x0500; 18621 18622 enum uint GL_ZERO = 0; 18623 enum uint GL_ONE = 1; 18624 18625 enum uint GL_BYTE = 0x1400; 18626 enum uint GL_UNSIGNED_BYTE = 0x1401; 18627 enum uint GL_SHORT = 0x1402; 18628 enum uint GL_UNSIGNED_SHORT = 0x1403; 18629 enum uint GL_INT = 0x1404; 18630 enum uint GL_UNSIGNED_INT = 0x1405; 18631 enum uint GL_FLOAT = 0x1406; 18632 enum uint GL_2_BYTES = 0x1407; 18633 enum uint GL_3_BYTES = 0x1408; 18634 enum uint GL_4_BYTES = 0x1409; 18635 enum uint GL_DOUBLE = 0x140A; 18636 18637 enum uint GL_STREAM_DRAW = 0x88E0; 18638 18639 enum uint GL_CCW = 0x0901; 18640 18641 enum uint GL_STENCIL_TEST = 0x0B90; 18642 enum uint GL_SCISSOR_TEST = 0x0C11; 18643 18644 enum uint GL_EQUAL = 0x0202; 18645 enum uint GL_NOTEQUAL = 0x0205; 18646 18647 enum uint GL_ALWAYS = 0x0207; 18648 enum uint GL_KEEP = 0x1E00; 18649 18650 enum uint GL_INCR = 0x1E02; 18651 18652 enum uint GL_INCR_WRAP = 0x8507; 18653 enum uint GL_DECR_WRAP = 0x8508; 18654 18655 enum uint GL_CULL_FACE = 0x0B44; 18656 enum uint GL_BACK = 0x0405; 18657 18658 enum uint GL_FRAGMENT_SHADER = 0x8B30; 18659 enum uint GL_VERTEX_SHADER = 0x8B31; 18660 18661 enum uint GL_COMPILE_STATUS = 0x8B81; 18662 enum uint GL_LINK_STATUS = 0x8B82; 18663 18664 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 18665 18666 enum uint GL_STATIC_DRAW = 0x88E4; 18667 18668 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 18669 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 18670 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 18671 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 18672 18673 enum uint GL_GENERATE_MIPMAP = 0x8191; 18674 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 18675 18676 enum uint GL_TEXTURE0 = 0x84C0U; 18677 enum uint GL_TEXTURE1 = 0x84C1U; 18678 18679 enum uint GL_ARRAY_BUFFER = 0x8892; 18680 18681 enum uint GL_SRC_COLOR = 0x0300; 18682 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 18683 enum uint GL_SRC_ALPHA = 0x0302; 18684 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 18685 enum uint GL_DST_ALPHA = 0x0304; 18686 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 18687 enum uint GL_DST_COLOR = 0x0306; 18688 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 18689 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 18690 18691 enum uint GL_INVERT = 0x150AU; 18692 18693 enum uint GL_DEPTH_STENCIL = 0x84F9U; 18694 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 18695 18696 enum uint GL_FRAMEBUFFER = 0x8D40U; 18697 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 18698 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 18699 18700 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 18701 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 18702 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 18703 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 18704 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 18705 18706 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 18707 enum uint GL_CLEAR = 0x1500U; 18708 enum uint GL_COPY = 0x1503U; 18709 enum uint GL_XOR = 0x1506U; 18710 18711 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 18712 18713 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 18714 18715 } 18716 } 18717 18718 /++ 18719 History: 18720 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. 18721 +/ 18722 __gshared bool gluSuccessfullyLoaded = true; 18723 18724 version(without_opengl) {} else { 18725 static if(!SdpyIsUsingIVGLBinds) { 18726 version(Windows) { 18727 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 18728 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 18729 } else { 18730 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 18731 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 18732 } 18733 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 18734 18735 18736 shared static this() { 18737 gl.loadDynamicLibrary(); 18738 18739 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 18740 // unless those functions are actually used 18741 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 18742 glu.loadDynamicLibrary(); 18743 } 18744 } 18745 } 18746 18747 /++ 18748 Convenience method for converting D arrays to opengl buffer data 18749 18750 I would LOVE to overload it with the original glBufferData, but D won't 18751 let me since glBufferData is a function pointer :( 18752 18753 Added: August 25, 2020 (version 8.5) 18754 +/ 18755 version(without_opengl) {} else 18756 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 18757 glBufferData(target, data.length, data.ptr, usage); 18758 } 18759 18760 /+ 18761 /++ 18762 A matrix for simple uses that easily integrates with [OpenGlShader]. 18763 18764 Might not be useful to you since it only as some simple functions and 18765 probably isn't that fast. 18766 18767 Note it uses an inline static array for its storage, so copying it 18768 may be expensive. 18769 +/ 18770 struct BasicMatrix(int columns, int rows, T = float) { 18771 import core.stdc.math; 18772 18773 T[columns * rows] data = 0.0; 18774 18775 /++ 18776 Basic operations that operate *in place*. 18777 +/ 18778 void translate() { 18779 18780 } 18781 18782 /// ditto 18783 void scale() { 18784 18785 } 18786 18787 /// ditto 18788 void rotate() { 18789 18790 } 18791 18792 /++ 18793 18794 +/ 18795 static if(columns == rows) 18796 static BasicMatrix identity() { 18797 BasicMatrix m; 18798 foreach(i; 0 .. columns) 18799 data[0 + i + i * columns] = 1.0; 18800 return m; 18801 } 18802 18803 static BasicMatrix ortho() { 18804 return BasicMatrix.init; 18805 } 18806 } 18807 +/ 18808 18809 /++ 18810 Convenience class for using opengl shaders. 18811 18812 Ensure that you've loaded opengl 3+ and set your active 18813 context before trying to use this. 18814 18815 Added: August 25, 2020 (version 8.5) 18816 +/ 18817 version(without_opengl) {} else 18818 final class OpenGlShader { 18819 private int shaderProgram_; 18820 private @property void shaderProgram(int a) { 18821 shaderProgram_ = a; 18822 } 18823 /// Get the program ID for use in OpenGL functions. 18824 public @property int shaderProgram() { 18825 return shaderProgram_; 18826 } 18827 18828 /++ 18829 18830 +/ 18831 static struct Source { 18832 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 18833 string code; /// 18834 } 18835 18836 /++ 18837 Helper method to just compile some shader code and check for errors 18838 while you do glCreateShader, etc. on the outside yourself. 18839 18840 This just does `glShaderSource` and `glCompileShader` for the given code. 18841 18842 If you the OpenGlShader class constructor, you never need to call this yourself. 18843 +/ 18844 static void compile(int sid, Source code) { 18845 const(char)*[1] buffer; 18846 int[1] lengthBuffer; 18847 18848 buffer[0] = code.code.ptr; 18849 lengthBuffer[0] = cast(int) code.code.length; 18850 18851 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 18852 glCompileShader(sid); 18853 18854 int success; 18855 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 18856 if(!success) { 18857 char[512] info; 18858 int len; 18859 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 18860 18861 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 18862 } 18863 } 18864 18865 /++ 18866 Calls `glLinkProgram` and throws if error a occurs. 18867 18868 If you the OpenGlShader class constructor, you never need to call this yourself. 18869 +/ 18870 static void link(int shaderProgram) { 18871 glLinkProgram(shaderProgram); 18872 int success; 18873 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 18874 if(!success) { 18875 char[512] info; 18876 int len; 18877 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 18878 18879 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 18880 } 18881 } 18882 18883 /++ 18884 Constructs the shader object by calling `glCreateProgram`, then 18885 compiling each given [Source], and finally, linking them together. 18886 18887 Throws: on compile or link failure. 18888 +/ 18889 this(Source[] codes...) { 18890 shaderProgram = glCreateProgram(); 18891 18892 int[16] shadersBufferStack; 18893 18894 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 18895 shadersBufferStack[0 .. codes.length] : 18896 new int[](codes.length); 18897 18898 foreach(idx, code; codes) { 18899 shadersBuffer[idx] = glCreateShader(code.type); 18900 18901 compile(shadersBuffer[idx], code); 18902 18903 glAttachShader(shaderProgram, shadersBuffer[idx]); 18904 } 18905 18906 link(shaderProgram); 18907 18908 foreach(s; shadersBuffer) 18909 glDeleteShader(s); 18910 } 18911 18912 /// Calls `glUseProgram(this.shaderProgram)` 18913 void use() { 18914 glUseProgram(this.shaderProgram); 18915 } 18916 18917 /// Deletes the program. 18918 void delete_() { 18919 glDeleteProgram(shaderProgram); 18920 shaderProgram = 0; 18921 } 18922 18923 /++ 18924 [OpenGlShader.uniforms].name gives you one of these. 18925 18926 You can get the id out of it or just assign 18927 +/ 18928 static struct Uniform { 18929 /// the id passed to glUniform* 18930 int id; 18931 18932 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 18933 void opAssign(float x, float y, float z, float w) { 18934 if(id != -1) 18935 glUniform4f(id, x, y, z, w); 18936 } 18937 18938 void opAssign(float x) { 18939 if(id != -1) 18940 glUniform1f(id, x); 18941 } 18942 18943 void opAssign(float x, float y) { 18944 if(id != -1) 18945 glUniform2f(id, x, y); 18946 } 18947 18948 void opAssign(T)(T t) { 18949 t.glUniform(id); 18950 } 18951 } 18952 18953 static struct UniformsHelper { 18954 OpenGlShader _shader; 18955 18956 @property Uniform opDispatch(string name)() { 18957 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 18958 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 18959 //if(i == -1) 18960 //throw new Exception("Could not find uniform " ~ name); 18961 return Uniform(i); 18962 } 18963 18964 @property void opDispatch(string name, T)(T t) { 18965 Uniform f = this.opDispatch!name; 18966 t.glUniform(f); 18967 } 18968 } 18969 18970 /++ 18971 Gives access to the uniforms through dot access. 18972 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 18973 +/ 18974 @property UniformsHelper uniforms() { return UniformsHelper(this); } 18975 } 18976 18977 version(without_opengl) {} else { 18978 /++ 18979 A static container of experimental types and value constructors for opengl 3+ shaders. 18980 18981 18982 You can declare variables like: 18983 18984 ``` 18985 OGL.vec3f something; 18986 ``` 18987 18988 But generally it would be used with [OpenGlShader]'s uniform helpers like 18989 18990 ``` 18991 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 18992 ``` 18993 18994 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 18995 18996 18997 History: 18998 Added December 7, 2021. Not yet stable. 18999 +/ 19000 final class OGL { 19001 static: 19002 19003 private template typeFromSpecifier(string specifier) { 19004 static if(specifier == "f") 19005 alias typeFromSpecifier = GLfloat; 19006 else static if(specifier == "i") 19007 alias typeFromSpecifier = GLint; 19008 else static if(specifier == "ui") 19009 alias typeFromSpecifier = GLuint; 19010 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 19011 } 19012 19013 private template CommonType(T...) { 19014 static if(T.length == 1) 19015 alias CommonType = T[0]; 19016 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 19017 alias CommonType = CommonType!(C, T[2 .. $]); 19018 } 19019 19020 private template typesToSpecifier(T...) { 19021 static if(is(CommonType!T == float)) 19022 enum typesToSpecifier = "f"; 19023 else static if(is(CommonType!T == int)) 19024 enum typesToSpecifier = "i"; 19025 else static if(is(CommonType!T == uint)) 19026 enum typesToSpecifier = "ui"; 19027 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 19028 } 19029 19030 private template genNames(size_t dim, size_t dim2 = 0) { 19031 string helper() { 19032 string s; 19033 if(dim2) { 19034 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;"; 19035 } else { 19036 if(dim > 0) s ~= "type x = 0;"; 19037 if(dim > 1) s ~= "type y = 0;"; 19038 if(dim > 2) s ~= "type z = 0;"; 19039 if(dim > 3) s ~= "type w = 0;"; 19040 } 19041 return s; 19042 } 19043 19044 enum genNames = helper(); 19045 } 19046 19047 // there's vec, arrays of vec, mat, and arrays of mat 19048 template opDispatch(string name) 19049 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 19050 { 19051 static if(name[4] == 'x') { 19052 enum dimX = cast(int) (name[3] - '0'); 19053 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 19054 19055 enum dimY = cast(int) (name[5] - '0'); 19056 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 19057 19058 enum isArray = name[$ - 1] == 'v'; 19059 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 19060 alias type = typeFromSpecifier!typeSpecifier; 19061 } else { 19062 enum dim = cast(int) (name[3] - '0'); 19063 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 19064 enum isArray = name[$ - 1] == 'v'; 19065 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 19066 alias type = typeFromSpecifier!typeSpecifier; 19067 } 19068 19069 align(1) 19070 struct opDispatch { 19071 align(1): 19072 static if(name[4] == 'x') 19073 mixin(genNames!(dimX, dimY)); 19074 else 19075 mixin(genNames!dim); 19076 19077 private void glUniform(OpenGlShader.Uniform assignTo) { 19078 glUniform(assignTo.id); 19079 } 19080 private void glUniform(int assignTo) { 19081 static if(name[4] == 'x') { 19082 // FIXME 19083 pragma(msg, "This matrix uniform helper has never been tested!!!!"); 19084 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr); 19085 } else 19086 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 19087 } 19088 } 19089 } 19090 19091 auto vec(T...)(T members) { 19092 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 19093 } 19094 } 19095 } 19096 19097 version(linux) { 19098 version(with_eventloop) {} else { 19099 private int epollFd = -1; 19100 void prepareEventLoop() { 19101 if(epollFd != -1) 19102 return; // already initialized, no need to do it again 19103 import ep = core.sys.linux.epoll; 19104 19105 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 19106 if(epollFd == -1) 19107 throw new Exception("epoll create failure"); 19108 } 19109 } 19110 } else version(Posix) { 19111 void prepareEventLoop() {} 19112 } 19113 19114 version(X11) { 19115 import core.stdc.locale : LC_ALL; // rdmd fix 19116 __gshared bool sdx_isUTF8Locale; 19117 19118 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 19119 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 19120 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 19121 // anal magic is here. I (Ketmar) hope you like it. 19122 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 19123 // always return correct unicode symbols. The detection is here 'cause user can change locale 19124 // later. 19125 19126 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 19127 shared static this () { 19128 if(!librariesSuccessfullyLoaded) 19129 return; 19130 19131 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 19132 19133 // this doesn't hurt; it may add some locking, but the speed is still 19134 // allows doing 60 FPS videogames; also, ignore the result, as most 19135 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 19136 // never seen this failing). 19137 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 19138 19139 setlocale(LC_ALL, ""); 19140 // check if out locale is UTF-8 19141 auto lct = setlocale(LC_CTYPE, null); 19142 if (lct is null) { 19143 sdx_isUTF8Locale = false; 19144 } else { 19145 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 19146 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 19147 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 19148 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 19149 { 19150 sdx_isUTF8Locale = true; 19151 break; 19152 } 19153 } 19154 } 19155 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 19156 } 19157 } 19158 19159 class ExperimentalTextComponent2 { 19160 /+ 19161 Stage 1: get it working monospace 19162 Stage 2: use proportional font 19163 Stage 3: allow changes in inline style 19164 Stage 4: allow new fonts and sizes in the middle 19165 Stage 5: optimize gap buffer 19166 Stage 6: optimize layout 19167 Stage 7: word wrap 19168 Stage 8: justification 19169 Stage 9: editing, selection, etc. 19170 19171 Operations: 19172 insert text 19173 overstrike text 19174 select 19175 cut 19176 modify 19177 +/ 19178 19179 /++ 19180 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. 19181 +/ 19182 this(SimpleWindow window) { 19183 this.window = window; 19184 } 19185 19186 private SimpleWindow window; 19187 19188 19189 /++ 19190 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 19191 representing the internal parts. The first pass is focused on the x parameter, then the 19192 renderer is responsible for going back to the parts in the current line and calling 19193 adjustDownForAscent to change the y params. 19194 +/ 19195 static interface ComponentRenderHelper { 19196 19197 /+ 19198 When you do an edit, possibly stuff on the same line previously need to move (to adjust 19199 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 19200 to move (adjust y to make room for new line) until you get back to the same position, 19201 then you can stop - if one thing is unchanged, nothing after it is changed too. 19202 19203 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 19204 once you reach something that is unchanged, you can stop. 19205 +/ 19206 19207 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 19208 19209 int ascent() const; 19210 int descent() const; 19211 19212 int advance() const; 19213 19214 bool endsWithExplititLineBreak() const; 19215 } 19216 19217 static interface RenderResult { 19218 /++ 19219 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. 19220 +/ 19221 void popFront(); 19222 @property bool empty() const; 19223 @property ComponentRenderHelper front() const; 19224 19225 void repositionForNextLine(Point baseline, int availableWidth); 19226 } 19227 19228 static interface ComponentInFlow { 19229 void draw(ScreenPainter painter); 19230 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 19231 19232 bool startsWithExplicitLineBreak() const; 19233 } 19234 19235 static class TextFlowComponent : ComponentInFlow { 19236 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 19237 19238 Color foreground; 19239 Color background; 19240 19241 OperatingSystemFont font; // should NEVER be null 19242 19243 ubyte attributes; // underline, strike through, display on new block 19244 19245 version(Windows) 19246 const(wchar)[] content; 19247 else 19248 const(char)[] content; // this should NEVER have a newline, except at the end 19249 19250 RenderedComponent[] rendered; // entirely controlled by [rerender] 19251 19252 // could prolly put some spacing around it too like margin / padding 19253 19254 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 19255 in { assert(font !is null); 19256 assert(!font.isNull); } 19257 do 19258 { 19259 this.foreground = f; 19260 this.background = b; 19261 this.font = font; 19262 19263 this.attributes = attr; 19264 version(Windows) { 19265 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 19266 auto sz = sizeOfConvertedWstring(c, conversionFlags); 19267 auto buffer = new wchar[](sz); 19268 this.content = makeWindowsString(c, buffer, conversionFlags); 19269 } else { 19270 this.content = c.dup; 19271 } 19272 } 19273 19274 void draw(ScreenPainter painter) { 19275 painter.setFont(this.font); 19276 painter.outlineColor = this.foreground; 19277 painter.fillColor = Color.transparent; 19278 foreach(rendered; this.rendered) { 19279 // the component works in term of baseline, 19280 // but the painter works in term of upper left bounding box 19281 // so need to translate that 19282 19283 if(this.background.a) { 19284 painter.fillColor = this.background; 19285 painter.outlineColor = this.background; 19286 19287 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 19288 19289 painter.outlineColor = this.foreground; 19290 painter.fillColor = Color.transparent; 19291 } 19292 19293 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 19294 19295 // FIXME: strike through, underline, highlight selection, etc. 19296 } 19297 } 19298 } 19299 19300 // I could split the parts into words on render 19301 // for easier word-wrap, each one being an unbreakable "inline-block" 19302 private TextFlowComponent[] parts; 19303 private int needsRerenderFrom; 19304 19305 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 19306 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 19307 parts ~= new TextFlowComponent(f, b, font, attr, c); 19308 } 19309 19310 static struct RenderedComponent { 19311 int startX; 19312 int startY; 19313 short width; 19314 // 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! 19315 // for individual chars in here you've gotta process on demand 19316 version(Windows) 19317 const(wchar)[] slice; 19318 else 19319 const(char)[] slice; 19320 } 19321 19322 19323 void rerender(Rectangle boundingBox) { 19324 Point baseline = boundingBox.upperLeft; 19325 19326 this.boundingBox.left = boundingBox.left; 19327 this.boundingBox.top = boundingBox.top; 19328 19329 auto remainingParts = parts; 19330 19331 int largestX; 19332 19333 19334 foreach(part; parts) 19335 part.font.prepareContext(window); 19336 scope(exit) 19337 foreach(part; parts) 19338 part.font.releaseContext(); 19339 19340 calculateNextLine: 19341 19342 int nextLineHeight = 0; 19343 int nextBiggestDescent = 0; 19344 19345 foreach(part; remainingParts) { 19346 auto height = part.font.ascent; 19347 if(height > nextLineHeight) 19348 nextLineHeight = height; 19349 if(part.font.descent > nextBiggestDescent) 19350 nextBiggestDescent = part.font.descent; 19351 if(part.content.length && part.content[$-1] == '\n') 19352 break; 19353 } 19354 19355 baseline.y += nextLineHeight; 19356 auto lineStart = baseline; 19357 19358 while(remainingParts.length) { 19359 remainingParts[0].rendered = null; 19360 19361 bool eol; 19362 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 19363 eol = true; 19364 19365 // FIXME: word wrap 19366 auto font = remainingParts[0].font; 19367 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 19368 auto width = font.stringWidth(slice, window); 19369 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 19370 19371 remainingParts = remainingParts[1 .. $]; 19372 baseline.x += width; 19373 19374 if(eol) { 19375 baseline.y += nextBiggestDescent; 19376 if(baseline.x > largestX) 19377 largestX = baseline.x; 19378 baseline.x = lineStart.x; 19379 goto calculateNextLine; 19380 } 19381 } 19382 19383 if(baseline.x > largestX) 19384 largestX = baseline.x; 19385 19386 this.boundingBox.right = largestX; 19387 this.boundingBox.bottom = baseline.y; 19388 } 19389 19390 // you must call rerender first! 19391 void draw(ScreenPainter painter) { 19392 foreach(part; parts) { 19393 part.draw(painter); 19394 } 19395 } 19396 19397 struct IdentifyResult { 19398 TextFlowComponent part; 19399 int charIndexInPart; 19400 int totalCharIndex = -1; // if this is -1, it just means the end 19401 19402 Rectangle boundingBox; 19403 } 19404 19405 IdentifyResult identify(Point pt, bool exact = false) { 19406 if(parts.length == 0) 19407 return IdentifyResult(null, 0); 19408 19409 if(pt.y < boundingBox.top) { 19410 if(exact) 19411 return IdentifyResult(null, 1); 19412 return IdentifyResult(parts[0], 0); 19413 } 19414 if(pt.y > boundingBox.bottom) { 19415 if(exact) 19416 return IdentifyResult(null, 2); 19417 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 19418 } 19419 19420 int tci = 0; 19421 19422 // I should probably like binary search this or something... 19423 foreach(ref part; parts) { 19424 foreach(rendered; part.rendered) { 19425 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 19426 if(rect.contains(pt)) { 19427 auto x = pt.x - rendered.startX; 19428 auto estimatedIdx = x / part.font.averageWidth; 19429 19430 if(estimatedIdx < 0) 19431 estimatedIdx = 0; 19432 19433 if(estimatedIdx > rendered.slice.length) 19434 estimatedIdx = cast(int) rendered.slice.length; 19435 19436 int idx; 19437 int x1, x2; 19438 if(part.font.isMonospace) { 19439 auto w = part.font.averageWidth; 19440 if(!exact && x > (estimatedIdx + 1) * w) 19441 return IdentifyResult(null, 4); 19442 idx = estimatedIdx; 19443 x1 = idx * w; 19444 x2 = (idx + 1) * w; 19445 } else { 19446 idx = estimatedIdx; 19447 19448 part.font.prepareContext(window); 19449 scope(exit) part.font.releaseContext(); 19450 19451 // int iterations; 19452 19453 while(true) { 19454 // iterations++; 19455 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 19456 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 19457 19458 x1 += rendered.startX; 19459 x2 += rendered.startX; 19460 19461 if(pt.x < x1) { 19462 if(idx == 0) { 19463 if(exact) 19464 return IdentifyResult(null, 6); 19465 else 19466 break; 19467 } 19468 idx--; 19469 } else if(pt.x > x2) { 19470 idx++; 19471 if(idx > rendered.slice.length) { 19472 if(exact) 19473 return IdentifyResult(null, 5); 19474 else 19475 break; 19476 } 19477 } else if(pt.x >= x1 && pt.x <= x2) { 19478 if(idx) 19479 idx--; // point it at the original index 19480 break; // we fit 19481 } 19482 } 19483 19484 // writeln(iterations) 19485 } 19486 19487 19488 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 19489 } 19490 } 19491 tci += cast(int) part.content.length; // FIXME: utf-8? 19492 } 19493 return IdentifyResult(null, 3); 19494 } 19495 19496 Rectangle boundingBox; // only set after [rerender] 19497 19498 // text will be positioned around the exclusion zone 19499 static struct ExclusionZone { 19500 19501 } 19502 19503 ExclusionZone[] exclusionZones; 19504 } 19505 19506 19507 // Don't use this yet. When I'm happy with it, I will move it to the 19508 // regular module namespace. 19509 mixin template ExperimentalTextComponent() { 19510 19511 static: 19512 19513 alias Rectangle = arsd.color.Rectangle; 19514 19515 struct ForegroundColor { 19516 Color color; 19517 alias color this; 19518 19519 this(Color c) { 19520 color = c; 19521 } 19522 19523 this(int r, int g, int b, int a = 255) { 19524 color = Color(r, g, b, a); 19525 } 19526 19527 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 19528 return ForegroundColor(mixin("Color." ~ s)); 19529 } 19530 } 19531 19532 struct BackgroundColor { 19533 Color color; 19534 alias color this; 19535 19536 this(Color c) { 19537 color = c; 19538 } 19539 19540 this(int r, int g, int b, int a = 255) { 19541 color = Color(r, g, b, a); 19542 } 19543 19544 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 19545 return BackgroundColor(mixin("Color." ~ s)); 19546 } 19547 } 19548 19549 static class InlineElement { 19550 string text; 19551 19552 BlockElement containingBlock; 19553 19554 Color color = Color.black; 19555 Color backgroundColor = Color.transparent; 19556 ushort styles; 19557 19558 string font; 19559 int fontSize; 19560 19561 int lineHeight; 19562 19563 void* identifier; 19564 19565 Rectangle boundingBox; 19566 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 19567 19568 bool isMergeCompatible(InlineElement other) { 19569 return 19570 containingBlock is other.containingBlock && 19571 color == other.color && 19572 backgroundColor == other.backgroundColor && 19573 styles == other.styles && 19574 font == other.font && 19575 fontSize == other.fontSize && 19576 lineHeight == other.lineHeight && 19577 true; 19578 } 19579 19580 int xOfIndex(size_t index) { 19581 if(index < letterXs.length) 19582 return letterXs[index]; 19583 else 19584 return boundingBox.right; 19585 } 19586 19587 InlineElement clone() { 19588 auto ie = new InlineElement(); 19589 ie.tupleof = this.tupleof; 19590 return ie; 19591 } 19592 19593 InlineElement getPreviousInlineElement() { 19594 InlineElement prev = null; 19595 foreach(ie; this.containingBlock.parts) { 19596 if(ie is this) 19597 break; 19598 prev = ie; 19599 } 19600 if(prev is null) { 19601 BlockElement pb; 19602 BlockElement cb = this.containingBlock; 19603 moar: 19604 foreach(ie; this.containingBlock.containingLayout.blocks) { 19605 if(ie is cb) 19606 break; 19607 pb = ie; 19608 } 19609 if(pb is null) 19610 return null; 19611 if(pb.parts.length == 0) { 19612 cb = pb; 19613 goto moar; 19614 } 19615 19616 prev = pb.parts[$-1]; 19617 19618 } 19619 return prev; 19620 } 19621 19622 InlineElement getNextInlineElement() { 19623 InlineElement next = null; 19624 foreach(idx, ie; this.containingBlock.parts) { 19625 if(ie is this) { 19626 if(idx + 1 < this.containingBlock.parts.length) 19627 next = this.containingBlock.parts[idx + 1]; 19628 break; 19629 } 19630 } 19631 if(next is null) { 19632 BlockElement n; 19633 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 19634 if(ie is this.containingBlock) { 19635 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 19636 n = this.containingBlock.containingLayout.blocks[idx + 1]; 19637 break; 19638 } 19639 } 19640 if(n is null) 19641 return null; 19642 19643 if(n.parts.length) 19644 next = n.parts[0]; 19645 else {} // FIXME 19646 19647 } 19648 return next; 19649 } 19650 19651 } 19652 19653 // Block elements are used entirely for positioning inline elements, 19654 // which are the things that are actually drawn. 19655 class BlockElement { 19656 InlineElement[] parts; 19657 uint alignment; 19658 19659 int whiteSpace; // pre, pre-wrap, wrap 19660 19661 TextLayout containingLayout; 19662 19663 // inputs 19664 Point where; 19665 Size minimumSize; 19666 Size maximumSize; 19667 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 19668 void* identifier; 19669 19670 Rectangle margin; 19671 Rectangle padding; 19672 19673 // outputs 19674 Rectangle[] boundingBoxes; 19675 } 19676 19677 struct TextIdentifyResult { 19678 InlineElement element; 19679 int offset; 19680 19681 private TextIdentifyResult fixupNewline() { 19682 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 19683 offset--; 19684 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 19685 offset--; 19686 } 19687 return this; 19688 } 19689 } 19690 19691 class TextLayout { 19692 BlockElement[] blocks; 19693 Rectangle boundingBox_; 19694 Rectangle boundingBox() { return boundingBox_; } 19695 void boundingBox(Rectangle r) { 19696 if(r != boundingBox_) { 19697 boundingBox_ = r; 19698 layoutInvalidated = true; 19699 } 19700 } 19701 19702 Rectangle contentBoundingBox() { 19703 Rectangle r; 19704 foreach(block; blocks) 19705 foreach(ie; block.parts) { 19706 if(ie.boundingBox.right > r.right) 19707 r.right = ie.boundingBox.right; 19708 if(ie.boundingBox.bottom > r.bottom) 19709 r.bottom = ie.boundingBox.bottom; 19710 } 19711 return r; 19712 } 19713 19714 BlockElement[] getBlocks() { 19715 return blocks; 19716 } 19717 19718 InlineElement[] getTexts() { 19719 InlineElement[] elements; 19720 foreach(block; blocks) 19721 elements ~= block.parts; 19722 return elements; 19723 } 19724 19725 string getPlainText() { 19726 string text; 19727 foreach(block; blocks) 19728 foreach(part; block.parts) 19729 text ~= part.text; 19730 return text; 19731 } 19732 19733 string getHtml() { 19734 return null; // FIXME 19735 } 19736 19737 this(Rectangle boundingBox) { 19738 this.boundingBox = boundingBox; 19739 } 19740 19741 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 19742 auto be = new BlockElement(); 19743 be.containingLayout = this; 19744 if(after is null) 19745 blocks ~= be; 19746 else { 19747 foreach(idx, b; blocks) { 19748 if(b is after.containingBlock) { 19749 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 19750 break; 19751 } 19752 } 19753 } 19754 return be; 19755 } 19756 19757 void clear() { 19758 blocks = null; 19759 selectionStart = selectionEnd = caret = Caret.init; 19760 } 19761 19762 void addText(Args...)(Args args) { 19763 if(blocks.length == 0) 19764 addBlock(); 19765 19766 InlineElement ie = new InlineElement(); 19767 foreach(idx, arg; args) { 19768 static if(is(typeof(arg) == ForegroundColor)) 19769 ie.color = arg; 19770 else static if(is(typeof(arg) == TextFormat)) { 19771 if(arg & 0x8000) // ~TextFormat.something turns it off 19772 ie.styles &= arg; 19773 else 19774 ie.styles |= arg; 19775 } else static if(is(typeof(arg) == string)) { 19776 static if(idx == 0 && args.length > 1) 19777 static assert(0, "Put styles before the string."); 19778 size_t lastLineIndex; 19779 foreach(cidx, char a; arg) { 19780 if(a == '\n') { 19781 ie.text = arg[lastLineIndex .. cidx + 1]; 19782 lastLineIndex = cidx + 1; 19783 ie.containingBlock = blocks[$-1]; 19784 blocks[$-1].parts ~= ie.clone; 19785 ie.text = null; 19786 } else { 19787 19788 } 19789 } 19790 19791 ie.text = arg[lastLineIndex .. $]; 19792 ie.containingBlock = blocks[$-1]; 19793 blocks[$-1].parts ~= ie.clone; 19794 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 19795 } 19796 } 19797 19798 invalidateLayout(); 19799 } 19800 19801 void tryMerge(InlineElement into, InlineElement what) { 19802 if(!into.isMergeCompatible(what)) { 19803 return; // cannot merge, different configs 19804 } 19805 19806 // cool, can merge, bring text together... 19807 into.text ~= what.text; 19808 19809 // and remove what 19810 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 19811 if(what.containingBlock.parts[a] is what) { 19812 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 19813 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 19814 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 19815 19816 } 19817 } 19818 19819 // FIXME: ensure no other carets have a reference to it 19820 } 19821 19822 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 19823 TextIdentifyResult identify(int x, int y, bool exact = false) { 19824 TextIdentifyResult inexactMatch; 19825 foreach(block; blocks) { 19826 foreach(part; block.parts) { 19827 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 19828 19829 // FIXME binary search 19830 int tidx; 19831 int lastX; 19832 foreach_reverse(idxo, lx; part.letterXs) { 19833 int idx = cast(int) idxo; 19834 if(lx <= x) { 19835 if(lastX && lastX - x < x - lx) 19836 tidx = idx + 1; 19837 else 19838 tidx = idx; 19839 break; 19840 } 19841 lastX = lx; 19842 } 19843 19844 return TextIdentifyResult(part, tidx).fixupNewline; 19845 } else if(!exact) { 19846 // we're not in the box, but are we on the same line? 19847 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 19848 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 19849 } 19850 } 19851 } 19852 19853 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 19854 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 19855 19856 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 19857 } 19858 19859 void moveCaretToPixelCoordinates(int x, int y) { 19860 auto result = identify(x, y); 19861 caret.inlineElement = result.element; 19862 caret.offset = result.offset; 19863 } 19864 19865 void selectToPixelCoordinates(int x, int y) { 19866 auto result = identify(x, y); 19867 19868 if(y < caretLastDrawnY1) { 19869 // on a previous line, carat is selectionEnd 19870 selectionEnd = caret; 19871 19872 selectionStart = Caret(this, result.element, result.offset); 19873 } else if(y > caretLastDrawnY2) { 19874 // on a later line 19875 selectionStart = caret; 19876 19877 selectionEnd = Caret(this, result.element, result.offset); 19878 } else { 19879 // on the same line... 19880 if(x <= caretLastDrawnX) { 19881 selectionEnd = caret; 19882 selectionStart = Caret(this, result.element, result.offset); 19883 } else { 19884 selectionStart = caret; 19885 selectionEnd = Caret(this, result.element, result.offset); 19886 } 19887 19888 } 19889 } 19890 19891 19892 /// Call this if the inputs change. It will reflow everything 19893 void redoLayout(ScreenPainter painter) { 19894 //painter.setClipRectangle(boundingBox); 19895 auto pos = Point(boundingBox.left, boundingBox.top); 19896 19897 int lastHeight; 19898 void nl() { 19899 pos.x = boundingBox.left; 19900 pos.y += lastHeight; 19901 } 19902 foreach(block; blocks) { 19903 nl(); 19904 foreach(part; block.parts) { 19905 part.letterXs = null; 19906 19907 auto size = painter.textSize(part.text); 19908 version(Windows) 19909 if(part.text.length && part.text[$-1] == '\n') 19910 size.height /= 2; // windows counts the new line at the end, but we don't want that 19911 19912 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 19913 19914 foreach(idx, char c; part.text) { 19915 // FIXME: unicode 19916 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 19917 } 19918 19919 pos.x += size.width; 19920 if(pos.x >= boundingBox.right) { 19921 pos.y += size.height; 19922 pos.x = boundingBox.left; 19923 lastHeight = 0; 19924 } else { 19925 lastHeight = size.height; 19926 } 19927 19928 if(part.text.length && part.text[$-1] == '\n') 19929 nl(); 19930 } 19931 } 19932 19933 layoutInvalidated = false; 19934 } 19935 19936 bool layoutInvalidated = true; 19937 void invalidateLayout() { 19938 layoutInvalidated = true; 19939 } 19940 19941 // FIXME: caret can remain sometimes when inserting 19942 // FIXME: inserting at the beginning once you already have something can eff it up. 19943 void drawInto(ScreenPainter painter, bool focused = false) { 19944 if(layoutInvalidated) 19945 redoLayout(painter); 19946 foreach(block; blocks) { 19947 foreach(part; block.parts) { 19948 painter.outlineColor = part.color; 19949 painter.fillColor = part.backgroundColor; 19950 19951 auto pos = part.boundingBox.upperLeft; 19952 auto size = part.boundingBox.size; 19953 19954 painter.drawText(pos, part.text); 19955 if(part.styles & TextFormat.underline) 19956 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 19957 if(part.styles & TextFormat.strikethrough) 19958 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 19959 } 19960 } 19961 19962 // on every redraw, I will force the caret to be 19963 // redrawn too, in order to eliminate perceived lag 19964 // when moving around with the mouse. 19965 eraseCaret(painter); 19966 19967 if(focused) { 19968 highlightSelection(painter); 19969 drawCaret(painter); 19970 } 19971 } 19972 19973 Color selectionXorColor = Color(255, 255, 127); 19974 19975 void highlightSelection(ScreenPainter painter) { 19976 if(selectionStart is selectionEnd) 19977 return; // no selection 19978 19979 if(selectionStart.inlineElement is null) return; 19980 if(selectionEnd.inlineElement is null) return; 19981 19982 assert(selectionStart.inlineElement !is null); 19983 assert(selectionEnd.inlineElement !is null); 19984 19985 painter.rasterOp = RasterOp.xor; 19986 painter.outlineColor = Color.transparent; 19987 painter.fillColor = selectionXorColor; 19988 19989 auto at = selectionStart.inlineElement; 19990 auto atOffset = selectionStart.offset; 19991 bool done; 19992 while(at) { 19993 auto box = at.boundingBox; 19994 if(atOffset < at.letterXs.length) 19995 box.left = at.letterXs[atOffset]; 19996 19997 if(at is selectionEnd.inlineElement) { 19998 if(selectionEnd.offset < at.letterXs.length) 19999 box.right = at.letterXs[selectionEnd.offset]; 20000 done = true; 20001 } 20002 20003 painter.drawRectangle(box.upperLeft, box.width, box.height); 20004 20005 if(done) 20006 break; 20007 20008 at = at.getNextInlineElement(); 20009 atOffset = 0; 20010 } 20011 } 20012 20013 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 20014 bool caretShowingOnScreen = false; 20015 void drawCaret(ScreenPainter painter) { 20016 //painter.setClipRectangle(boundingBox); 20017 int x, y1, y2; 20018 if(caret.inlineElement is null) { 20019 x = boundingBox.left; 20020 y1 = boundingBox.top + 2; 20021 y2 = boundingBox.top + painter.fontHeight; 20022 } else { 20023 x = caret.inlineElement.xOfIndex(caret.offset); 20024 y1 = caret.inlineElement.boundingBox.top + 2; 20025 y2 = caret.inlineElement.boundingBox.bottom - 2; 20026 } 20027 20028 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 20029 eraseCaret(painter); 20030 20031 painter.pen = Pen(Color.white, 1); 20032 painter.rasterOp = RasterOp.xor; 20033 painter.drawLine( 20034 Point(x, y1), 20035 Point(x, y2) 20036 ); 20037 painter.rasterOp = RasterOp.normal; 20038 caretShowingOnScreen = !caretShowingOnScreen; 20039 20040 if(caretShowingOnScreen) { 20041 caretLastDrawnX = x; 20042 caretLastDrawnY1 = y1; 20043 caretLastDrawnY2 = y2; 20044 } 20045 } 20046 20047 Rectangle caretBoundingBox() { 20048 int x, y1, y2; 20049 if(caret.inlineElement is null) { 20050 x = boundingBox.left; 20051 y1 = boundingBox.top + 2; 20052 y2 = boundingBox.top + 16; 20053 } else { 20054 x = caret.inlineElement.xOfIndex(caret.offset); 20055 y1 = caret.inlineElement.boundingBox.top + 2; 20056 y2 = caret.inlineElement.boundingBox.bottom - 2; 20057 } 20058 20059 return Rectangle(x, y1, x + 1, y2); 20060 } 20061 20062 void eraseCaret(ScreenPainter painter) { 20063 //painter.setClipRectangle(boundingBox); 20064 if(!caretShowingOnScreen) return; 20065 painter.pen = Pen(Color.white, 1); 20066 painter.rasterOp = RasterOp.xor; 20067 painter.drawLine( 20068 Point(caretLastDrawnX, caretLastDrawnY1), 20069 Point(caretLastDrawnX, caretLastDrawnY2) 20070 ); 20071 20072 caretShowingOnScreen = false; 20073 painter.rasterOp = RasterOp.normal; 20074 } 20075 20076 /// Caret movement api 20077 /// These should give the user a logical result based on what they see on screen... 20078 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 20079 void moveUp() { 20080 if(caret.inlineElement is null) return; 20081 auto x = caret.inlineElement.xOfIndex(caret.offset); 20082 auto y = caret.inlineElement.boundingBox.top + 2; 20083 20084 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20085 if(y < 0) 20086 return; 20087 20088 auto i = identify(x, y); 20089 20090 if(i.element) { 20091 caret.inlineElement = i.element; 20092 caret.offset = i.offset; 20093 } 20094 } 20095 void moveDown() { 20096 if(caret.inlineElement is null) return; 20097 auto x = caret.inlineElement.xOfIndex(caret.offset); 20098 auto y = caret.inlineElement.boundingBox.bottom - 2; 20099 20100 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20101 20102 auto i = identify(x, y); 20103 if(i.element) { 20104 caret.inlineElement = i.element; 20105 caret.offset = i.offset; 20106 } 20107 } 20108 void moveLeft() { 20109 if(caret.inlineElement is null) return; 20110 if(caret.offset) 20111 caret.offset--; 20112 else { 20113 auto p = caret.inlineElement.getPreviousInlineElement(); 20114 if(p) { 20115 caret.inlineElement = p; 20116 if(p.text.length && p.text[$-1] == '\n') 20117 caret.offset = cast(int) p.text.length - 1; 20118 else 20119 caret.offset = cast(int) p.text.length; 20120 } 20121 } 20122 } 20123 void moveRight() { 20124 if(caret.inlineElement is null) return; 20125 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 20126 caret.offset++; 20127 } else { 20128 auto p = caret.inlineElement.getNextInlineElement(); 20129 if(p) { 20130 caret.inlineElement = p; 20131 caret.offset = 0; 20132 } 20133 } 20134 } 20135 void moveHome() { 20136 if(caret.inlineElement is null) return; 20137 auto x = 0; 20138 auto y = caret.inlineElement.boundingBox.top + 2; 20139 20140 auto i = identify(x, y); 20141 20142 if(i.element) { 20143 caret.inlineElement = i.element; 20144 caret.offset = i.offset; 20145 } 20146 } 20147 void moveEnd() { 20148 if(caret.inlineElement is null) return; 20149 auto x = int.max; 20150 auto y = caret.inlineElement.boundingBox.top + 2; 20151 20152 auto i = identify(x, y); 20153 20154 if(i.element) { 20155 caret.inlineElement = i.element; 20156 caret.offset = i.offset; 20157 } 20158 20159 } 20160 void movePageUp(ref Caret caret) {} 20161 void movePageDown(ref Caret caret) {} 20162 20163 void moveDocumentStart(ref Caret caret) { 20164 if(blocks.length && blocks[0].parts.length) 20165 caret = Caret(this, blocks[0].parts[0], 0); 20166 else 20167 caret = Caret.init; 20168 } 20169 20170 void moveDocumentEnd(ref Caret caret) { 20171 if(blocks.length) { 20172 auto parts = blocks[$-1].parts; 20173 if(parts.length) { 20174 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 20175 } else { 20176 caret = Caret.init; 20177 } 20178 } else 20179 caret = Caret.init; 20180 } 20181 20182 void deleteSelection() { 20183 if(selectionStart is selectionEnd) 20184 return; 20185 20186 if(selectionStart.inlineElement is null) return; 20187 if(selectionEnd.inlineElement is null) return; 20188 20189 assert(selectionStart.inlineElement !is null); 20190 assert(selectionEnd.inlineElement !is null); 20191 20192 auto at = selectionStart.inlineElement; 20193 20194 if(selectionEnd.inlineElement is at) { 20195 // same element, need to chop out 20196 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 20197 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 20198 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 20199 } else { 20200 // different elements, we can do it with slicing 20201 at.text = at.text[0 .. selectionStart.offset]; 20202 if(selectionStart.offset < at.letterXs.length) 20203 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 20204 20205 at = at.getNextInlineElement(); 20206 20207 while(at) { 20208 if(at is selectionEnd.inlineElement) { 20209 at.text = at.text[selectionEnd.offset .. $]; 20210 if(selectionEnd.offset < at.letterXs.length) 20211 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 20212 selectionEnd.offset = 0; 20213 break; 20214 } else { 20215 auto cfd = at; 20216 cfd.text = null; // delete the whole thing 20217 20218 at = at.getNextInlineElement(); 20219 20220 if(cfd.text.length == 0) { 20221 // and remove cfd 20222 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 20223 if(cfd.containingBlock.parts[a] is cfd) { 20224 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 20225 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 20226 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 20227 20228 } 20229 } 20230 } 20231 } 20232 } 20233 } 20234 20235 caret = selectionEnd; 20236 selectNone(); 20237 20238 invalidateLayout(); 20239 20240 } 20241 20242 /// Plain text editing api. These work at the current caret inside the selected inline element. 20243 void insert(in char[] text) { 20244 foreach(dchar ch; text) 20245 insert(ch); 20246 } 20247 /// ditto 20248 void insert(dchar ch) { 20249 20250 bool selectionDeleted = false; 20251 if(selectionStart !is selectionEnd) { 20252 deleteSelection(); 20253 selectionDeleted = true; 20254 } 20255 20256 if(ch == 127) { 20257 delete_(); 20258 return; 20259 } 20260 if(ch == 8) { 20261 if(!selectionDeleted) 20262 backspace(); 20263 return; 20264 } 20265 20266 invalidateLayout(); 20267 20268 if(ch == 13) ch = 10; 20269 auto e = caret.inlineElement; 20270 if(e is null) { 20271 addText("" ~ cast(char) ch) ; // FIXME 20272 return; 20273 } 20274 20275 if(caret.offset == e.text.length) { 20276 e.text ~= cast(char) ch; // FIXME 20277 caret.offset++; 20278 if(ch == 10) { 20279 auto c = caret.inlineElement.clone; 20280 c.text = null; 20281 c.letterXs = null; 20282 insertPartAfter(c,e); 20283 caret = Caret(this, c, 0); 20284 } 20285 } else { 20286 // FIXME cast char sucks 20287 if(ch == 10) { 20288 auto c = caret.inlineElement.clone; 20289 c.text = e.text[caret.offset .. $]; 20290 if(caret.offset < c.letterXs.length) 20291 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 20292 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 20293 if(caret.offset <= e.letterXs.length) { 20294 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 20295 } 20296 insertPartAfter(c,e); 20297 caret = Caret(this, c, 0); 20298 } else { 20299 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 20300 caret.offset++; 20301 } 20302 } 20303 } 20304 20305 void insertPartAfter(InlineElement what, InlineElement where) { 20306 foreach(idx, p; where.containingBlock.parts) { 20307 if(p is where) { 20308 if(idx + 1 == where.containingBlock.parts.length) 20309 where.containingBlock.parts ~= what; 20310 else 20311 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 20312 return; 20313 } 20314 } 20315 } 20316 20317 void cleanupStructures() { 20318 for(size_t i = 0; i < blocks.length; i++) { 20319 auto block = blocks[i]; 20320 for(size_t a = 0; a < block.parts.length; a++) { 20321 auto part = block.parts[a]; 20322 if(part.text.length == 0) { 20323 for(size_t b = a; b < block.parts.length - 1; b++) 20324 block.parts[b] = block.parts[b+1]; 20325 block.parts = block.parts[0 .. $-1]; 20326 } 20327 } 20328 if(block.parts.length == 0) { 20329 for(size_t a = i; a < blocks.length - 1; a++) 20330 blocks[a] = blocks[a+1]; 20331 blocks = blocks[0 .. $-1]; 20332 } 20333 } 20334 } 20335 20336 void backspace() { 20337 try_again: 20338 auto e = caret.inlineElement; 20339 if(e is null) 20340 return; 20341 if(caret.offset == 0) { 20342 auto prev = e.getPreviousInlineElement(); 20343 if(prev is null) 20344 return; 20345 auto newOffset = cast(int) prev.text.length; 20346 tryMerge(prev, e); 20347 caret.inlineElement = prev; 20348 caret.offset = prev is null ? 0 : newOffset; 20349 20350 goto try_again; 20351 } else if(caret.offset == e.text.length) { 20352 e.text = e.text[0 .. $-1]; 20353 caret.offset--; 20354 } else { 20355 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 20356 caret.offset--; 20357 } 20358 //cleanupStructures(); 20359 20360 invalidateLayout(); 20361 } 20362 void delete_() { 20363 if(selectionStart !is selectionEnd) 20364 deleteSelection(); 20365 else { 20366 auto before = caret; 20367 moveRight(); 20368 if(caret != before) { 20369 backspace(); 20370 } 20371 } 20372 20373 invalidateLayout(); 20374 } 20375 void overstrike() {} 20376 20377 /// Selection API. See also: caret movement. 20378 void selectAll() { 20379 moveDocumentStart(selectionStart); 20380 moveDocumentEnd(selectionEnd); 20381 } 20382 bool selectNone() { 20383 if(selectionStart != selectionEnd) { 20384 selectionStart = selectionEnd = Caret.init; 20385 return true; 20386 } 20387 return false; 20388 } 20389 20390 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 20391 /// They will modify the current selection if there is one and will splice one in if needed. 20392 void changeAttributes() {} 20393 20394 20395 /// Text search api. They manipulate the selection and/or caret. 20396 void findText(string text) {} 20397 void findIndex(size_t textIndex) {} 20398 20399 // sample event handlers 20400 20401 void handleEvent(KeyEvent event) { 20402 //if(event.type == KeyEvent.Type.KeyPressed) { 20403 20404 //} 20405 } 20406 20407 void handleEvent(dchar ch) { 20408 20409 } 20410 20411 void handleEvent(MouseEvent event) { 20412 20413 } 20414 20415 bool contentEditable; // can it be edited? 20416 bool contentCaretable; // is there a caret/cursor that moves around in there? 20417 bool contentSelectable; // selectable? 20418 20419 Caret caret; 20420 Caret selectionStart; 20421 Caret selectionEnd; 20422 20423 bool insertMode; 20424 } 20425 20426 struct Caret { 20427 TextLayout layout; 20428 InlineElement inlineElement; 20429 int offset; 20430 } 20431 20432 enum TextFormat : ushort { 20433 // decorations 20434 underline = 1, 20435 strikethrough = 2, 20436 20437 // font selectors 20438 20439 bold = 0x4000 | 1, // weight 700 20440 light = 0x4000 | 2, // weight 300 20441 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 20442 // bold | light is really invalid but should give weight 500 20443 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 20444 20445 italic = 0x4000 | 8, 20446 smallcaps = 0x4000 | 16, 20447 } 20448 20449 void* findFont(string family, int weight, TextFormat formats) { 20450 return null; 20451 } 20452 20453 } 20454 20455 /++ 20456 $(PITFALL This is not yet stable and may break in future versions without notice.) 20457 20458 History: 20459 Added February 19, 2021 20460 +/ 20461 /// Group: drag_and_drop 20462 interface DropHandler { 20463 /++ 20464 Called when the drag enters the handler's area. 20465 +/ 20466 DragAndDropAction dragEnter(DropPackage*); 20467 /++ 20468 Called when the drag leaves the handler's area or is 20469 cancelled. You should free your resources when this is called. 20470 +/ 20471 void dragLeave(); 20472 /++ 20473 Called continually as the drag moves over the handler's area. 20474 20475 Returns: feedback to the dragger 20476 +/ 20477 DropParameters dragOver(Point pt); 20478 /++ 20479 The user dropped the data and you should process it now. You can 20480 access the data through the given [DropPackage]. 20481 +/ 20482 void drop(scope DropPackage*); 20483 /++ 20484 Called when the drop is complete. You should free whatever temporary 20485 resources you were using. It is often reasonable to simply forward 20486 this call to [dragLeave]. 20487 +/ 20488 void finish(); 20489 20490 /++ 20491 Parameters returned by [DropHandler.drop]. 20492 +/ 20493 static struct DropParameters { 20494 /++ 20495 Acceptable action over this area. 20496 +/ 20497 DragAndDropAction action; 20498 /++ 20499 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 20500 20501 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 20502 +/ 20503 Rectangle consistentWithin; 20504 } 20505 } 20506 20507 /++ 20508 History: 20509 Added February 19, 2021 20510 +/ 20511 /// Group: drag_and_drop 20512 enum DragAndDropAction { 20513 none = 0, 20514 copy, 20515 move, 20516 link, 20517 ask, 20518 custom 20519 } 20520 20521 /++ 20522 An opaque structure representing dropped data. It contains 20523 private, platform-specific data that your `drop` function 20524 should simply forward. 20525 20526 $(PITFALL This is not yet stable and may break in future versions without notice.) 20527 20528 History: 20529 Added February 19, 2021 20530 +/ 20531 /// Group: drag_and_drop 20532 struct DropPackage { 20533 /++ 20534 Lists the available formats as magic numbers. You should compare these 20535 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 20536 understand the passed data. 20537 +/ 20538 DraggableData.FormatId[] availableFormats() { 20539 version(X11) { 20540 return xFormats; 20541 } else version(Windows) { 20542 if(pDataObj is null) 20543 return null; 20544 20545 typeof(return) ret; 20546 20547 IEnumFORMATETC ef; 20548 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 20549 FORMATETC fmt; 20550 ULONG fetched; 20551 while(ef.Next(1, &fmt, &fetched) == S_OK) { 20552 if(fetched == 0) 20553 break; 20554 20555 if(fmt.lindex != -1) 20556 continue; 20557 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 20558 continue; 20559 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 20560 continue; 20561 20562 ret ~= fmt.cfFormat; 20563 } 20564 } 20565 20566 return ret; 20567 } 20568 } 20569 20570 /++ 20571 Gets data from the drop and optionally accepts it. 20572 20573 Returns: 20574 void because the data is fed asynchronously through the `dg` parameter. 20575 20576 Params: 20577 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. 20578 20579 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. 20580 20581 Calling `getData` again after accepting a drop is not permitted. 20582 20583 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 20584 20585 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. 20586 20587 Throws: 20588 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 20589 20590 History: 20591 Included in first release of [DropPackage]. 20592 +/ 20593 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 20594 version(X11) { 20595 20596 auto display = XDisplayConnection.get(); 20597 auto selectionAtom = GetAtom!"XdndSelection"(display); 20598 auto best = format; 20599 20600 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 20601 20602 XDisplay* display; 20603 Atom selectionAtom; 20604 DraggableData.FormatId best; 20605 DraggableData.FormatId format; 20606 void delegate(scope ubyte[] data) dg; 20607 DragAndDropAction acceptedAction; 20608 Window sourceWindow; 20609 SimpleWindow win; 20610 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 20611 this.display = display; 20612 this.win = win; 20613 this.sourceWindow = sourceWindow; 20614 this.format = format; 20615 this.selectionAtom = selectionAtom; 20616 this.best = best; 20617 this.dg = dg; 20618 this.acceptedAction = acceptedAction; 20619 } 20620 20621 20622 mixin X11GetSelectionHandler_Basics; 20623 20624 void handleData(Atom target, in ubyte[] data) { 20625 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 20626 20627 dg(cast(ubyte[]) data); 20628 20629 if(acceptedAction != DragAndDropAction.none) { 20630 auto display = XDisplayConnection.get; 20631 20632 XClientMessageEvent xclient; 20633 20634 xclient.type = EventType.ClientMessage; 20635 xclient.window = sourceWindow; 20636 xclient.message_type = GetAtom!"XdndFinished"(display); 20637 xclient.format = 32; 20638 xclient.data.l[0] = win.impl.window; 20639 xclient.data.l[1] = 1; // drop successful 20640 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 20641 20642 XSendEvent( 20643 display, 20644 sourceWindow, 20645 false, 20646 EventMask.NoEventMask, 20647 cast(XEvent*) &xclient 20648 ); 20649 20650 XFlush(display); 20651 } 20652 } 20653 20654 Atom findBestFormat(Atom[] answer) { 20655 Atom best = None; 20656 foreach(option; answer) { 20657 if(option == format) { 20658 best = option; 20659 break; 20660 } 20661 /* 20662 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 20663 best = option; 20664 break; 20665 } else if(option == XA_STRING) { 20666 best = option; 20667 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 20668 best = option; 20669 } 20670 */ 20671 } 20672 return best; 20673 } 20674 } 20675 20676 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 20677 20678 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 20679 20680 } else version(Windows) { 20681 20682 // clean up like DragLeave 20683 // pass effect back up 20684 20685 FORMATETC t; 20686 assert(format >= 0 && format <= ushort.max); 20687 t.cfFormat = cast(ushort) format; 20688 t.lindex = -1; 20689 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 20690 t.tymed = TYMED.TYMED_HGLOBAL; 20691 20692 STGMEDIUM m; 20693 20694 if(pDataObj.GetData(&t, &m) != S_OK) { 20695 // fail 20696 } else { 20697 // succeed, take the data and clean up 20698 20699 // FIXME: ensure it is legit HGLOBAL 20700 auto handle = m.hGlobal; 20701 20702 if(handle) { 20703 auto sz = GlobalSize(handle); 20704 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 20705 scope(exit) GlobalUnlock(handle); 20706 scope(exit) GlobalFree(handle); 20707 20708 auto data = ptr[0 .. sz]; 20709 20710 dg(data); 20711 } 20712 } 20713 } 20714 } 20715 } 20716 20717 private: 20718 20719 version(X11) { 20720 SimpleWindow win; 20721 Window sourceWindow; 20722 Time dataTimestamp; 20723 20724 Atom[] xFormats; 20725 } 20726 version(Windows) { 20727 IDataObject pDataObj; 20728 } 20729 } 20730 20731 /++ 20732 A generic helper base class for making a drop handler with a preference list of custom types. 20733 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 20734 droppers too. 20735 20736 It assumes the whole window it used, but you can subclass to change that. 20737 20738 $(PITFALL This is not yet stable and may break in future versions without notice.) 20739 20740 History: 20741 Added February 19, 2021 20742 +/ 20743 /// Group: drag_and_drop 20744 class GenericDropHandlerBase : DropHandler { 20745 // no fancy state here so no need to do anything here 20746 void finish() { } 20747 void dragLeave() { } 20748 20749 private DragAndDropAction acceptedAction; 20750 private DraggableData.FormatId acceptedFormat; 20751 private void delegate(scope ubyte[]) acceptedHandler; 20752 20753 struct FormatHandler { 20754 DraggableData.FormatId format; 20755 void delegate(scope ubyte[]) handler; 20756 } 20757 20758 protected abstract FormatHandler[] formatHandlers(); 20759 20760 DragAndDropAction dragEnter(DropPackage* pkg) { 20761 debug(sdpy_dnd) { foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 20762 foreach(fmt; formatHandlers()) 20763 foreach(f; pkg.availableFormats()) 20764 if(f == fmt.format) { 20765 acceptedFormat = f; 20766 acceptedHandler = fmt.handler; 20767 return acceptedAction = DragAndDropAction.copy; 20768 } 20769 return acceptedAction = DragAndDropAction.none; 20770 } 20771 DropParameters dragOver(Point pt) { 20772 return DropParameters(acceptedAction); 20773 } 20774 20775 void drop(scope DropPackage* dropPackage) { 20776 if(!acceptedFormat || acceptedHandler is null) { 20777 debug(sdpy_dnd) { writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 20778 return; // prolly shouldn't happen anyway... 20779 } 20780 20781 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 20782 } 20783 } 20784 20785 /++ 20786 A simple handler for making your window accept drops of plain text. 20787 20788 $(PITFALL This is not yet stable and may break in future versions without notice.) 20789 20790 History: 20791 Added February 22, 2021 20792 +/ 20793 /// Group: drag_and_drop 20794 class TextDropHandler : GenericDropHandlerBase { 20795 private void delegate(in char[] text) dg; 20796 20797 /++ 20798 20799 +/ 20800 this(void delegate(in char[] text) dg) { 20801 this.dg = dg; 20802 } 20803 20804 protected override FormatHandler[] formatHandlers() { 20805 version(X11) 20806 return [ 20807 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 20808 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 20809 ]; 20810 else version(Windows) 20811 return [ 20812 FormatHandler(CF_UNICODETEXT, &translator), 20813 ]; 20814 } 20815 20816 private void translator(scope ubyte[] data) { 20817 version(X11) 20818 dg(cast(char[]) data); 20819 else version(Windows) 20820 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 20821 } 20822 } 20823 20824 /++ 20825 A simple handler for making your window accept drops of files, issued to you as file names. 20826 20827 $(PITFALL This is not yet stable and may break in future versions without notice.) 20828 20829 History: 20830 Added February 22, 2021 20831 +/ 20832 /// Group: drag_and_drop 20833 20834 class FilesDropHandler : GenericDropHandlerBase { 20835 private void delegate(in char[][]) dg; 20836 20837 /++ 20838 20839 +/ 20840 this(void delegate(in char[][] fileNames) dg) { 20841 this.dg = dg; 20842 } 20843 20844 protected override FormatHandler[] formatHandlers() { 20845 version(X11) 20846 return [ 20847 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 20848 ]; 20849 else version(Windows) 20850 return [ 20851 FormatHandler(CF_HDROP, &translator), 20852 ]; 20853 } 20854 20855 private void translator(scope ubyte[] data) { 20856 version(X11) { 20857 char[] listString = cast(char[]) data; 20858 char[][16] buffer; 20859 int count; 20860 char[][] result = buffer[]; 20861 20862 void commit(char[] s) { 20863 if(count == result.length) 20864 result.length += 16; 20865 if(s.length > 7 && s[0 ..7] == "file://") 20866 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 20867 result[count++] = s; 20868 } 20869 20870 size_t last; 20871 foreach(idx, char c; listString) { 20872 if(c == '\n') { 20873 commit(listString[last .. idx - 1]); // a \r 20874 last = idx + 1; // a \n 20875 } 20876 } 20877 20878 if(last < listString.length) { 20879 commit(listString[last .. $]); 20880 } 20881 20882 // FIXME: they are uris now, should I translate it to local file names? 20883 // of course the host name is supposed to be there cuz of X rokking... 20884 20885 dg(result[0 .. count]); 20886 } else version(Windows) { 20887 20888 static struct DROPFILES { 20889 DWORD pFiles; 20890 POINT pt; 20891 BOOL fNC; 20892 BOOL fWide; 20893 } 20894 20895 20896 const(char)[][16] buffer; 20897 int count; 20898 const(char)[][] result = buffer[]; 20899 size_t last; 20900 20901 void commitA(in char[] stuff) { 20902 if(count == result.length) 20903 result.length += 16; 20904 result[count++] = stuff; 20905 } 20906 20907 void commitW(in wchar[] stuff) { 20908 commitA(makeUtf8StringFromWindowsString(stuff)); 20909 } 20910 20911 void magic(T)(T chars) { 20912 size_t idx; 20913 while(chars[idx]) { 20914 last = idx; 20915 while(chars[idx]) { 20916 idx++; 20917 } 20918 static if(is(T == char*)) 20919 commitA(chars[last .. idx]); 20920 else 20921 commitW(chars[last .. idx]); 20922 idx++; 20923 } 20924 } 20925 20926 auto df = cast(DROPFILES*) data.ptr; 20927 if(df.fWide) { 20928 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 20929 magic(chars); 20930 } else { 20931 char* chars = cast(char*) (data.ptr + df.pFiles); 20932 magic(chars); 20933 } 20934 dg(result[0 .. count]); 20935 } 20936 } 20937 } 20938 20939 /++ 20940 Interface to describe data being dragged. See also [draggable] helper function. 20941 20942 $(PITFALL This is not yet stable and may break in future versions without notice.) 20943 20944 History: 20945 Added February 19, 2021 20946 +/ 20947 interface DraggableData { 20948 version(X11) 20949 alias FormatId = Atom; 20950 else 20951 alias FormatId = uint; 20952 /++ 20953 Gets the platform-specific FormatId associated with the given named format. 20954 20955 This may be a MIME type, but may also be other various strings defined by the 20956 programs you want to interoperate with. 20957 20958 FIXME: sdpy needs to offer data adapter things that look for compatible formats 20959 and convert it to some particular type for you. 20960 +/ 20961 static FormatId getFormatId(string name)() { 20962 version(X11) 20963 return GetAtom!name(XDisplayConnection.get); 20964 else version(Windows) { 20965 static UINT cache; 20966 if(!cache) 20967 cache = RegisterClipboardFormatA(name); 20968 return cache; 20969 } else 20970 throw new NotYetImplementedException(); 20971 } 20972 20973 /++ 20974 Looks up a string to represent the name for the given format, if there is one. 20975 20976 You should avoid using this function because it is slow. It is provided more for 20977 debugging than for primary use. 20978 +/ 20979 static string getFormatName(FormatId format) { 20980 version(X11) { 20981 if(format == 0) 20982 return "None"; 20983 else 20984 return getAtomName(format, XDisplayConnection.get); 20985 } else version(Windows) { 20986 switch(format) { 20987 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 20988 case CF_DIBV5: return "CF_DIBV5"; 20989 case CF_RIFF: return "CF_RIFF"; 20990 case CF_WAVE: return "CF_WAVE"; 20991 case CF_HDROP: return "CF_HDROP"; 20992 default: 20993 char[1024] name; 20994 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 20995 return name[0 .. count].idup; 20996 } 20997 } 20998 } 20999 21000 FormatId[] availableFormats(); 21001 // Return the slice of data you filled, empty slice if done. 21002 // this is to support the incremental thing 21003 ubyte[] getData(FormatId format, return scope ubyte[] data); 21004 21005 size_t dataLength(FormatId format); 21006 } 21007 21008 /++ 21009 $(PITFALL This is not yet stable and may break in future versions without notice.) 21010 21011 History: 21012 Added February 19, 2021 21013 +/ 21014 DraggableData draggable(string s) { 21015 version(X11) 21016 return new class X11SetSelectionHandler_Text, DraggableData { 21017 this() { 21018 super(s); 21019 } 21020 21021 override FormatId[] availableFormats() { 21022 return X11SetSelectionHandler_Text.availableFormats(); 21023 } 21024 21025 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 21026 return X11SetSelectionHandler_Text.getData(format, data); 21027 } 21028 21029 size_t dataLength(FormatId format) { 21030 return s.length; 21031 } 21032 }; 21033 version(Windows) 21034 return new class DraggableData { 21035 FormatId[] availableFormats() { 21036 return [CF_UNICODETEXT]; 21037 } 21038 21039 ubyte[] getData(FormatId format, return scope ubyte[] data) { 21040 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 21041 } 21042 21043 size_t dataLength(FormatId format) { 21044 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 21045 } 21046 }; 21047 } 21048 21049 /++ 21050 $(PITFALL This is not yet stable and may break in future versions without notice.) 21051 21052 History: 21053 Added February 19, 2021 21054 +/ 21055 /// Group: drag_and_drop 21056 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 21057 in { 21058 assert(window !is null); 21059 assert(handler !is null); 21060 } 21061 do 21062 { 21063 version(X11) { 21064 auto sh = cast(X11SetSelectionHandler) handler; 21065 if(sh is null) { 21066 // gotta make my own adapter. 21067 sh = new class X11SetSelectionHandler { 21068 mixin X11SetSelectionHandler_Basics; 21069 21070 Atom[] availableFormats() { return handler.availableFormats(); } 21071 ubyte[] getData(Atom format, return scope ubyte[] data) { 21072 return handler.getData(format, data); 21073 } 21074 21075 // since the drop selection is only ever used once it isn't important 21076 // to reset it. 21077 void done() {} 21078 }; 21079 } 21080 return doDragDropX11(window, sh, action); 21081 } else version(Windows) { 21082 return doDragDropWindows(window, handler, action); 21083 } else throw new NotYetImplementedException(); 21084 } 21085 21086 version(Windows) 21087 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 21088 IDataObject obj = new class IDataObject { 21089 ULONG refCount; 21090 ULONG AddRef() { 21091 return ++refCount; 21092 } 21093 ULONG Release() { 21094 return --refCount; 21095 } 21096 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21097 if (IID_IUnknown == *riid) { 21098 *ppv = cast(void*) cast(IUnknown) this; 21099 } 21100 else if (IID_IDataObject == *riid) { 21101 *ppv = cast(void*) cast(IDataObject) this; 21102 } 21103 else { 21104 *ppv = null; 21105 return E_NOINTERFACE; 21106 } 21107 21108 AddRef(); 21109 return NOERROR; 21110 } 21111 21112 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 21113 // writeln("Advise"); 21114 return E_NOTIMPL; 21115 } 21116 HRESULT DUnadvise(DWORD dwConnection) { 21117 return E_NOTIMPL; 21118 } 21119 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 21120 // writeln("EnumDAdvise"); 21121 return OLE_E_ADVISENOTSUPPORTED; 21122 } 21123 // tell what formats it supports 21124 21125 FORMATETC[] types; 21126 this() { 21127 FORMATETC t; 21128 foreach(ty; handler.availableFormats()) { 21129 assert(ty <= ushort.max && ty >= 0); 21130 t.cfFormat = cast(ushort) ty; 21131 t.lindex = -1; 21132 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21133 t.tymed = TYMED.TYMED_HGLOBAL; 21134 } 21135 types ~= t; 21136 } 21137 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 21138 if(dwDirection == DATADIR.DATADIR_GET) { 21139 *ppenumFormatEtc = new class IEnumFORMATETC { 21140 ULONG refCount; 21141 ULONG AddRef() { 21142 return ++refCount; 21143 } 21144 ULONG Release() { 21145 return --refCount; 21146 } 21147 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21148 if (IID_IUnknown == *riid) { 21149 *ppv = cast(void*) cast(IUnknown) this; 21150 } 21151 else if (IID_IEnumFORMATETC == *riid) { 21152 *ppv = cast(void*) cast(IEnumFORMATETC) this; 21153 } 21154 else { 21155 *ppv = null; 21156 return E_NOINTERFACE; 21157 } 21158 21159 AddRef(); 21160 return NOERROR; 21161 } 21162 21163 21164 int pos; 21165 this() { 21166 pos = 0; 21167 } 21168 21169 HRESULT Clone(IEnumFORMATETC* ppenum) { 21170 // writeln("clone"); 21171 return E_NOTIMPL; // FIXME 21172 } 21173 21174 // Caller is responsible for freeing memory 21175 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 21176 // fetched may be null if celt is one 21177 if(celt != 1) 21178 return E_NOTIMPL; // FIXME 21179 21180 if(celt + pos > types.length) 21181 return S_FALSE; 21182 21183 *rgelt = types[pos++]; 21184 21185 if(pceltFetched !is null) 21186 *pceltFetched = 1; 21187 21188 // writeln("ok celt ", celt); 21189 return S_OK; 21190 } 21191 21192 HRESULT Reset() { 21193 pos = 0; 21194 return S_OK; 21195 } 21196 21197 HRESULT Skip(ULONG celt) { 21198 if(celt + pos <= types.length) { 21199 pos += celt; 21200 return S_OK; 21201 } 21202 return S_FALSE; 21203 } 21204 }; 21205 21206 return S_OK; 21207 } else 21208 return E_NOTIMPL; 21209 } 21210 // given a format, return the format you'd prefer to use cuz it is identical 21211 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 21212 // FIXME: prolly could be better but meh 21213 // writeln("gcf: ", *pformatectIn); 21214 *pformatetcOut = *pformatectIn; 21215 return S_OK; 21216 } 21217 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21218 foreach(ty; types) { 21219 if(ty == *pformatetcIn) { 21220 auto format = ty.cfFormat; 21221 // writeln("A: ", *pformatetcIn, "\nB: ", ty); 21222 STGMEDIUM medium; 21223 medium.tymed = TYMED.TYMED_HGLOBAL; 21224 21225 auto sz = handler.dataLength(format); 21226 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 21227 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 21228 if(auto data = cast(wchar*) GlobalLock(handle)) { 21229 auto slice = data[0 .. sz]; 21230 scope(exit) 21231 GlobalUnlock(handle); 21232 21233 handler.getData(format, cast(ubyte[]) slice[]); 21234 } 21235 21236 21237 medium.hGlobal = handle; // FIXME 21238 *pmedium = medium; 21239 return S_OK; 21240 } 21241 } 21242 return DV_E_FORMATETC; 21243 } 21244 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21245 // writeln("GDH: ", *pformatetcIn); 21246 return E_NOTIMPL; // FIXME 21247 } 21248 HRESULT QueryGetData(FORMATETC* pformatetc) { 21249 auto search = *pformatetc; 21250 search.tymed &= TYMED.TYMED_HGLOBAL; 21251 foreach(ty; types) 21252 if(ty == search) { 21253 // writeln("QueryGetData ", search, " ", types[0]); 21254 return S_OK; 21255 } 21256 if(pformatetc.cfFormat==CF_UNICODETEXT) { 21257 //writeln("QueryGetData FALSE ", search, " ", types[0]); 21258 } 21259 return S_FALSE; 21260 } 21261 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 21262 // writeln("SetData: "); 21263 return E_NOTIMPL; 21264 } 21265 }; 21266 21267 21268 IDropSource src = new class IDropSource { 21269 ULONG refCount; 21270 ULONG AddRef() { 21271 return ++refCount; 21272 } 21273 ULONG Release() { 21274 return --refCount; 21275 } 21276 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21277 if (IID_IUnknown == *riid) { 21278 *ppv = cast(void*) cast(IUnknown) this; 21279 } 21280 else if (IID_IDropSource == *riid) { 21281 *ppv = cast(void*) cast(IDropSource) this; 21282 } 21283 else { 21284 *ppv = null; 21285 return E_NOINTERFACE; 21286 } 21287 21288 AddRef(); 21289 return NOERROR; 21290 } 21291 21292 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 21293 if(fEscapePressed) 21294 return DRAGDROP_S_CANCEL; 21295 if(!(grfKeyState & MK_LBUTTON)) 21296 return DRAGDROP_S_DROP; 21297 return S_OK; 21298 } 21299 21300 int GiveFeedback(uint dwEffect) { 21301 return DRAGDROP_S_USEDEFAULTCURSORS; 21302 } 21303 }; 21304 21305 DWORD effect; 21306 21307 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 21308 21309 DROPEFFECT de = win32DragAndDropAction(action); 21310 21311 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 21312 // but still prolly a FIXME 21313 21314 auto ret = DoDragDrop(obj, src, de, &effect); 21315 /+ 21316 if(ret == DRAGDROP_S_DROP) 21317 writeln("drop ", effect); 21318 else if(ret == DRAGDROP_S_CANCEL) 21319 writeln("cancel"); 21320 else if(ret == S_OK) 21321 writeln("ok"); 21322 else writeln(ret); 21323 +/ 21324 21325 return ret; 21326 } 21327 21328 version(Windows) 21329 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 21330 DROPEFFECT de; 21331 21332 with(DragAndDropAction) 21333 with(DROPEFFECT) 21334 final switch(action) { 21335 case none: de = DROPEFFECT_NONE; break; 21336 case copy: de = DROPEFFECT_COPY; break; 21337 case move: de = DROPEFFECT_MOVE; break; 21338 case link: de = DROPEFFECT_LINK; break; 21339 case ask: throw new Exception("ask not implemented yet"); 21340 case custom: throw new Exception("custom not implemented yet"); 21341 } 21342 21343 return de; 21344 } 21345 21346 21347 /++ 21348 History: 21349 Added February 19, 2021 21350 +/ 21351 /// Group: drag_and_drop 21352 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 21353 version(X11) { 21354 auto display = XDisplayConnection.get; 21355 21356 Atom atom = 5; // right??? 21357 21358 XChangeProperty( 21359 display, 21360 window.impl.window, 21361 GetAtom!"XdndAware"(display), 21362 XA_ATOM, 21363 32 /* bits */, 21364 PropModeReplace, 21365 &atom, 21366 1); 21367 21368 window.dropHandler = handler; 21369 } else version(Windows) { 21370 21371 initDnd(); 21372 21373 auto dropTarget = new class (handler) IDropTarget { 21374 DropHandler handler; 21375 this(DropHandler handler) { 21376 this.handler = handler; 21377 } 21378 ULONG refCount; 21379 ULONG AddRef() { 21380 return ++refCount; 21381 } 21382 ULONG Release() { 21383 return --refCount; 21384 } 21385 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21386 if (IID_IUnknown == *riid) { 21387 *ppv = cast(void*) cast(IUnknown) this; 21388 } 21389 else if (IID_IDropTarget == *riid) { 21390 *ppv = cast(void*) cast(IDropTarget) this; 21391 } 21392 else { 21393 *ppv = null; 21394 return E_NOINTERFACE; 21395 } 21396 21397 AddRef(); 21398 return NOERROR; 21399 } 21400 21401 21402 // /////////////////// 21403 21404 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21405 DropPackage dropPackage = DropPackage(pDataObj); 21406 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 21407 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 21408 } 21409 21410 HRESULT DragLeave() { 21411 handler.dragLeave(); 21412 // release the IDataObject if needed 21413 return S_OK; 21414 } 21415 21416 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21417 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 21418 21419 *pdwEffect = win32DragAndDropAction(res.action); 21420 // same as DragEnter basically 21421 return S_OK; 21422 } 21423 21424 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21425 DropPackage pkg = DropPackage(pDataObj); 21426 handler.drop(&pkg); 21427 21428 return S_OK; 21429 } 21430 }; 21431 // Windows can hold on to the handler and try to call it 21432 // during which time the GC can't see it. so important to 21433 // manually manage this. At some point i'll FIXME and make 21434 // all my com instances manually managed since they supposed 21435 // to respect the refcount. 21436 import core.memory; 21437 GC.addRoot(cast(void*) dropTarget); 21438 21439 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 21440 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 21441 21442 window.dropHandler = handler; 21443 } else throw new NotYetImplementedException(); 21444 } 21445 21446 21447 21448 static if(UsingSimpledisplayX11) { 21449 21450 enum _NET_WM_STATE_ADD = 1; 21451 enum _NET_WM_STATE_REMOVE = 0; 21452 enum _NET_WM_STATE_TOGGLE = 2; 21453 21454 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 21455 void demandAttention(SimpleWindow window, bool needs = true) { 21456 demandAttention(window.impl.window, needs); 21457 } 21458 21459 /// ditto 21460 void demandAttention(Window window, bool needs = true) { 21461 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 21462 } 21463 21464 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 21465 auto display = XDisplayConnection.get(); 21466 if(atom == None) 21467 return; // non-failure error 21468 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 21469 21470 XClientMessageEvent xclient; 21471 21472 xclient.type = EventType.ClientMessage; 21473 xclient.window = window; 21474 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 21475 xclient.format = 32; 21476 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 21477 xclient.data.l[1] = atom; 21478 xclient.data.l[2] = atom2; 21479 xclient.data.l[3] = 1; 21480 // [3] == source. 0 == unknown, 1 == app, 2 == else 21481 21482 XSendEvent( 21483 display, 21484 RootWindow(display, DefaultScreen(display)), 21485 false, 21486 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 21487 cast(XEvent*) &xclient 21488 ); 21489 21490 /+ 21491 XChangeProperty( 21492 display, 21493 window.impl.window, 21494 GetAtom!"_NET_WM_STATE"(display), 21495 XA_ATOM, 21496 32 /* bits */, 21497 PropModeAppend, 21498 &atom, 21499 1); 21500 +/ 21501 } 21502 21503 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 21504 Atom actionAtom; 21505 with(DragAndDropAction) 21506 final switch(action) { 21507 case none: actionAtom = None; break; 21508 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 21509 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 21510 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 21511 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 21512 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 21513 } 21514 21515 return actionAtom; 21516 } 21517 21518 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 21519 // FIXME: I need to show user feedback somehow. 21520 auto display = XDisplayConnection.get; 21521 21522 auto actionAtom = dndActionAtom(display, action); 21523 assert(actionAtom, "Don't use action none to accept a drop"); 21524 21525 setX11Selection!"XdndSelection"(window, handler, null); 21526 21527 auto oldKeyHandler = window.handleKeyEvent; 21528 scope(exit) window.handleKeyEvent = oldKeyHandler; 21529 21530 auto oldCharHandler = window.handleCharEvent; 21531 scope(exit) window.handleCharEvent = oldCharHandler; 21532 21533 auto oldMouseHandler = window.handleMouseEvent; 21534 scope(exit) window.handleMouseEvent = oldMouseHandler; 21535 21536 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 21537 21538 import core.sys.posix.sys.time; 21539 timeval tv; 21540 gettimeofday(&tv, null); 21541 21542 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 21543 21544 Time lastMouseTimestamp; 21545 21546 bool dnding = true; 21547 Window lastIn = None; 21548 21549 void leave() { 21550 if(lastIn == None) 21551 return; 21552 21553 XEvent ev; 21554 ev.xclient.type = EventType.ClientMessage; 21555 ev.xclient.window = lastIn; 21556 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 21557 ev.xclient.format = 32; 21558 ev.xclient.data.l[0] = window.impl.window; 21559 21560 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21561 XFlush(display); 21562 21563 lastIn = None; 21564 } 21565 21566 void enter(Window w) { 21567 assert(lastIn == None); 21568 21569 lastIn = w; 21570 21571 XEvent ev; 21572 ev.xclient.type = EventType.ClientMessage; 21573 ev.xclient.window = lastIn; 21574 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 21575 ev.xclient.format = 32; 21576 ev.xclient.data.l[0] = window.impl.window; 21577 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 21578 21579 auto types = handler.availableFormats(); 21580 assert(types.length > 0); 21581 21582 ev.xclient.data.l[2] = types[0]; 21583 if(types.length > 1) 21584 ev.xclient.data.l[3] = types[1]; 21585 if(types.length > 2) 21586 ev.xclient.data.l[4] = types[2]; 21587 21588 // FIXME: other types?!?!? and make sure we skip TARGETS 21589 21590 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21591 XFlush(display); 21592 } 21593 21594 void position(int rootX, int rootY) { 21595 assert(lastIn != None); 21596 21597 XEvent ev; 21598 ev.xclient.type = EventType.ClientMessage; 21599 ev.xclient.window = lastIn; 21600 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 21601 ev.xclient.format = 32; 21602 ev.xclient.data.l[0] = window.impl.window; 21603 ev.xclient.data.l[1] = 0; // reserved 21604 ev.xclient.data.l[2] = (rootX << 16) | rootY; 21605 ev.xclient.data.l[3] = dataTimestamp; 21606 ev.xclient.data.l[4] = actionAtom; 21607 21608 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21609 XFlush(display); 21610 21611 } 21612 21613 void drop() { 21614 XEvent ev; 21615 ev.xclient.type = EventType.ClientMessage; 21616 ev.xclient.window = lastIn; 21617 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 21618 ev.xclient.format = 32; 21619 ev.xclient.data.l[0] = window.impl.window; 21620 ev.xclient.data.l[1] = 0; // reserved 21621 ev.xclient.data.l[2] = dataTimestamp; 21622 21623 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21624 XFlush(display); 21625 21626 lastIn = None; 21627 dnding = false; 21628 } 21629 21630 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 21631 // but idk if i should... 21632 21633 window.setEventHandlers( 21634 delegate(KeyEvent ev) { 21635 if(ev.pressed == true && ev.key == Key.Escape) { 21636 // cancel 21637 dnding = false; 21638 } 21639 }, 21640 delegate(MouseEvent ev) { 21641 if(ev.timestamp < lastMouseTimestamp) 21642 return; 21643 21644 lastMouseTimestamp = ev.timestamp; 21645 21646 if(ev.type == MouseEventType.motion) { 21647 auto display = XDisplayConnection.get; 21648 auto root = RootWindow(display, DefaultScreen(display)); 21649 21650 Window topWindow; 21651 int rootX, rootY; 21652 21653 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 21654 21655 if(topWindow == None) 21656 return; 21657 21658 top: 21659 if(auto result = topWindow in eligibility) { 21660 auto dropWindow = *result; 21661 if(dropWindow == None) { 21662 leave(); 21663 return; 21664 } 21665 21666 if(dropWindow != lastIn) { 21667 leave(); 21668 enter(dropWindow); 21669 position(rootX, rootY); 21670 } else { 21671 position(rootX, rootY); 21672 } 21673 } else { 21674 // determine eligibility 21675 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 21676 if(data.length == 1) { 21677 // in case there is no WM or it isn't reparenting 21678 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 21679 } else { 21680 21681 Window tryScanChildren(Window search, int maxRecurse) { 21682 // could be reparenting window manager, so gotta check the next few children too 21683 Window child; 21684 int x; 21685 int y; 21686 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 21687 21688 if(child == None) 21689 return None; 21690 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 21691 if(data.length == 1) { 21692 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 21693 } else { 21694 if(maxRecurse) 21695 return tryScanChildren(child, maxRecurse - 1); 21696 else 21697 return None; 21698 } 21699 21700 } 21701 21702 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 21703 auto topResult = tryScanChildren(topWindow, 3); 21704 // it is easy to have a false negative due to the mouse going over a WM 21705 // child window like the close button if separate from the frame... so I 21706 // can't really cache negatives, :( 21707 if(topResult != None) { 21708 eligibility[topWindow] = topResult; 21709 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 21710 } 21711 } 21712 21713 } 21714 21715 } else if(ev.type == MouseEventType.buttonReleased) { 21716 drop(); 21717 dnding = false; 21718 } 21719 } 21720 ); 21721 21722 window.grabInput(); 21723 scope(exit) 21724 window.releaseInputGrab(); 21725 21726 21727 EventLoop.get.run(() => dnding); 21728 21729 return 0; 21730 } 21731 21732 /// X-specific 21733 TrueColorImage getWindowNetWmIcon(Window window) { 21734 try { 21735 auto display = XDisplayConnection.get; 21736 21737 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 21738 21739 if (data.length > arch_ulong.sizeof * 2) { 21740 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 21741 // these are an array of rgba images that we have to convert into pixmaps ourself 21742 21743 int width = cast(int) meta[0]; 21744 int height = cast(int) meta[1]; 21745 21746 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 21747 21748 static if(arch_ulong.sizeof == 4) { 21749 bytes = bytes[0 .. width * height * 4]; 21750 alias imageData = bytes; 21751 } else static if(arch_ulong.sizeof == 8) { 21752 bytes = bytes[0 .. width * height * 8]; 21753 auto imageData = new ubyte[](4 * width * height); 21754 } else static assert(0); 21755 21756 21757 21758 // this returns ARGB. Remember it is little-endian so 21759 // we have BGRA 21760 // our thing uses RGBA, which in little endian, is ABGR 21761 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 21762 auto r = bytes[idx + 2]; 21763 auto g = bytes[idx + 1]; 21764 auto b = bytes[idx + 0]; 21765 auto a = bytes[idx + 3]; 21766 21767 imageData[idx2 + 0] = r; 21768 imageData[idx2 + 1] = g; 21769 imageData[idx2 + 2] = b; 21770 imageData[idx2 + 3] = a; 21771 } 21772 21773 return new TrueColorImage(width, height, imageData); 21774 } 21775 21776 return null; 21777 } catch(Exception e) { 21778 return null; 21779 } 21780 } 21781 21782 } /* UsingSimpledisplayX11 */ 21783 21784 21785 void loadBinNameToWindowClassName () { 21786 import core.stdc.stdlib : realloc; 21787 version(linux) { 21788 // args[0] MAY be empty, so we'll just use this 21789 import core.sys.posix.unistd : readlink; 21790 char[1024] ebuf = void; // 1KB should be enough for everyone! 21791 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 21792 if (len < 1) return; 21793 } else /*version(Windows)*/ { 21794 import core.runtime : Runtime; 21795 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 21796 auto ebuf = Runtime.args[0]; 21797 auto len = ebuf.length; 21798 } 21799 auto pos = len; 21800 while (pos > 0 && ebuf[pos-1] != '/') --pos; 21801 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 21802 if (sdpyWindowClassStr is null) return; // oops 21803 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 21804 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 21805 } 21806 21807 /++ 21808 An interface representing a font that is drawn with custom facilities. 21809 21810 You might want [OperatingSystemFont] instead, which represents 21811 a font loaded and drawn by functions native to the operating system. 21812 21813 WARNING: I might still change this. 21814 +/ 21815 interface DrawableFont : MeasurableFont { 21816 /++ 21817 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 21818 21819 Implementations must use the painter's fillColor to draw a rectangle behind the string, 21820 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 21821 fill color, but that's up to the implementation. 21822 +/ 21823 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 21824 21825 /++ 21826 Requests that the given string is added to the image cache. You should only do this rarely, but 21827 if you have a string that you know will be used over and over again, adding it to a cache can 21828 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 21829 to implement this as a do-nothing method). 21830 +/ 21831 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 21832 } 21833 21834 /++ 21835 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 21836 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 21837 21838 You should also consider [OperatingSystemFont], which loads and draws a font with 21839 facilities native to the user's operating system. You might also consider 21840 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 21841 of game, as they have their own ways to draw text too. 21842 21843 Be warned: this can be slow, especially on remote connections to the X server, since 21844 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 21845 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 21846 experiment in your specific case. 21847 21848 Please note that the return type of [DrawableFont] also includes an implementation of 21849 [MeasurableFont]. 21850 +/ 21851 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 21852 import arsd.ttf; 21853 static class ArsdTtfFont : DrawableFont { 21854 TtfFont font; 21855 int size; 21856 this(in ubyte[] data, int size) { 21857 font = TtfFont(data); 21858 this.size = size; 21859 21860 21861 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 21862 int ascent_, descent_, line_gap; 21863 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 21864 21865 int advance, lsb; 21866 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 21867 xWidth = cast(int) (advance * scale); 21868 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 21869 MWidth = cast(int) (advance * scale); 21870 } 21871 21872 private int ascent_; 21873 private int descent_; 21874 private int xWidth; 21875 private int MWidth; 21876 21877 bool isMonospace() { 21878 return xWidth == MWidth; 21879 } 21880 int averageWidth() { 21881 return xWidth; 21882 } 21883 int height() { 21884 return size; 21885 } 21886 int ascent() { 21887 return ascent_; 21888 } 21889 int descent() { 21890 return descent_; 21891 } 21892 21893 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 21894 int width, height; 21895 font.getStringSize(s, size, width, height); 21896 return width; 21897 } 21898 21899 21900 21901 Sprite[string] cache; 21902 21903 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 21904 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 21905 cache[text] = sprite; 21906 } 21907 21908 Image stringToImage(Color fg, Color bg, in char[] text) { 21909 int width, height; 21910 auto data = font.renderString(text, size, width, height); 21911 auto image = new TrueColorImage(width, height); 21912 int pos = 0; 21913 foreach(y; 0 .. height) 21914 foreach(x; 0 .. width) { 21915 fg.a = data[0]; 21916 bg.a = 255; 21917 auto color = alphaBlend(fg, bg); 21918 image.imageData.bytes[pos++] = color.r; 21919 image.imageData.bytes[pos++] = color.g; 21920 image.imageData.bytes[pos++] = color.b; 21921 image.imageData.bytes[pos++] = data[0]; 21922 data = data[1 .. $]; 21923 } 21924 assert(data.length == 0); 21925 21926 return Image.fromMemoryImage(image); 21927 } 21928 21929 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 21930 Sprite sprite = (text in cache) ? *(text in cache) : null; 21931 21932 auto fg = painter.impl._outlineColor; 21933 auto bg = painter.impl._fillColor; 21934 21935 if(sprite !is null) { 21936 auto w = cast(SimpleWindow) painter.window; 21937 assert(w !is null); 21938 21939 sprite.drawAt(painter, upperLeft); 21940 } else { 21941 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 21942 } 21943 } 21944 } 21945 21946 return new ArsdTtfFont(data, size); 21947 } 21948 21949 class NotYetImplementedException : Exception { 21950 this(string file = __FILE__, size_t line = __LINE__) { 21951 super("Not yet implemented", file, line); 21952 } 21953 } 21954 21955 /// 21956 __gshared bool librariesSuccessfullyLoaded = true; 21957 /// 21958 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 21959 21960 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 21961 mixin(staticForeachReplacement!Iface); 21962 21963 void loadDynamicLibrary() @nogc { 21964 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 21965 } 21966 21967 void loadDynamicLibraryForReal() { 21968 foreach(name; __traits(derivedMembers, Iface)) { 21969 mixin("alias tmp = " ~ name ~ ";"); 21970 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 21971 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 21972 } 21973 } 21974 } 21975 21976 private const(char)[] staticForeachReplacement(Iface)() pure { 21977 /* 21978 // just this for gdc 9.... 21979 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 21980 21981 static foreach(name; __traits(derivedMembers, Iface)) 21982 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 21983 */ 21984 21985 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 21986 size_t pos; 21987 21988 void append(in char[] what) { 21989 if(pos + what.length > code.length) 21990 code.length = (code.length * 3) / 2; 21991 code[pos .. pos + what.length] = what[]; 21992 pos += what.length; 21993 } 21994 21995 foreach(name; __traits(derivedMembers, Iface)) { 21996 append(`__gshared typeof(&__traits(getMember, Iface, "`); 21997 append(name); 21998 append(`")) `); 21999 append(name); 22000 append(";"); 22001 } 22002 22003 return code[0 .. pos]; 22004 } 22005 22006 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 22007 mixin(staticForeachReplacement!Iface); 22008 22009 private __gshared void* libHandle; 22010 private __gshared bool attempted; 22011 22012 void loadDynamicLibrary() @nogc { 22013 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 22014 } 22015 22016 bool loadAttempted() { 22017 return attempted; 22018 } 22019 bool loadSuccessful() { 22020 return libHandle !is null; 22021 } 22022 22023 void loadDynamicLibraryForReal() { 22024 attempted = true; 22025 version(Posix) { 22026 import core.sys.posix.dlfcn; 22027 version(OSX) { 22028 version(X11) 22029 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 22030 else 22031 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 22032 } else { 22033 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 22034 if(libHandle is null) 22035 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 22036 } 22037 22038 static void* loadsym(void* l, const char* name) { 22039 import core.stdc.stdlib; 22040 if(l is null) 22041 return &abort; 22042 return dlsym(l, name); 22043 } 22044 } else version(Windows) { 22045 import core.sys.windows.winbase; 22046 libHandle = LoadLibrary(library ~ ".dll"); 22047 static void* loadsym(void* l, const char* name) { 22048 import core.stdc.stdlib; 22049 if(l is null) 22050 return &abort; 22051 return GetProcAddress(l, name); 22052 } 22053 } 22054 if(libHandle is null) { 22055 success = false; 22056 //throw new Exception("load failure of library " ~ library); 22057 } 22058 foreach(name; __traits(derivedMembers, Iface)) { 22059 mixin("alias tmp = " ~ name ~ ";"); 22060 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 22061 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 22062 } 22063 } 22064 22065 void unloadDynamicLibrary() { 22066 version(Posix) { 22067 import core.sys.posix.dlfcn; 22068 dlclose(libHandle); 22069 } else version(Windows) { 22070 import core.sys.windows.winbase; 22071 FreeLibrary(libHandle); 22072 } 22073 foreach(name; __traits(derivedMembers, Iface)) 22074 mixin(name ~ " = null;"); 22075 } 22076 } 22077 22078 /+ 22079 The GC can be called from any thread, and a lot of cleanup must be done 22080 on the gui thread. Since the GC can interrupt any locks - including being 22081 triggered inside a critical section - it is vital to avoid deadlocks to get 22082 these functions called from the right place. 22083 22084 If the buffer overflows, things are going to get leaked. I'm kinda ok with that 22085 right now. 22086 22087 The cleanup function is run when the event loop gets around to it, which is just 22088 whenever there's something there after it has been woken up for other work. It does 22089 NOT wake up the loop itself - can't risk doing that from inside the GC in another thread. 22090 (Well actually it might be ok but i don't wanna mess with it right now.) 22091 +/ 22092 private struct CleanupQueue { 22093 import core.stdc.stdlib; 22094 22095 void queue(alias func, T...)(T args) { 22096 static struct Args { 22097 T args; 22098 } 22099 static struct RealJob { 22100 Job j; 22101 Args a; 22102 } 22103 static void call(Job* data) { 22104 auto rj = cast(RealJob*) data; 22105 func(rj.a.args); 22106 } 22107 22108 RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof); 22109 thing.j.call = &call; 22110 thing.a.args = args; 22111 22112 buffer[tail++] = cast(Job*) thing; 22113 22114 // FIXME: set overflowed 22115 } 22116 22117 void process() { 22118 const tail = this.tail; 22119 22120 while(tail != head) { 22121 Job* job = cast(Job*) buffer[head++]; 22122 job.call(job); 22123 free(job); 22124 } 22125 22126 if(overflowed) 22127 throw new Exception("cleanup overflowed"); 22128 } 22129 22130 private: 22131 22132 ubyte tail; // must ONLY be written by queue 22133 ubyte head; // must ONLY be written by process 22134 bool overflowed; 22135 22136 static struct Job { 22137 void function(Job*) call; 22138 } 22139 22140 void*[256] buffer; 22141 } 22142 private __gshared CleanupQueue cleanupQueue; 22143 22144 version(X11) 22145 /++ 22146 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 22147 22148 $(WARNING 22149 This function is exempted from stability guarantees. 22150 ) 22151 +/ 22152 float customScalingFactorForMonitor(int monitorNumber) { 22153 import core.stdc.stdlib; 22154 auto val = getenv("ARSD_SCALING_FACTOR"); 22155 22156 if(val is null) 22157 return 1.0; 22158 22159 char[16] buffer = 0; 22160 int pos; 22161 22162 const(char)* at = val; 22163 22164 foreach(item; 0 .. monitorNumber + 1) { 22165 if(*at == 0) 22166 break; // reuse the last number when we at the end of the string 22167 pos = 0; 22168 while(pos + 1 < buffer.length && *at && *at != ';') { 22169 buffer[pos++] = *at; 22170 at++; 22171 } 22172 if(*at) 22173 at++; // skip the semicolon 22174 buffer[pos] = 0; 22175 } 22176 22177 //sdpyPrintDebugString(buffer[0 .. pos]); 22178 22179 import core.stdc.math; 22180 auto f = atof(buffer.ptr); 22181 22182 if(f <= 0.0 || isnan(f) || isinf(f)) 22183 return 1.0; 22184 22185 return f; 22186 } 22187 22188 void guiAbortProcess(string msg) { 22189 import core.stdc.stdlib; 22190 version(Windows) { 22191 WCharzBuffer t = WCharzBuffer(msg); 22192 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 22193 } else { 22194 import core.stdc.stdio; 22195 fwrite(msg.ptr, 1, msg.length, stderr); 22196 msg = "\n"; 22197 fwrite(msg.ptr, 1, msg.length, stderr); 22198 fflush(stderr); 22199 } 22200 22201 abort(); 22202 } 22203 22204 private int minInternal(int a, int b) { 22205 return (a < b) ? a : b; 22206 } 22207 22208 private alias scriptable = arsd_jsvar_compatible;