1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 // Search for: FIXME: leaks if multithreaded gc 4 5 // https://freedesktop.org/wiki/Specifications/XDND/ 6 7 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format 8 9 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html 10 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html 11 12 13 // on Mac with X11: -L-L/usr/X11/lib 14 15 /+ 16 17 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works 18 19 Progress bar in taskbar 20 - i can probably just set a property on the window... 21 it sets that prop to an integer 0 .. 100. Taskbar 22 deletes it or window deletes it when it is handled. 23 - prolly display it as a nice little line at the bottom. 24 25 26 from gtk: 27 28 #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" 29 #define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" 30 31 >+ if (cardinal > 0) 32 >+ { 33 >+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display), 34 >+ xid, 35 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name), 36 >+ XA_CARDINAL, 32, 37 >+ PropModeReplace, 38 >+ (guchar *) &cardinal, 1); 39 >+ } 40 >+ else 41 >+ { 42 >+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), 43 >+ xid, 44 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name)); 45 >+ } 46 47 from Windows: 48 49 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3 50 51 interface 52 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 53 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 54 listen for msg, return TRUE 55 interface->SetProgressState(hwnd, TBPF_NORMAL); 56 interface->SetProgressValue(hwnd, 40, 100); 57 58 59 My new notification system. 60 - use a unix socket? or a x property? or a udp port? 61 - could of course also get on the dbus train but ugh. 62 - it could also reply with the info as a string for easy remote examination. 63 64 +/ 65 66 /* 67 Event Loop would be nices: 68 69 * add on idle - runs when nothing else happens 70 * which can specify how long to yield for 71 * send messages without a recipient window 72 * setTimeout 73 * setInterval 74 */ 75 76 /* 77 Classic games I want to add: 78 * my tetris clone 79 * pac man 80 */ 81 82 /* 83 Text layout needs a lot of work. Plain drawText is useful but too 84 limited. It will need some kind of text context thing which it will 85 update and you can pass it on and get more details out of it. 86 87 It will need a bounding box, a current cursor location that is updated 88 as drawing continues, and various changable facts (which can also be 89 changed on the painter i guess) like font, color, size, background, 90 etc. 91 92 We can also fetch the caret location from it somehow. 93 94 Should prolly be an overload of drawText 95 96 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 97 98 WS_EX_NOACTIVATE 99 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 100 full screen windows. Can just set the atom on X. Windows will be harder. 101 102 moving windows. resizing windows. 103 104 hide cursor, capture cursor, change cursor. 105 106 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 107 sure the pieces are there to do its job easily and make other jobs possible. 108 */ 109 110 /++ 111 simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality, 112 including creating windows, drawing on them, working with the clipboard, 113 timers, OpenGL, and more. However, it does NOT provide high level GUI 114 widgets. See my minigui.d, an extension to this module, for that 115 functionality. 116 117 simpledisplay provides cross-platform wrapping for Windows and Linux 118 (and perhaps other OSes that use X11), but also does not prevent you 119 from using the underlying facilities if you need them. It has a goal 120 of working efficiently over a remote X link (at least as far as Xlib 121 reasonably allows.) 122 123 simpledisplay depends on [arsd.color|color.d], which should be available from the 124 same place where you got this file. Other than that, however, it has 125 very few dependencies and ones that don't come with the OS and/or the 126 compiler are all opt-in. 127 128 simpledisplay.d's home base is on my arsd repo on Github. The file is: 129 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 130 131 simpledisplay is basically stable. I plan to refactor the internals, 132 and may add new features and fix bugs, but It do not expect to 133 significantly change the API. It has been stable a few years already now. 134 135 Installation_instructions: 136 137 `simpledisplay.d` does not have any dependencies outside the 138 operating system and `color.d`, so it should just work most the 139 time, but there are a few caveats on some systems: 140 141 On Win32, you can pass `-L/subsystem:windows` if you don't want a 142 console to be automatically allocated. 143 144 Please note when compiling on Win64, you need to explicitly list 145 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 146 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 147 148 If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`; 149 note the "w". 150 151 I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you, 152 but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi). 153 See [EnableWindowsSubsystem] for more information. 154 155 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. 156 157 On Ubuntu, you might need to install X11 development libraries to 158 successfully link. 159 160 $(CONSOLE 161 $ sudo apt-get install libglc-dev 162 $ sudo apt-get install libx11-dev 163 ) 164 165 166 Jump_list: 167 168 Don't worry, you don't have to read this whole documentation file! 169 170 Check out the [#event-example] and [#Pong-example] to get started quickly. 171 172 The main classes you may want to create are [SimpleWindow], [Timer], 173 [Image], and [Sprite]. 174 175 The main functions you'll want are [setClipboardText] and [getClipboardText]. 176 177 There are also platform-specific functions available such as [XDisplayConnection] 178 and [GetAtom] for X11, among others. 179 180 See the examples and topics list below to learn more. 181 182 $(WARNING 183 There should only be one GUI thread per application, 184 and all windows should be created in it and your 185 event loop should run there. 186 187 To do otherwise is undefined behavior and has no 188 cross platform guarantees. 189 ) 190 191 $(H2 About this documentation) 192 193 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. 194 195 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! 196 197 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. 198 199 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. 200 201 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. 202 203 At points, I will talk about implementation details in the documentation. These are sometimes 204 subject to change, but nevertheless useful to understand what is really going on. You can learn 205 more about some of the referenced things by searching the web for info about using them from C. 206 You can always look at the source of simpledisplay.d too for the most authoritative source on 207 its specific implementation. If you disagree with how I did something, please contact me so we 208 can discuss it! 209 210 $(H2 Using with fibers) 211 212 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). 213 214 $(H2 Topics) 215 216 $(H3 $(ID topic-windows) Windows) 217 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 218 window on the user's screen. 219 220 You may create multiple windows, if the underlying platform supports it. You may check 221 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 222 SimpleWindow's constructor at runtime to handle those cases. 223 224 A single running event loop will handle as many windows as needed. 225 226 $(H3 $(ID topic-event-loops) Event loops) 227 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 228 229 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: 230 231 --- 232 // dmd example.d simpledisplay.d color.d 233 import arsd.simpledisplay; 234 void main() { 235 auto window = new SimpleWindow(200, 200); 236 window.eventLoop(0, 237 delegate (dchar) { /* got a character key press */ } 238 ); 239 } 240 --- 241 242 $(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.) 243 244 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. 245 246 On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 247 248 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 249 250 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. 251 252 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 253 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. 254 255 See the [NotificationAreaIcon] class. 256 257 $(H3 $(ID topic-input-handling) Input handling) 258 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 259 260 See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent]. 261 262 $(H3 $(ID topic-2d-drawing) 2d Drawing) 263 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 264 265 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: 266 267 --- 268 // dmd example.d simpledisplay.d color.d 269 import arsd.simpledisplay; 270 void main() { 271 auto window = new SimpleWindow(200, 200); 272 { // introduce sub-scope 273 auto painter = window.draw(); // begin drawing 274 /* draw here */ 275 painter.outlineColor = Color.red; 276 painter.fillColor = Color.black; 277 painter.drawRectangle(Point(0, 0), 200, 200); 278 } // end scope, calling `painter`'s destructor, drawing to the screen. 279 window.eventLoop(0); // handle events 280 } 281 --- 282 283 Painting is done based on two color properties, a pen and a brush. 284 285 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. 286 287 FIXME Add example of 2d opengl drawing here. 288 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 289 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 290 291 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. 292 293 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 294 295 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 296 297 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]. 298 299 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. 300 301 This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color: 302 303 --- 304 import arsd.simpledisplay; 305 306 void main() { 307 auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing); 308 309 float otherColor = 0.0; 310 float colorDelta = 0.05; 311 312 window.redrawOpenGlScene = delegate() { 313 glLoadIdentity(); 314 glBegin(GL_QUADS); 315 316 glColor3f(1.0, otherColor, 0); 317 glVertex3f(-0.8, -0.8, 0); 318 319 glColor3f(1.0, otherColor, 1.0); 320 glVertex3f(0.8, -0.8, 0); 321 322 glColor3f(0, 1.0, otherColor); 323 glVertex3f(0.8, 0.8, 0); 324 325 glColor3f(otherColor, 0, 1.0); 326 glVertex3f(-0.8, 0.8, 0); 327 328 glEnd(); 329 }; 330 331 window.eventLoop(50, () { 332 otherColor += colorDelta; 333 if(otherColor > 1.0) { 334 otherColor = 1.0; 335 colorDelta = -0.05; 336 } 337 if(otherColor < 0) { 338 otherColor = 0; 339 colorDelta = 0.05; 340 } 341 // at the end of the timer, we have to request a redraw 342 // or we won't see the changes. 343 window.redrawOpenGlSceneSoon(); 344 }); 345 } 346 --- 347 348 My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow]. 349 $(H3 $(ID topic-modern-opengl) Modern OpenGL) 350 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. 351 352 This example program shows how you can set up a shader to draw a rectangle: 353 354 --- 355 module opengl3test; 356 import arsd.simpledisplay; 357 358 // based on https://learnopengl.com/Getting-started/Hello-Triangle 359 360 void main() { 361 // First thing we do, before creating the window, is declare what version we want. 362 setOpenGLContextVersion(3, 3); 363 // turning off legacy compat is required to use version 3.3 and newer 364 openGLContextCompatible = false; 365 366 uint VAO; 367 OpenGlShader shader; 368 369 // then we can create the window. 370 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 371 372 // additional setup needs to be done when it is visible, simpledisplay offers a property 373 // for exactly that: 374 window.visibleForTheFirstTime = delegate() { 375 // now with the window loaded, we can start loading the modern opengl functions. 376 377 // you MUST set the context first. 378 window.setAsCurrentOpenGlContext; 379 // then load the remainder of the library 380 gl3.loadDynamicLibrary(); 381 382 // now you can create the shaders, etc. 383 shader = new OpenGlShader( 384 OpenGlShader.Source(GL_VERTEX_SHADER, ` 385 #version 330 core 386 layout (location = 0) in vec3 aPos; 387 void main() { 388 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 389 } 390 `), 391 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 392 #version 330 core 393 out vec4 FragColor; 394 uniform vec4 mycolor; 395 void main() { 396 FragColor = mycolor; 397 } 398 `), 399 ); 400 401 // and do whatever other setup you want. 402 403 float[] vertices = [ 404 0.5f, 0.5f, 0.0f, // top right 405 0.5f, -0.5f, 0.0f, // bottom right 406 -0.5f, -0.5f, 0.0f, // bottom left 407 -0.5f, 0.5f, 0.0f // top left 408 ]; 409 uint[] indices = [ // note that we start from 0! 410 0, 1, 3, // first Triangle 411 1, 2, 3 // second Triangle 412 ]; 413 uint VBO, EBO; 414 glGenVertexArrays(1, &VAO); 415 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 416 glBindVertexArray(VAO); 417 418 glGenBuffers(1, &VBO); 419 glGenBuffers(1, &EBO); 420 421 glBindBuffer(GL_ARRAY_BUFFER, VBO); 422 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 423 424 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 425 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 426 427 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 428 glEnableVertexAttribArray(0); 429 430 // the library will set the initial viewport and trigger our first draw, 431 // so these next two lines are NOT needed. they are just here as comments 432 // to show what would happen next. 433 434 // glViewport(0, 0, window.width, window.height); 435 // window.redrawOpenGlSceneNow(); 436 }; 437 438 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 439 // it is our render method. 440 window.redrawOpenGlScene = delegate() { 441 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 442 glClear(GL_COLOR_BUFFER_BIT); 443 444 glUseProgram(shader.shaderProgram); 445 446 // the shader helper class has methods to set uniforms too 447 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 448 449 glBindVertexArray(VAO); 450 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 451 }; 452 453 window.eventLoop(0); 454 } 455 --- 456 457 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. 458 459 460 $(H3 $(ID topic-images) Displaying images) 461 You can also load PNG images using [arsd.png]. 462 463 --- 464 // dmd example.d simpledisplay.d color.d png.d 465 import arsd.simpledisplay; 466 import arsd.png; 467 468 void main() { 469 auto image = Image.fromMemoryImage(readPng("image.png")); 470 displayImage(image); 471 } 472 --- 473 474 Compile with `dmd example.d simpledisplay.d png.d`. 475 476 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. 477 478 $(H3 $(ID topic-sprites) Sprites) 479 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. 480 481 [Sprite] is also the only facility that currently supports alpha blending without using OpenGL . 482 483 $(H3 $(ID topic-clipboard) Clipboard) 484 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 485 486 It also has helpers for handling X-specific events. 487 488 $(H3 $(ID topic-dnd) Drag and Drop) 489 See [enableDragAndDrop] and [draggable]. 490 491 $(H3 $(ID topic-timers) Timers) 492 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]. 493 494 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. 495 496 --- 497 import arsd.simpledisplay; 498 499 void main() { 500 auto window = new SimpleWindow(400, 400); 501 // every 100 ms, it will draw a random line 502 // on the window. 503 window.eventLoop(100, { 504 auto painter = window.draw(); 505 506 import std.random; 507 // random color 508 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 509 // random line 510 painter.drawLine( 511 Point(uniform(0, window.width), uniform(0, window.height)), 512 Point(uniform(0, window.width), uniform(0, window.height))); 513 514 }); 515 } 516 --- 517 518 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. 519 520 The pulse timer and instances of the [Timer] class may be combined at will. 521 522 --- 523 import arsd.simpledisplay; 524 525 void main() { 526 auto window = new SimpleWindow(400, 400); 527 auto timer = new Timer(1000, delegate { 528 auto painter = window.draw(); 529 painter.clear(); 530 }); 531 532 window.eventLoop(0); 533 } 534 --- 535 536 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 537 538 $(H3 $(ID topic-os-helpers) OS-specific helpers) 539 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. 540 541 See also: `xwindows.d` from my github. 542 543 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 544 `handleNativeEvent` and `handleNativeGlobalEvent`. 545 546 $(H3 $(ID topic-integration) Integration with other libraries) 547 Integration with a third-party event loop is possible. 548 549 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. 550 551 $(H3 $(ID topic-guis) GUI widgets) 552 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! 553 554 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. 555 556 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.) 557 558 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 559 560 $(H2 Platform-specific tips and tricks) 561 562 X_tips: 563 564 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. 565 566 Windows_tips: 567 568 You can add icons or manifest files to your exe using a resource file. 569 570 To create a Windows .ico file, use the gimp or something. I'll write a helper 571 program later. 572 573 Create `yourapp.rc`: 574 575 ```rc 576 1 ICON filename.ico 577 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 578 ``` 579 580 And `yourapp.exe.manifest`: 581 582 ```xml 583 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 584 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 585 <assemblyIdentity 586 version="1.0.0.0" 587 processorArchitecture="*" 588 name="CompanyName.ProductName.YourApplication" 589 type="win32" 590 /> 591 <description>Your application description here.</description> 592 <dependency> 593 <dependentAssembly> 594 <assemblyIdentity 595 type="win32" 596 name="Microsoft.Windows.Common-Controls" 597 version="6.0.0.0" 598 processorArchitecture="*" 599 publicKeyToken="6595b64144ccf1df" 600 language="*" 601 /> 602 </dependentAssembly> 603 </dependency> 604 <application xmlns="urn:schemas-microsoft-com:asm.v3"> 605 <windowsSettings> 606 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style --> 607 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style --> 608 <!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text --> 609 <!-- to render crisply in DPI-unaware contexts --> 610 <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>--> 611 </windowsSettings> 612 </application> 613 </assembly> 614 ``` 615 616 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`. 617 618 Doing this lets you opt into various new things since Windows XP. 619 620 See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests 621 622 $(H2 Tips) 623 624 $(H3 Name conflicts) 625 626 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: 627 628 --- 629 static import sdpy = arsd.simpledisplay; 630 import arsd.simpledisplay : SimpleWindow; 631 632 void main() { 633 auto window = new SimpleWindow(); 634 sdpy.EventLoop.get.run(); 635 } 636 --- 637 638 $(H2 $(ID developer-notes) Developer notes) 639 640 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 641 implementation though. 642 643 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 644 suck. If I was rewriting it, I wouldn't do it that way again. 645 646 This file must not have any more required dependencies. If you need bindings, add 647 them right to this file. Once it gets into druntime and is there for a while, remove 648 bindings from here to avoid conflicts (or put them in an appropriate version block 649 so it continues to just work on old dmd), but wait a couple releases before making the 650 transition so this module remains usable with older versions of dmd. 651 652 You may have optional dependencies if needed by putting them in version blocks or 653 template functions. You may also extend the module with other modules with UFCS without 654 actually editing this - that is nice to do if you can. 655 656 Try to make functions work the same way across operating systems. I typically make 657 it thinly wrap Windows, then emulate that on Linux. 658 659 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 660 Phobos! So try to avoid it. 661 662 See more comments throughout the source. 663 664 I realize this file is fairly large, but over half that is just bindings at the bottom 665 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 666 to understand. I suggest you jump around the source by looking for a particular 667 declaration you're interested in, like `class SimpleWindow` using your editor's search 668 function, then look at one piece at a time. 669 670 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 671 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 672 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 673 674 I live in the eastern United States, so I will most likely not be around at night in 675 that US east timezone. 676 677 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 678 679 Building documentation: use my adrdox generator, `dub run adrdox`. 680 681 Examples: 682 683 $(DIV $(ID Event-example)) 684 $(H3 $(ID event-example) Event example) 685 This program creates a window and draws events inside them as they 686 happen, scrolling the text in the window as needed. Run this program 687 and experiment to get a feel for where basic input events take place 688 in the library. 689 690 --- 691 // dmd example.d simpledisplay.d color.d 692 import arsd.simpledisplay; 693 import std.conv; 694 695 void main() { 696 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 697 698 int y = 0; 699 700 void addLine(string text) { 701 auto painter = window.draw(); 702 703 if(y + painter.fontHeight >= window.height) { 704 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 705 y -= painter.fontHeight; 706 } 707 708 painter.outlineColor = Color.red; 709 painter.fillColor = Color.black; 710 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 711 712 painter.outlineColor = Color.white; 713 714 painter.drawText(Point(10, y), text); 715 716 y += painter.fontHeight; 717 } 718 719 window.eventLoop(1000, 720 () { 721 addLine("Timer went off!"); 722 }, 723 (KeyEvent event) { 724 addLine(to!string(event)); 725 }, 726 (MouseEvent event) { 727 addLine(to!string(event)); 728 }, 729 (dchar ch) { 730 addLine(to!string(ch)); 731 } 732 ); 733 } 734 --- 735 736 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. 737 738 $(COMMENT 739 This program displays a pie chart. Clicking on a color will increase its share of the pie. 740 741 --- 742 743 --- 744 ) 745 746 747 +/ 748 module arsd.simpledisplay; 749 750 // FIXME: tetris demo 751 // FIXME: space invaders demo 752 // FIXME: asteroids demo 753 754 /++ $(ID Pong-example) 755 $(H3 Pong) 756 757 This program creates a little Pong-like game. Player one is controlled 758 with the keyboard. Player two is controlled with the mouse. It demos 759 the pulse timer, event handling, and some basic drawing. 760 +/ 761 version(demos) 762 unittest { 763 // dmd example.d simpledisplay.d color.d 764 import arsd.simpledisplay; 765 766 enum paddleMovementSpeed = 8; 767 enum paddleHeight = 48; 768 769 void main() { 770 auto window = new SimpleWindow(600, 400, "Pong game!"); 771 772 int playerOnePosition, playerTwoPosition; 773 int playerOneMovement, playerTwoMovement; 774 int playerOneScore, playerTwoScore; 775 776 int ballX, ballY; 777 int ballDx, ballDy; 778 779 void serve() { 780 import std.random; 781 782 ballX = window.width / 2; 783 ballY = window.height / 2; 784 ballDx = uniform(-4, 4) * 3; 785 ballDy = uniform(-4, 4) * 3; 786 if(ballDx == 0) 787 ballDx = uniform(0, 2) == 0 ? 3 : -3; 788 } 789 790 serve(); 791 792 window.eventLoop(50, // set a 50 ms timer pulls 793 // This runs once per timer pulse 794 delegate () { 795 auto painter = window.draw(); 796 797 painter.clear(); 798 799 // Update everyone's motion 800 playerOnePosition += playerOneMovement; 801 playerTwoPosition += playerTwoMovement; 802 803 ballX += ballDx; 804 ballY += ballDy; 805 806 // Bounce off the top and bottom edges of the window 807 if(ballY + 7 >= window.height) 808 ballDy = -ballDy; 809 if(ballY - 8 <= 0) 810 ballDy = -ballDy; 811 812 // Bounce off the paddle, if it is in position 813 if(ballX - 8 <= 16) { 814 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 815 ballDx = -ballDx + 1; // add some speed to keep it interesting 816 ballDy += playerOneMovement; // and y movement based on your controls too 817 ballX = 24; // move it past the paddle so it doesn't wiggle inside 818 } else { 819 // Missed it 820 playerTwoScore ++; 821 serve(); 822 } 823 } 824 825 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 826 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 827 ballDx = -ballDx - 1; 828 ballDy += playerTwoMovement; 829 ballX = window.width - 24; 830 } else { 831 // Missed it 832 playerOneScore ++; 833 serve(); 834 } 835 } 836 837 // Draw the paddles 838 painter.outlineColor = Color.black; 839 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 840 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 841 842 // Draw the ball 843 painter.fillColor = Color.red; 844 painter.outlineColor = Color.yellow; 845 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 846 847 // Draw the score 848 painter.outlineColor = Color.blue; 849 import std.conv; 850 painter.drawText(Point(64, 4), to!string(playerOneScore)); 851 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 852 853 }, 854 delegate (KeyEvent event) { 855 // Player 1's controls are the arrow keys on the keyboard 856 if(event.key == Key.Down) 857 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 858 if(event.key == Key.Up) 859 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 860 861 }, 862 delegate (MouseEvent event) { 863 // Player 2's controls are mouse movement while the left button is held down 864 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 865 if(event.dy > 0) 866 playerTwoMovement = paddleMovementSpeed; 867 else if(event.dy < 0) 868 playerTwoMovement = -paddleMovementSpeed; 869 } else { 870 playerTwoMovement = 0; 871 } 872 } 873 ); 874 } 875 } 876 877 /++ $(H3 $(ID example-minesweeper) Minesweeper) 878 879 This minesweeper demo shows how we can implement another classic 880 game with simpledisplay and shows some mouse input and basic output 881 code. 882 +/ 883 version(demos) 884 unittest { 885 import arsd.simpledisplay; 886 887 enum GameSquare { 888 mine = 0, 889 clear, 890 m1, m2, m3, m4, m5, m6, m7, m8 891 } 892 893 enum UserSquare { 894 unknown, 895 revealed, 896 flagged, 897 questioned 898 } 899 900 enum GameState { 901 inProgress, 902 lose, 903 win 904 } 905 906 GameSquare[] board; 907 UserSquare[] userState; 908 GameState gameState; 909 int boardWidth; 910 int boardHeight; 911 912 bool isMine(int x, int y) { 913 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 914 return false; 915 return board[y * boardWidth + x] == GameSquare.mine; 916 } 917 918 GameState reveal(int x, int y) { 919 if(board[y * boardWidth + x] == GameSquare.clear) { 920 floodFill(userState, boardWidth, boardHeight, 921 UserSquare.unknown, UserSquare.revealed, 922 x, y, 923 (x, y) { 924 if(board[y * boardWidth + x] == GameSquare.clear) 925 return true; 926 else { 927 userState[y * boardWidth + x] = UserSquare.revealed; 928 return false; 929 } 930 }); 931 } else { 932 userState[y * boardWidth + x] = UserSquare.revealed; 933 if(isMine(x, y)) 934 return GameState.lose; 935 } 936 937 foreach(state; userState) { 938 if(state == UserSquare.unknown || state == UserSquare.questioned) 939 return GameState.inProgress; 940 } 941 942 return GameState.win; 943 } 944 945 void initializeBoard(int width, int height, int numberOfMines) { 946 boardWidth = width; 947 boardHeight = height; 948 board.length = width * height; 949 950 userState.length = width * height; 951 userState[] = UserSquare.unknown; 952 953 import std.algorithm, std.random, std.range; 954 955 board[] = GameSquare.clear; 956 957 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 958 board[minePosition] = GameSquare.mine; 959 960 int x; 961 int y; 962 foreach(idx, ref square; board) { 963 if(square == GameSquare.clear) { 964 int danger = 0; 965 danger += isMine(x-1, y-1)?1:0; 966 danger += isMine(x-1, y)?1:0; 967 danger += isMine(x-1, y+1)?1:0; 968 danger += isMine(x, y-1)?1:0; 969 danger += isMine(x, y+1)?1:0; 970 danger += isMine(x+1, y-1)?1:0; 971 danger += isMine(x+1, y)?1:0; 972 danger += isMine(x+1, y+1)?1:0; 973 974 square = cast(GameSquare) (danger + 1); 975 } 976 977 x++; 978 if(x == width) { 979 x = 0; 980 y++; 981 } 982 } 983 } 984 985 void redraw(SimpleWindow window) { 986 import std.conv; 987 988 auto painter = window.draw(); 989 990 painter.clear(); 991 992 final switch(gameState) with(GameState) { 993 case inProgress: 994 break; 995 case win: 996 painter.fillColor = Color.green; 997 painter.drawRectangle(Point(0, 0), window.width, window.height); 998 return; 999 case lose: 1000 painter.fillColor = Color.red; 1001 painter.drawRectangle(Point(0, 0), window.width, window.height); 1002 return; 1003 } 1004 1005 int x = 0; 1006 int y = 0; 1007 1008 foreach(idx, square; board) { 1009 auto state = userState[idx]; 1010 1011 final switch(state) with(UserSquare) { 1012 case unknown: 1013 painter.outlineColor = Color.black; 1014 painter.fillColor = Color(128,128,128); 1015 1016 painter.drawRectangle( 1017 Point(x * 20, y * 20), 1018 20, 20 1019 ); 1020 break; 1021 case revealed: 1022 if(square == GameSquare.clear) { 1023 painter.outlineColor = Color.white; 1024 painter.fillColor = Color.white; 1025 1026 painter.drawRectangle( 1027 Point(x * 20, y * 20), 1028 20, 20 1029 ); 1030 } else { 1031 painter.outlineColor = Color.black; 1032 painter.fillColor = Color.white; 1033 1034 painter.drawText( 1035 Point(x * 20, y * 20), 1036 to!string(square)[1..2], 1037 Point(x * 20 + 20, y * 20 + 20), 1038 TextAlignment.Center | TextAlignment.VerticalCenter); 1039 } 1040 break; 1041 case flagged: 1042 painter.outlineColor = Color.black; 1043 painter.fillColor = Color.red; 1044 painter.drawRectangle( 1045 Point(x * 20, y * 20), 1046 20, 20 1047 ); 1048 break; 1049 case questioned: 1050 painter.outlineColor = Color.black; 1051 painter.fillColor = Color.yellow; 1052 painter.drawRectangle( 1053 Point(x * 20, y * 20), 1054 20, 20 1055 ); 1056 break; 1057 } 1058 1059 x++; 1060 if(x == boardWidth) { 1061 x = 0; 1062 y++; 1063 } 1064 } 1065 1066 } 1067 1068 void main() { 1069 auto window = new SimpleWindow(200, 200); 1070 1071 initializeBoard(10, 10, 10); 1072 1073 redraw(window); 1074 window.eventLoop(0, 1075 delegate (MouseEvent me) { 1076 if(me.type != MouseEventType.buttonPressed) 1077 return; 1078 auto x = me.x / 20; 1079 auto y = me.y / 20; 1080 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1081 if(me.button == MouseButton.left) { 1082 gameState = reveal(x, y); 1083 } else { 1084 userState[y*boardWidth+x] = UserSquare.flagged; 1085 } 1086 redraw(window); 1087 } 1088 } 1089 ); 1090 } 1091 } 1092 1093 /* 1094 version(OSX) { 1095 version=without_opengl; 1096 version=allow_unimplemented_features; 1097 version=OSXCocoa; 1098 pragma(linkerDirective, "-framework Cocoa"); 1099 } 1100 */ 1101 1102 version(without_opengl) { 1103 enum SdpyIsUsingIVGLBinds = false; 1104 } else /*version(Posix)*/ { 1105 static if (__traits(compiles, (){import iv.glbinds;})) { 1106 enum SdpyIsUsingIVGLBinds = true; 1107 public import iv.glbinds; 1108 //pragma(msg, "SDPY: using iv.glbinds"); 1109 } else { 1110 enum SdpyIsUsingIVGLBinds = false; 1111 } 1112 //} else { 1113 // enum SdpyIsUsingIVGLBinds = false; 1114 } 1115 1116 1117 version(Windows) { 1118 //import core.sys.windows.windows; 1119 import core.sys.windows.winnls; 1120 import core.sys.windows.windef; 1121 import core.sys.windows.basetyps; 1122 import core.sys.windows.winbase; 1123 import core.sys.windows.winuser; 1124 import core.sys.windows.shellapi; 1125 import core.sys.windows.wingdi; 1126 static import gdi = core.sys.windows.wingdi; // so i 1127 1128 pragma(lib, "gdi32"); 1129 pragma(lib, "user32"); 1130 1131 // for AlphaBlend... a breaking change.... 1132 version(CRuntime_DigitalMars) { } else 1133 pragma(lib, "msimg32"); 1134 } else version (linux) { 1135 //k8: this is hack for rdmd. sorry. 1136 static import core.sys.linux.epoll; 1137 static import core.sys.linux.timerfd; 1138 } 1139 1140 1141 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1142 1143 // http://wiki.dlang.org/Simpledisplay.d 1144 1145 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1146 1147 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1148 // but can i control the scroll lock led 1149 1150 1151 // Note: if you are using Image on X, you might want to do: 1152 /* 1153 static if(UsingSimpledisplayX11) { 1154 if(!Image.impl.xshmAvailable) { 1155 // the images will use the slower XPutImage, you might 1156 // want to consider an alternative method to get better speed 1157 } 1158 } 1159 1160 If the shared memory extension is available though, simpledisplay uses it 1161 for a significant speed boost whenever you draw large Images. 1162 */ 1163 1164 // 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. 1165 1166 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1167 1168 /* 1169 Biggest FIXME: 1170 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1171 1172 clean up opengl contexts when their windows close 1173 1174 fix resizing the bitmaps/pixmaps 1175 */ 1176 1177 // BTW on Windows: 1178 // -L/SUBSYSTEM:WINDOWS:5.0 1179 // to dmd will make a nice windows binary w/o a console if you want that. 1180 1181 /* 1182 Stuff to add: 1183 1184 use multibyte functions everywhere we can 1185 1186 OpenGL windows 1187 more event stuff 1188 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1189 1190 1191 resizeEvent 1192 and make the windows non-resizable by default, 1193 or perhaps stretched (if I can find something in X like StretchBlt) 1194 1195 take a screenshot function! 1196 1197 Pens and brushes? 1198 Maybe a global event loop? 1199 1200 Mouse deltas 1201 Key items 1202 */ 1203 1204 /* 1205 From MSDN: 1206 1207 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1208 1209 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. 1210 1211 */ 1212 1213 version(linux) { 1214 version = X11; 1215 version(without_libnotify) { 1216 // we cool 1217 } 1218 else 1219 version = libnotify; 1220 } 1221 1222 version(libnotify) { 1223 pragma(lib, "dl"); 1224 import core.sys.posix.dlfcn; 1225 1226 void delegate()[int] libnotify_action_delegates; 1227 int libnotify_action_delegates_count; 1228 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1229 auto idx = cast(int) user_data; 1230 if(auto dgptr = idx in libnotify_action_delegates) { 1231 (*dgptr)(); 1232 libnotify_action_delegates.remove(idx); 1233 } 1234 } 1235 1236 struct C_DynamicLibrary { 1237 void* handle; 1238 this(string name) { 1239 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1240 if(handle is null) 1241 throw new Exception("dlopen"); 1242 } 1243 1244 void close() { 1245 dlclose(handle); 1246 } 1247 1248 ~this() { 1249 // close 1250 } 1251 1252 // FIXME: this looks up by name every time.... 1253 template call(string func, Ret, Args...) { 1254 extern(C) Ret function(Args) fptr; 1255 typeof(fptr) call() { 1256 fptr = cast(typeof(fptr)) dlsym(handle, func); 1257 return fptr; 1258 } 1259 } 1260 } 1261 1262 C_DynamicLibrary* libnotify; 1263 } 1264 1265 version(OSX) { 1266 version(OSXCocoa) {} 1267 else { version = X11; } 1268 } 1269 //version = OSXCocoa; // this was written by KennyTM 1270 version(FreeBSD) 1271 version = X11; 1272 version(Solaris) 1273 version = X11; 1274 1275 version(X11) { 1276 version(without_xft) {} 1277 else version=with_xft; 1278 } 1279 1280 void featureNotImplemented()() { 1281 version(allow_unimplemented_features) 1282 throw new NotYetImplementedException(); 1283 else 1284 static assert(0); 1285 } 1286 1287 // these are so the static asserts don't trigger unless you want to 1288 // add support to it for an OS 1289 version(Windows) 1290 version = with_timer; 1291 version(linux) 1292 version = with_timer; 1293 1294 version(with_timer) 1295 enum bool SimpledisplayTimerAvailable = true; 1296 else 1297 enum bool SimpledisplayTimerAvailable = false; 1298 1299 /// 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. 1300 version(Windows) 1301 enum bool UsingSimpledisplayWindows = true; 1302 else 1303 enum bool UsingSimpledisplayWindows = false; 1304 1305 /// 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. 1306 version(X11) 1307 enum bool UsingSimpledisplayX11 = true; 1308 else 1309 enum bool UsingSimpledisplayX11 = false; 1310 1311 /// 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. 1312 version(OSXCocoa) 1313 enum bool UsingSimpledisplayCocoa = true; 1314 else 1315 enum bool UsingSimpledisplayCocoa = false; 1316 1317 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1318 version(Windows) 1319 enum multipleWindowsSupported = true; 1320 else version(X11) 1321 enum multipleWindowsSupported = true; 1322 else version(OSXCocoa) 1323 enum multipleWindowsSupported = true; 1324 else 1325 static assert(0); 1326 1327 version(without_opengl) 1328 enum bool OpenGlEnabled = false; 1329 else 1330 enum bool OpenGlEnabled = true; 1331 1332 /++ 1333 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1334 If you mix this in above your `main` function, you no longer need to use the linker 1335 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1336 1337 It does nothing if not compiling for Windows, so you need not version it out yourself. 1338 1339 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1340 stderr writeln. It will fail and throw an exception. 1341 1342 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1343 1344 History: 1345 Added November 24, 2021 (dub v10.4) 1346 +/ 1347 mixin template EnableWindowsSubsystem() { 1348 version(Windows) 1349 version(CRuntime_Microsoft) { 1350 pragma(linkerDirective, "/subsystem:windows"); 1351 version(LDC) 1352 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1353 else 1354 pragma(linkerDirective, "/entry:mainCRTStartup"); 1355 } 1356 } 1357 1358 1359 /++ 1360 After selecting a type from [WindowTypes], you may further customize 1361 its behavior by setting one or more of these flags. 1362 1363 1364 The different window types have different meanings of `normal`. If the 1365 window type already is a good match for what you want to do, you should 1366 just use [WindowFlags.normal], the default, which will do the right thing 1367 for your users. 1368 1369 The window flags will not always be honored by the operating system 1370 and window managers; they are hints, not commands. 1371 +/ 1372 enum WindowFlags : int { 1373 normal = 0, /// 1374 skipTaskbar = 1, /// 1375 alwaysOnTop = 2, /// 1376 alwaysOnBottom = 4, /// 1377 cannotBeActivated = 8, /// 1378 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. 1379 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. 1380 /++ 1381 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1382 it is still a top-level window. This should NOT be set separately for most window types. 1383 1384 A transient window will not keep the application open if its main window closes. 1385 1386 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1387 1388 1389 From the ICCM: 1390 1391 $(BLOCKQUOTE 1392 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. 1393 1394 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1395 ) 1396 1397 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1398 1399 History: 1400 Added February 23, 2021 but not yet stabilized. 1401 +/ 1402 transient = 64, 1403 /++ 1404 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. 1405 1406 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1407 1408 History: 1409 Added April 1, 2022 1410 +/ 1411 managesChildWindowFocus = 128, 1412 1413 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1414 } 1415 1416 /++ 1417 When creating a window, you can pass a type to SimpleWindow's constructor, 1418 then further customize the window by changing `WindowFlags`. 1419 1420 1421 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1422 use. The others are there to build a foundation for a higher level GUI toolkit, 1423 but are themselves not as high level as you might think from their names. 1424 1425 This list is based on the EMWH spec for X11. 1426 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1427 +/ 1428 enum WindowTypes : int { 1429 /// An ordinary application window. 1430 normal, 1431 /// 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. 1432 undecorated, 1433 /// 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. 1434 eventOnly, 1435 /// A drop down menu, such as from a menu bar 1436 dropdownMenu, 1437 /// A popup menu, such as from a right click 1438 popupMenu, 1439 /// A popup bubble notification 1440 notification, 1441 /* 1442 menu, /// a tearable menu bar 1443 splashScreen, /// a loading splash screen for your application 1444 tooltip, /// A tiny window showing temporary help text or something. 1445 comboBoxDropdown, 1446 dialog, 1447 toolbar 1448 */ 1449 /// a child nested inside the parent. You must pass a parent window to the ctor 1450 nestedChild, 1451 } 1452 1453 1454 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1455 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1456 private __gshared char* sdpyWindowClassStr = null; 1457 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1458 1459 /** 1460 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1461 You may want to change context version if you want to use advanced shaders or 1462 other modern OpenGL techinques. This setting doesn't affect already created 1463 windows. You may use version 2.1 as your default, which should be supported 1464 by any box since 2006, so seems to be a reasonable choice. 1465 1466 Note that by default version is set to `0`, which forces SimpleDisplay to use 1467 old context creation code without any version specified. This is the safest 1468 way to init OpenGL, but it may not give you access to advanced features. 1469 1470 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1471 */ 1472 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1473 1474 /** 1475 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1476 pipeline functions, and without "compatible" mode you won't be able to use 1477 your old non-shader-based code with such contexts. By default SimpleDisplay 1478 creates compatible context, so you can gradually upgrade your OpenGL code if 1479 you want to (or leave it as is, as it should "just work"). 1480 */ 1481 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1482 1483 /** 1484 Set to `true` to allow creating OpenGL context with lower version than requested 1485 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1486 `openGLContextFallbackActivated()` will return `true`. 1487 */ 1488 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1489 1490 /** 1491 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1492 */ 1493 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1494 1495 1496 /** 1497 Set window class name for all following `new SimpleWindow()` calls. 1498 1499 WARNING! For Windows, you should set your class name before creating any 1500 window, and NEVER change it after that! 1501 */ 1502 void sdpyWindowClass (const(char)[] v) { 1503 import core.stdc.stdlib : realloc; 1504 if (v.length == 0) v = "SimpleDisplayWindow"; 1505 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1506 if (sdpyWindowClassStr is null) return; // oops 1507 sdpyWindowClassStr[0..v.length+1] = 0; 1508 sdpyWindowClassStr[0..v.length] = v[]; 1509 } 1510 1511 /** 1512 Get current window class name. 1513 */ 1514 string sdpyWindowClass () { 1515 if (sdpyWindowClassStr is null) return null; 1516 foreach (immutable idx; 0..size_t.max-1) { 1517 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1518 } 1519 return null; 1520 } 1521 1522 /++ 1523 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. 1524 1525 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1526 +/ 1527 float[2] getDpi() { 1528 float[2] dpi; 1529 version(Windows) { 1530 HDC screen = GetDC(null); 1531 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1532 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1533 } else version(X11) { 1534 auto display = XDisplayConnection.get; 1535 auto screen = DefaultScreen(display); 1536 1537 void fallback() { 1538 /+ 1539 // 25.4 millimeters in an inch... 1540 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1541 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1542 +/ 1543 1544 // the physical size isn't actually as important as the logical size since this is 1545 // all about scaling really 1546 dpi[0] = 96; 1547 dpi[1] = 96; 1548 } 1549 1550 auto xft = getXftDpi(); 1551 if(xft is float.init) 1552 fallback(); 1553 else { 1554 dpi[0] = xft; 1555 dpi[1] = xft; 1556 } 1557 } 1558 1559 return dpi; 1560 } 1561 1562 version(X11) 1563 float getXftDpi() { 1564 auto display = XDisplayConnection.get; 1565 1566 char* resourceString = XResourceManagerString(display); 1567 XrmInitialize(); 1568 1569 if (resourceString) { 1570 auto db = XrmGetStringDatabase(resourceString); 1571 XrmValue value; 1572 char* type; 1573 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1574 if (value.addr) { 1575 import core.stdc.stdlib; 1576 return atof(cast(char*) value.addr); 1577 } 1578 } 1579 } 1580 1581 return float.init; 1582 } 1583 1584 /++ 1585 Implementation used by [SimpleWindow.takeScreenshot]. 1586 1587 Params: 1588 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1589 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1590 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1591 x = the x-offset of the image to capture, from the left. 1592 y = the y-offset of the image to capture, from the top. 1593 1594 History: 1595 Added on March 14, 2021 1596 1597 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1598 1599 +/ 1600 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1601 TrueColorImage got; 1602 version(X11) { 1603 auto display = XDisplayConnection.get; 1604 if(handle == 0) 1605 handle = RootWindow(display, DefaultScreen(display)); 1606 1607 if(width == 0 || height == 0) { 1608 Window root; 1609 int xpos, ypos; 1610 uint widthret, heightret, borderret, depthret; 1611 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1612 1613 if(width == 0) 1614 width = widthret; 1615 if(height == 0) 1616 height = heightret; 1617 } 1618 1619 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1620 1621 // https://github.com/adamdruppe/arsd/issues/98 1622 1623 auto i = new Image(image); 1624 got = i.toTrueColorImage(); 1625 1626 XDestroyImage(image); 1627 } else version(Windows) { 1628 auto hdc = GetDC(handle); 1629 scope(exit) ReleaseDC(handle, hdc); 1630 1631 if(width == 0 || height == 0) { 1632 BITMAP bmHeader; 1633 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1634 GetObject(bm, BITMAP.sizeof, &bmHeader); 1635 if(width == 0) 1636 width = bmHeader.bmWidth; 1637 if(height == 0) 1638 height = bmHeader.bmHeight; 1639 } 1640 1641 auto i = new Image(width, height); 1642 HDC hdcMem = CreateCompatibleDC(hdc); 1643 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1644 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1645 SelectObject(hdcMem, hbmOld); 1646 DeleteDC(hdcMem); 1647 1648 got = i.toTrueColorImage(); 1649 } else featureNotImplemented(); 1650 1651 return got; 1652 } 1653 1654 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1655 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1656 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1657 1658 version(Windows) 1659 shared static this() { 1660 auto lib = LoadLibrary("User32.dll"); 1661 if(lib is null) 1662 return; 1663 //scope(exit) 1664 //FreeLibrary(lib); 1665 1666 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1667 1668 if(SetProcessDpiAwarenessContext is null) 1669 return; 1670 1671 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1672 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1673 //writeln(GetLastError()); 1674 } 1675 1676 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1677 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1678 } 1679 1680 /++ 1681 Blocking mode for event loop calls associated with a window instance. 1682 1683 History: 1684 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1685 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1686 is, all would block until the application quit. 1687 1688 That behavior can still be achieved here with `untilApplicationQuits`, 1689 or explicitly calling the top-level `EventLoop.get.run` function. 1690 +/ 1691 enum BlockingMode { 1692 /++ 1693 The event loop call will block until the whole application is ready 1694 to quit if it is the only one running, but if it is nested inside 1695 another one, it will only block until the window you're calling it on 1696 closes. 1697 +/ 1698 automatic = 0x00, 1699 /++ 1700 The event loop call will only return when the whole application 1701 is ready to quit. This usually means all windows have been closed. 1702 1703 This is appropriate for your main application event loop. 1704 +/ 1705 untilApplicationQuits = 0x01, 1706 /++ 1707 The event loop will return when the window you're calling it on 1708 closes. If there are other windows still open, they may be destroyed 1709 unless you have another event loop running later. 1710 1711 This might be appropriate for a modal dialog box loop. Remember that 1712 other windows are still processing input though, so you can end up 1713 with a lengthy call stack if this happens in a loop, similar to a 1714 recursive function (well, it literally is a recursive function, just 1715 not an obvious looking one). 1716 +/ 1717 untilWindowCloses = 0x02, 1718 /++ 1719 If an event loop is already running, this call will immediately 1720 return, allowing the existing loop to handle it. If not, this call 1721 will block until the condition you bitwise-or into the flag. 1722 1723 The default is to block until the application quits, same as with 1724 the `automatic` setting (since if it were nested, which triggers until 1725 window closes in automatic, this flag would instead not block at all), 1726 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1727 it will only nest until the window closes. You might want that if you are 1728 going to open two windows simultaneously and want closing just one of them 1729 to trigger the event loop return. 1730 +/ 1731 onlyIfNotNested = 0x10, 1732 } 1733 1734 /++ 1735 The flagship window class. 1736 1737 1738 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1739 out of more advanced or complex features of the underlying windowing system. 1740 1741 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1742 and get a suitable window to work with. 1743 1744 From there, you can opt into additional features, like custom resizability and OpenGL support 1745 with the next two constructor arguments. Or, if you need even more, you can set a window type 1746 and customization flags with the final two constructor arguments. 1747 1748 If none of that works for you, you can also create a window using native function calls, then 1749 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1750 though, if you do this, managing the window is still your own responsibility! Notably, you 1751 will need to destroy it yourself. 1752 +/ 1753 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1754 1755 /++ 1756 Copies the window's current state into a [TrueColorImage]. 1757 1758 Be warned: this can be a very slow operation 1759 1760 History: 1761 Actually implemented on March 14, 2021 1762 +/ 1763 TrueColorImage takeScreenshot() { 1764 version(Windows) 1765 return trueColorImageFromNativeHandle(impl.hwnd, width, height); 1766 else version(OSXCocoa) 1767 throw new NotYetImplementedException(); 1768 else 1769 return trueColorImageFromNativeHandle(impl.window, width, height); 1770 } 1771 1772 /++ 1773 Returns the actual logical DPI for the window on its current display monitor. If the window 1774 straddles monitors, it will return the value of one or the other in a platform-defined manner. 1775 1776 Please note this function may return zero if it doesn't know the answer! 1777 1778 1779 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 1780 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 1781 1782 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 1783 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 1784 window primarily resides on by checking the center point of the window against the monitor map. 1785 1786 Returns: 1787 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 1788 assumes the X and Y dpi are the same. 1789 1790 History: 1791 Added November 26, 2021 (dub v10.4) 1792 1793 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 1794 always a logical value on Windows and usually one on Linux too, so now the docs reflect 1795 that. 1796 1797 Bugs: 1798 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 1799 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 1800 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 1801 and 1.5 on the secondary monitor. 1802 1803 The local dpi is not necessarily related to the physical dpi of the monitor. The name 1804 is a historical misnomer - the real thing of interest is the scale factor and due to 1805 compatibility concerns the scale would modify dpi values to trick applications. But since 1806 that's the terminology common out there, I used it too. 1807 1808 See_Also: 1809 [getDpi] gives the value provided for the default monitor. Not necessarily the same 1810 as this since the window many be on a different monitor, but it is a reasonable fallback 1811 to use if `actualDpi` returns 0. 1812 1813 [onDpiChanged] is changed when `actualDpi` has changed. 1814 +/ 1815 int actualDpi() { 1816 if(!actualDpiLoadAttempted) { 1817 // FIXME: do the actual monitor we are on 1818 // and on X this is a good chance to load the monitor map. 1819 version(Windows) { 1820 if(GetDpiForWindow) 1821 actualDpi_ = GetDpiForWindow(impl.hwnd); 1822 } else version(X11) { 1823 if(!xRandrInfoLoadAttemped) { 1824 xRandrInfoLoadAttemped = true; 1825 if(!XRandrLibrary.attempted) { 1826 XRandrLibrary.loadDynamicLibrary(); 1827 } 1828 1829 if(XRandrLibrary.loadSuccessful) { 1830 auto display = XDisplayConnection.get; 1831 int scratch; 1832 int major, minor; 1833 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 1834 goto fallback; 1835 1836 XRRQueryVersion(display, &major, &minor); 1837 if(major <= 1 && minor < 5) 1838 goto fallback; 1839 1840 int count; 1841 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 1842 if(monitors is null) 1843 goto fallback; 1844 scope(exit) XRRFreeMonitors(monitors); 1845 1846 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 1847 MonitorInfo.info.assumeSafeAppend(); 1848 foreach(idx, monitor; monitors[0 .. count]) { 1849 MonitorInfo.info ~= MonitorInfo( 1850 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1851 Size(monitor.mwidth, monitor.mheight), 1852 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 1853 ); 1854 1855 /+ 1856 if(monitor.mwidth == 0 || monitor.mheight == 0) 1857 // unknown physical size, just guess 96 to avoid divide by zero 1858 MonitorInfo.info ~= MonitorInfo( 1859 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1860 Size(monitor.mwidth, monitor.mheight), 1861 96 1862 ); 1863 else 1864 // and actual thing 1865 MonitorInfo.info ~= MonitorInfo( 1866 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1867 Size(monitor.mwidth, monitor.mheight), 1868 minInternal( 1869 // millimeter to int then rounding up. 1870 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 1871 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 1872 ) 1873 ); 1874 +/ 1875 } 1876 // import std.stdio; writeln("Here", MonitorInfo.info); 1877 } 1878 } 1879 1880 if(XRandrLibrary.loadSuccessful) { 1881 updateActualDpi(true); 1882 //import std.stdio; writeln("updated"); 1883 1884 if(!requestedInput) { 1885 // this is what requests live updates should the configuration change 1886 // each time you select input, it sends an initial event, so very important 1887 // to not get into a loop of selecting input, getting event, updating data, 1888 // and reselecting input... 1889 requestedInput = true; 1890 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 1891 //import std.stdio; writeln("requested input"); 1892 } 1893 } else { 1894 fallback: 1895 // make sure we disable events that aren't coming 1896 xrrEventBase = -1; 1897 // best guess... respect the custom scaling user command to some extent at least though 1898 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 1899 } 1900 } 1901 actualDpiLoadAttempted = true; 1902 } 1903 return actualDpi_; 1904 } 1905 1906 private int actualDpi_; 1907 private bool actualDpiLoadAttempted; 1908 1909 version(X11) private { 1910 bool requestedInput; 1911 static bool xRandrInfoLoadAttemped; 1912 struct MonitorInfo { 1913 Rectangle position; 1914 Size size; 1915 int dpi; 1916 1917 static MonitorInfo[] info; 1918 } 1919 bool screenPositionKnown; 1920 int screenPositionX; 1921 int screenPositionY; 1922 void updateActualDpi(bool loadingNow = false) { 1923 if(!loadingNow && !actualDpiLoadAttempted) 1924 actualDpi(); // just to make it do the load 1925 foreach(idx, m; MonitorInfo.info) { 1926 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 1927 bool changed = actualDpi_ && actualDpi_ != m.dpi; 1928 actualDpi_ = m.dpi; 1929 //import std.stdio; writeln("monitor ", idx); 1930 if(changed && onDpiChanged) 1931 onDpiChanged(); 1932 break; 1933 } 1934 } 1935 } 1936 } 1937 1938 /++ 1939 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 1940 or if the window is moved to a new remote connection or a monitor is hot-swapped. 1941 1942 History: 1943 Added November 26, 2021 (dub v10.4) 1944 1945 See_Also: 1946 [actualDpi] 1947 +/ 1948 void delegate() onDpiChanged; 1949 1950 version(X11) { 1951 void recreateAfterDisconnect() { 1952 if(!stateDiscarded) return; 1953 1954 if(_parent !is null && _parent.stateDiscarded) 1955 _parent.recreateAfterDisconnect(); 1956 1957 bool wasHidden = hidden; 1958 1959 activeScreenPainter = null; // should already be done but just to confirm 1960 1961 actualDpi_ = 0; 1962 actualDpiLoadAttempted = false; 1963 xRandrInfoLoadAttemped = false; 1964 1965 impl.createWindow(_width, _height, _title, openglMode, _parent); 1966 1967 if(auto dh = dropHandler) { 1968 dropHandler = null; 1969 enableDragAndDrop(this, dh); 1970 } 1971 1972 if(recreateAdditionalConnectionState) 1973 recreateAdditionalConnectionState(); 1974 1975 hidden = wasHidden; 1976 stateDiscarded = false; 1977 } 1978 1979 bool stateDiscarded; 1980 void discardConnectionState() { 1981 if(XDisplayConnection.display) 1982 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 1983 if(discardAdditionalConnectionState) 1984 discardAdditionalConnectionState(); 1985 stateDiscarded = true; 1986 } 1987 1988 void delegate() discardAdditionalConnectionState; 1989 void delegate() recreateAdditionalConnectionState; 1990 1991 } 1992 1993 private DropHandler dropHandler; 1994 1995 SimpleWindow _parent; 1996 bool beingOpenKeepsAppOpen = true; 1997 /++ 1998 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. 1999 2000 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2001 2002 Params: 2003 2004 width = the width of the window's client area, in pixels 2005 height = the height of the window's client area, in pixels 2006 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2007 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2008 resizable = [Resizability] has three options: 2009 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2010 $(P `fixedSize` will not allow the user to resize the window.) 2011 $(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.) 2012 windowType = The type of window you want to make. 2013 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. 2014 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". 2015 +/ 2016 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) { 2017 claimGuiThread(); 2018 version(sdpy_thread_checks) assert(thisIsGuiThread); 2019 this._width = width; 2020 this._height = height; 2021 this.openglMode = opengl; 2022 this.resizability = resizable; 2023 this.windowType = windowType; 2024 this.customizationFlags = customizationFlags; 2025 this._title = (title is null ? "D Application" : title); 2026 this._parent = parent; 2027 impl.createWindow(width, height, this._title, opengl, parent); 2028 2029 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2030 beingOpenKeepsAppOpen = false; 2031 } 2032 2033 /// ditto 2034 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2035 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2036 } 2037 2038 /// Same as above, except using the `Size` struct instead of separate width and height. 2039 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2040 this(size.width, size.height, title, opengl, resizable); 2041 } 2042 2043 /// ditto 2044 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2045 this(size, title, opengl, resizable); 2046 } 2047 2048 2049 /++ 2050 Creates a window based on the given [Image]. It's client area 2051 width and height is equal to the image. (A window's client area 2052 is the drawable space inside; it excludes the title bar, etc.) 2053 2054 Windows based on images will not be resizable and do not use OpenGL. 2055 2056 It will draw the image in upon creation, but this will be overwritten 2057 upon any draws, including the initial window visible event. 2058 2059 You probably do not want to use this and it may be removed from 2060 the library eventually, or I might change it to be a "permanent" 2061 background image; one that is automatically drawn on it before any 2062 other drawing event. idk. 2063 +/ 2064 this(Image image, string title = null) { 2065 this(image.width, image.height, title); 2066 this.image = image; 2067 } 2068 2069 /++ 2070 Wraps a native window handle with very little additional processing - notably no destruction 2071 this is incomplete so don't use it for much right now. The purpose of this is to make native 2072 windows created through the low level API (so you can use platform-specific options and 2073 other details SimpleWindow does not expose) available to the event loop wrappers. 2074 +/ 2075 this(NativeWindowHandle nativeWindow) { 2076 version(Windows) 2077 impl.hwnd = nativeWindow; 2078 else version(X11) { 2079 impl.window = nativeWindow; 2080 if(nativeWindow) 2081 display = XDisplayConnection.get(); // get initial display to not segfault 2082 } else version(OSXCocoa) 2083 throw new NotYetImplementedException(); 2084 else featureNotImplemented(); 2085 // FIXME: set the size correctly 2086 _width = 1; 2087 _height = 1; 2088 if(nativeWindow) 2089 nativeMapping[nativeWindow] = this; 2090 2091 beingOpenKeepsAppOpen = false; 2092 2093 if(nativeWindow) 2094 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2095 _suppressDestruction = true; // so it doesn't try to close 2096 } 2097 2098 /++ 2099 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2100 The delegate will be called when the window manager asks you to take focus. 2101 2102 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2103 2104 History: 2105 Added April 1, 2022 (dub v10.8) 2106 +/ 2107 SimpleWindow delegate() setRequestedInputFocus; 2108 2109 /// Experimental, do not use yet 2110 /++ 2111 Grabs exclusive input from the user until you release it with 2112 [releaseInputGrab]. 2113 2114 2115 Note: it is extremely rude to do this without good reason. 2116 Reasons may include doing some kind of mouse drag operation 2117 or popping up a temporary menu that should get events and will 2118 be dismissed at ease by the user clicking away. 2119 2120 Params: 2121 keyboard = do you want to grab keyboard input? 2122 mouse = grab mouse input? 2123 confine = confine the mouse cursor to inside this window? 2124 2125 History: 2126 Prior to March 11, 2021, grabbing the keyboard would always also 2127 set the X input focus. Now, it only focuses if it is a non-transient 2128 window and otherwise manages the input direction internally. 2129 2130 This means spurious focus/blur events will no longer be sent and the 2131 application will not steal focus from other applications (which the 2132 window manager may have rejected anyway). 2133 +/ 2134 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2135 static if(UsingSimpledisplayX11) { 2136 XSync(XDisplayConnection.get, 0); 2137 if(keyboard) { 2138 if(isTransient && _parent) { 2139 /* 2140 FIXME: 2141 setting the keyboard focus is not actually that helpful, what I more likely want 2142 is the events from the parent window to be sent over here if we're transient. 2143 */ 2144 2145 _parent.inputProxy = this; 2146 } else { 2147 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 2148 } 2149 } 2150 if(mouse) { 2151 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2152 EventMask.PointerMotionMask // FIXME: not efficient 2153 | EventMask.ButtonPressMask 2154 | EventMask.ButtonReleaseMask 2155 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2156 ) 2157 { 2158 XSync(XDisplayConnection.get, 0); 2159 import core.stdc.stdio; 2160 printf("Grab input failed %d\n", res); 2161 //throw new Exception("Grab input failed"); 2162 } else { 2163 // cool 2164 } 2165 } 2166 2167 } else version(Windows) { 2168 // FIXME: keyboard? 2169 SetCapture(impl.hwnd); 2170 if(confine) { 2171 RECT rcClip; 2172 //RECT rcOldClip; 2173 //GetClipCursor(&rcOldClip); 2174 GetWindowRect(hwnd, &rcClip); 2175 ClipCursor(&rcClip); 2176 } 2177 } else version(OSXCocoa) { 2178 throw new NotYetImplementedException(); 2179 } else static assert(0); 2180 } 2181 2182 /++ 2183 Returns the native window. 2184 2185 History: 2186 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2187 to access it through the `impl` member (which is semi-supported 2188 but platform specific and here it is simple enough to offer an accessor). 2189 2190 Bugs: 2191 Not implemented outside Windows or X11. 2192 +/ 2193 NativeWindowHandle nativeWindowHandle() { 2194 version(X11) 2195 return impl.window; 2196 else version(Windows) 2197 return impl.hwnd; 2198 else 2199 throw new NotYetImplementedException(); 2200 } 2201 2202 private bool isTransient() { 2203 with(WindowTypes) 2204 final switch(windowType) { 2205 case normal, undecorated, eventOnly: 2206 case nestedChild: 2207 return (customizationFlags & WindowFlags.transient) ? true : false; 2208 case dropdownMenu, popupMenu, notification: 2209 return true; 2210 } 2211 } 2212 2213 private SimpleWindow inputProxy; 2214 2215 /++ 2216 Releases the grab acquired by [grabInput]. 2217 +/ 2218 void releaseInputGrab() { 2219 static if(UsingSimpledisplayX11) { 2220 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2221 if(_parent) 2222 _parent.inputProxy = null; 2223 } else version(Windows) { 2224 ReleaseCapture(); 2225 ClipCursor(null); 2226 } else version(OSXCocoa) { 2227 throw new NotYetImplementedException(); 2228 } else static assert(0); 2229 } 2230 2231 /++ 2232 Sets the input focus to this window. 2233 2234 You shouldn't call this very often - please let the user control the input focus. 2235 +/ 2236 void focus() { 2237 static if(UsingSimpledisplayX11) { 2238 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 2239 } else version(Windows) { 2240 SetFocus(this.impl.hwnd); 2241 } else version(OSXCocoa) { 2242 throw new NotYetImplementedException(); 2243 } else static assert(0); 2244 } 2245 2246 /++ 2247 Requests attention from the user for this window. 2248 2249 2250 The typical result of this function is to change the color 2251 of the taskbar icon, though it may be tweaked on specific 2252 platforms. 2253 2254 It is meant to unobtrusively tell the user that something 2255 relevant to them happened in the background and they should 2256 check the window when they get a chance. Upon receiving the 2257 keyboard focus, the window will automatically return to its 2258 natural state. 2259 2260 If the window already has the keyboard focus, this function 2261 may do nothing, because the user is presumed to already be 2262 giving the window attention. 2263 2264 Implementation_note: 2265 2266 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2267 atom on X11 and the FlashWindow function on Windows. 2268 +/ 2269 void requestAttention() { 2270 if(_focused) 2271 return; 2272 2273 version(Windows) { 2274 FLASHWINFO info; 2275 info.cbSize = info.sizeof; 2276 info.hwnd = impl.hwnd; 2277 info.dwFlags = FLASHW_TRAY; 2278 info.uCount = 1; 2279 2280 FlashWindowEx(&info); 2281 2282 } else version(X11) { 2283 demandingAttention = true; 2284 demandAttention(this, true); 2285 } else version(OSXCocoa) { 2286 throw new NotYetImplementedException(); 2287 } else static assert(0); 2288 } 2289 2290 private bool _focused; 2291 2292 version(X11) private bool demandingAttention; 2293 2294 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2295 /// You'll have to call `close()` manually if you set this delegate. 2296 void delegate () closeQuery; 2297 2298 /// This will be called when window visibility was changed. 2299 void delegate (bool becomesVisible) visibilityChanged; 2300 2301 /// This will be called when window becomes visible for the first time. 2302 /// You can do OpenGL initialization here. Note that in X11 you can't call 2303 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2304 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2305 private bool _visibleForTheFirstTimeCalled; 2306 void delegate () visibleForTheFirstTime; 2307 2308 /// Returns true if the window has been closed. 2309 final @property bool closed() { return _closed; } 2310 2311 private final @property bool notClosed() { return !_closed; } 2312 2313 /// Returns true if the window is focused. 2314 final @property bool focused() { return _focused; } 2315 2316 private bool _visible; 2317 /// Returns true if the window is visible (mapped). 2318 final @property bool visible() { return _visible; } 2319 2320 /// Closes the window. If there are no more open windows, the event loop will terminate. 2321 void close() { 2322 if (!_closed) { 2323 runInGuiThread( { 2324 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2325 if (onClosing !is null) onClosing(); 2326 impl.closeWindow(); 2327 _closed = true; 2328 } ); 2329 } 2330 } 2331 2332 /++ 2333 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2334 2335 History: 2336 Overload added on March 7, 2021. 2337 +/ 2338 void close() shared { 2339 (cast() this).close(); 2340 } 2341 2342 /++ 2343 2344 +/ 2345 void maximize() { 2346 version(Windows) 2347 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2348 else version(X11) { 2349 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2350 2351 // also note _NET_WM_STATE_FULLSCREEN 2352 } 2353 2354 } 2355 2356 private bool _fullscreen; 2357 2358 /// not fully implemented but planned for a future release 2359 void fullscreen(bool yes) { 2360 version(X11) 2361 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2362 2363 _fullscreen = yes; 2364 2365 } 2366 2367 bool fullscreen() { 2368 return _fullscreen; 2369 } 2370 2371 /++ 2372 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2373 2374 +/ 2375 void minimize() { 2376 version(Windows) 2377 ShowWindow(impl.hwnd, SW_MINIMIZE); 2378 //else version(X11) 2379 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2380 } 2381 2382 /// Alias for `hidden = false` 2383 void show() { 2384 hidden = false; 2385 } 2386 2387 /// Alias for `hidden = true` 2388 void hide() { 2389 hidden = true; 2390 } 2391 2392 /// Hide cursor when it enters the window. 2393 void hideCursor() { 2394 version(OSXCocoa) throw new NotYetImplementedException(); else 2395 if (!_closed) impl.hideCursor(); 2396 } 2397 2398 /// Don't hide cursor when it enters the window. 2399 void showCursor() { 2400 version(OSXCocoa) throw new NotYetImplementedException(); else 2401 if (!_closed) impl.showCursor(); 2402 } 2403 2404 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2405 * 2406 * Please remember that the cursor is a shared resource that should usually be left to the user's 2407 * control. Try to think for other approaches before using this function. 2408 * 2409 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2410 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2411 * receive "mouse moved here" event. 2412 */ 2413 bool warpMouse (int x, int y) { 2414 version(X11) { 2415 if (!_closed) { impl.warpMouse(x, y); return true; } 2416 } else version(Windows) { 2417 if (!_closed) { 2418 POINT point; 2419 point.x = x; 2420 point.y = y; 2421 if(ClientToScreen(impl.hwnd, &point)) { 2422 SetCursorPos(point.x, point.y); 2423 return true; 2424 } 2425 } 2426 } 2427 return false; 2428 } 2429 2430 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2431 void sendDummyEvent () { 2432 version(X11) { 2433 if (!_closed) { impl.sendDummyEvent(); } 2434 } 2435 } 2436 2437 /// Set window minimal size. 2438 void setMinSize (int minwidth, int minheight) { 2439 version(OSXCocoa) throw new NotYetImplementedException(); else 2440 if (!_closed) impl.setMinSize(minwidth, minheight); 2441 } 2442 2443 /// Set window maximal size. 2444 void setMaxSize (int maxwidth, int maxheight) { 2445 version(OSXCocoa) throw new NotYetImplementedException(); else 2446 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2447 } 2448 2449 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2450 /// Currently only supported on X11. 2451 void setResizeGranularity (int granx, int grany) { 2452 version(OSXCocoa) throw new NotYetImplementedException(); else 2453 if (!_closed) impl.setResizeGranularity(granx, grany); 2454 } 2455 2456 /// Move window. 2457 void move(int x, int y) { 2458 version(OSXCocoa) throw new NotYetImplementedException(); else 2459 if (!_closed) impl.move(x, y); 2460 } 2461 2462 /// ditto 2463 void move(Point p) { 2464 version(OSXCocoa) throw new NotYetImplementedException(); else 2465 if (!_closed) impl.move(p.x, p.y); 2466 } 2467 2468 /++ 2469 Resize window. 2470 2471 Note that the width and height of the window are NOT instantly 2472 updated - it waits for the window manager to approve the resize 2473 request, which means you must return to the event loop before the 2474 width and height are actually changed. 2475 +/ 2476 void resize(int w, int h) { 2477 if(!_closed && _fullscreen) fullscreen = false; 2478 version(OSXCocoa) throw new NotYetImplementedException(); else 2479 if (!_closed) impl.resize(w, h); 2480 } 2481 2482 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2483 void moveResize (int x, int y, int w, int h) { 2484 if(!_closed && _fullscreen) fullscreen = false; 2485 version(OSXCocoa) throw new NotYetImplementedException(); else 2486 if (!_closed) impl.moveResize(x, y, w, h); 2487 } 2488 2489 private bool _hidden; 2490 2491 /// Returns true if the window is hidden. 2492 final @property bool hidden() { 2493 return _hidden; 2494 } 2495 2496 /// Shows or hides the window based on the bool argument. 2497 final @property void hidden(bool b) { 2498 _hidden = b; 2499 version(Windows) { 2500 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2501 } else version(X11) { 2502 if(b) 2503 //XUnmapWindow(impl.display, impl.window); 2504 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2505 else 2506 XMapWindow(impl.display, impl.window); 2507 } else version(OSXCocoa) { 2508 throw new NotYetImplementedException(); 2509 } else static assert(0); 2510 } 2511 2512 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2513 void opacity(double opacity) @property 2514 in { 2515 assert(opacity >= 0 && opacity <= 1); 2516 } do { 2517 version (Windows) { 2518 impl.setOpacity(cast(ubyte)(255 * opacity)); 2519 } else version (X11) { 2520 impl.setOpacity(cast(uint)(uint.max * opacity)); 2521 } else throw new NotYetImplementedException(); 2522 } 2523 2524 /++ 2525 Sets your event handlers, without entering the event loop. Useful if you 2526 have multiple windows - set the handlers on each window, then only do 2527 [eventLoop] on your main window or call `EventLoop.get.run();`. 2528 2529 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2530 [handlePulse], and [handleMouseEvent] automatically based on the provide 2531 delegate signatures. 2532 +/ 2533 void setEventHandlers(T...)(T eventHandlers) { 2534 // FIXME: add more events 2535 foreach(handler; eventHandlers) { 2536 static if(__traits(compiles, handleKeyEvent = handler)) { 2537 handleKeyEvent = handler; 2538 } else static if(__traits(compiles, handleCharEvent = handler)) { 2539 handleCharEvent = handler; 2540 } else static if(__traits(compiles, handlePulse = handler)) { 2541 handlePulse = handler; 2542 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2543 handleMouseEvent = handler; 2544 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2545 } 2546 } 2547 2548 /++ 2549 The event loop automatically returns when the window is closed 2550 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2551 pulse timer is created. The event loop will block until an event 2552 arrives or the pulse timer goes off. 2553 2554 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2555 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2556 [handleMouseEvent], based on the signature of delegates you provide. 2557 2558 Give one with no parameters to set a timer pulse handler. Give one that 2559 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2560 and one that takes `dchar` for a char event handler. You can use as many 2561 or as few handlers as you need for your application. 2562 2563 History: 2564 The overload without `pulseTimeout` was added on December 8, 2021. 2565 2566 On December 9, 2021, the default blocking mode (which is now configurable 2567 because [eventLoopWithBlockingMode] was added) switched from 2568 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2569 should almost never be noticeable to you since the typical simpledisplay 2570 paradigm has been (and I still recommend) to have one `eventLoop` call. 2571 2572 See_Also: 2573 [eventLoopWithBlockingMode] 2574 +/ 2575 final int eventLoop(T...)( 2576 long pulseTimeout, /// set to zero if you don't want a pulse. 2577 T eventHandlers) /// delegate list like std.concurrency.receive 2578 { 2579 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2580 } 2581 2582 /// ditto 2583 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2584 { 2585 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 2586 } 2587 2588 /++ 2589 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 2590 2591 History: 2592 Added December 8, 2021 (dub v10.5) 2593 2594 Previously, this implementation was right inside [eventLoop], but when I wanted 2595 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 2596 just renamed it instead of adding as an overload. Besides, the new name makes it 2597 easier to remember the order and avoids ambiguity between two int-like params anyway. 2598 2599 See_Also: 2600 [SimpleWindow.eventLoop], [EventLoop] 2601 2602 Bugs: 2603 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 2604 +/ 2605 final int eventLoopWithBlockingMode(T...)( 2606 BlockingMode blockingMode, /// when you want this function to block until 2607 long pulseTimeout, /// set to zero if you don't want a pulse. 2608 T eventHandlers) /// delegate list like std.concurrency.receive 2609 { 2610 setEventHandlers(eventHandlers); 2611 2612 version(with_eventloop) { 2613 // delegates event loop to my other module 2614 version(X11) 2615 XFlush(display); 2616 2617 import arsd.eventloop; 2618 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2619 scope(exit) clearInterval(handle); 2620 2621 loop(); 2622 return 0; 2623 } else version(OSXCocoa) { 2624 // FIXME 2625 if (handlePulse !is null && pulseTimeout != 0) { 2626 timer = scheduledTimer(pulseTimeout*1e-3, 2627 view, sel_registerName("simpledisplay_pulse"), 2628 null, true); 2629 } 2630 2631 setNeedsDisplay(view, true); 2632 run(NSApp); 2633 return 0; 2634 } else { 2635 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2636 2637 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 2638 return 0; 2639 2640 return el.run( 2641 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 2642 null : 2643 &this.notClosed 2644 ); 2645 } 2646 } 2647 2648 /++ 2649 This lets you draw on the window (or its backing buffer) using basic 2650 2D primitives. 2651 2652 Be sure to call this in a limited scope because your changes will not 2653 actually appear on the window until ScreenPainter's destructor runs. 2654 2655 Returns: an instance of [ScreenPainter], which has the drawing methods 2656 on it to draw on this window. 2657 2658 Params: 2659 manualInvalidations = if you set this to true, you will need to 2660 set the invalid rectangle on the painter yourself. If false, it 2661 assumes the whole window has been redrawn each time you draw. 2662 2663 Only invalidated rectangles are blitted back to the window when 2664 the destructor runs. Doing this yourself can reduce flickering 2665 of child windows. 2666 2667 History: 2668 The `manualInvalidations` parameter overload was added on 2669 December 30, 2021 (dub v10.5) 2670 +/ 2671 ScreenPainter draw() { 2672 return draw(false); 2673 } 2674 /// ditto 2675 ScreenPainter draw(bool manualInvalidations) { 2676 return impl.getPainter(manualInvalidations); 2677 } 2678 2679 // This is here to implement the interface we use for various native handlers. 2680 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2681 2682 // maps native window handles to SimpleWindow instances, if there are any 2683 // you shouldn't need this, but it is public in case you do in a native event handler or something 2684 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2685 2686 /// Width of the window's drawable client area, in pixels. 2687 @scriptable 2688 final @property int width() const pure nothrow @safe @nogc { return _width; } 2689 2690 /// Height of the window's drawable client area, in pixels. 2691 @scriptable 2692 final @property int height() const pure nothrow @safe @nogc { return _height; } 2693 2694 private int _width; 2695 private int _height; 2696 2697 // HACK: making the best of some copy constructor woes with refcounting 2698 private ScreenPainterImplementation* activeScreenPainter_; 2699 2700 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2701 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2702 2703 private OpenGlOptions openglMode; 2704 private Resizability resizability; 2705 private WindowTypes windowType; 2706 private int customizationFlags; 2707 2708 /// `true` if OpenGL was initialized for this window. 2709 @property bool isOpenGL () const pure nothrow @safe @nogc { 2710 version(without_opengl) 2711 return false; 2712 else 2713 return (openglMode == OpenGlOptions.yes); 2714 } 2715 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2716 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2717 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2718 2719 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2720 /// to call this, as it's not recommended to share window between threads. 2721 void mtLock () { 2722 version(X11) { 2723 XLockDisplay(this.display); 2724 } 2725 } 2726 2727 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2728 /// to call this, as it's not recommended to share window between threads. 2729 void mtUnlock () { 2730 version(X11) { 2731 XUnlockDisplay(this.display); 2732 } 2733 } 2734 2735 /// Emit a beep to get user's attention. 2736 void beep () { 2737 version(X11) { 2738 XBell(this.display, 100); 2739 } else version(Windows) { 2740 MessageBeep(0xFFFFFFFF); 2741 } 2742 } 2743 2744 2745 2746 version(without_opengl) {} else { 2747 2748 /// 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`. 2749 void delegate() redrawOpenGlScene; 2750 2751 /// This will allow you to change OpenGL vsync state. 2752 final @property void vsync (bool wait) { 2753 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2754 version(X11) { 2755 setAsCurrentOpenGlContext(); 2756 glxSetVSync(display, impl.window, wait); 2757 } else version(Windows) { 2758 setAsCurrentOpenGlContext(); 2759 wglSetVSync(wait); 2760 } 2761 } 2762 2763 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 2764 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 2765 /// enough without waiting 'em to finish their frame bussiness. 2766 bool useGLFinish = true; 2767 2768 // FIXME: it should schedule it for the end of the current iteration of the event loop... 2769 /// 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. 2770 void redrawOpenGlSceneNow() { 2771 version(X11) if (!this._visible) return; // no need to do this if window is invisible 2772 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2773 if(redrawOpenGlScene is null) 2774 return; 2775 2776 this.mtLock(); 2777 scope(exit) this.mtUnlock(); 2778 2779 this.setAsCurrentOpenGlContext(); 2780 2781 redrawOpenGlScene(); 2782 2783 this.swapOpenGlBuffers(); 2784 // 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. 2785 if (useGLFinish) glFinish(); 2786 } 2787 2788 private bool redrawOpenGlSceneSoonSet = false; 2789 private static class RedrawOpenGlSceneEvent { 2790 SimpleWindow w; 2791 this(SimpleWindow w) { this.w = w; } 2792 } 2793 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 2794 /++ 2795 Queues an opengl redraw as soon as the other pending events are cleared. 2796 +/ 2797 void redrawOpenGlSceneSoon() { 2798 if(!redrawOpenGlSceneSoonSet) { 2799 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 2800 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 2801 redrawOpenGlSceneSoonSet = true; 2802 } 2803 this.postEvent(redrawOpenGlSceneEvent, true); 2804 } 2805 2806 2807 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2808 void setAsCurrentOpenGlContext() { 2809 assert(openglMode == OpenGlOptions.yes); 2810 version(X11) { 2811 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 2812 throw new Exception("glXMakeCurrent"); 2813 } else version(Windows) { 2814 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2815 if (!wglMakeCurrent(ghDC, ghRC)) 2816 throw new Exception("wglMakeCurrent"); // let windows users suffer too 2817 } 2818 } 2819 2820 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2821 /// This doesn't throw, returning success flag instead. 2822 bool setAsCurrentOpenGlContextNT() nothrow { 2823 assert(openglMode == OpenGlOptions.yes); 2824 version(X11) { 2825 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 2826 } else version(Windows) { 2827 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2828 return wglMakeCurrent(ghDC, ghRC) ? true : false; 2829 } 2830 } 2831 2832 /// 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. 2833 /// This doesn't throw, returning success flag instead. 2834 bool releaseCurrentOpenGlContext() nothrow { 2835 assert(openglMode == OpenGlOptions.yes); 2836 version(X11) { 2837 return (glXMakeCurrent(display, 0, null) != 0); 2838 } else version(Windows) { 2839 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2840 return wglMakeCurrent(ghDC, null) ? true : false; 2841 } 2842 } 2843 2844 /++ 2845 simpledisplay always uses double buffering, usually automatically. This 2846 manually swaps the OpenGL buffers. 2847 2848 2849 You should not need to call this yourself because simpledisplay will do it 2850 for you after calling your `redrawOpenGlScene`. 2851 2852 Remember that this may throw an exception, which you can catch in a multithreaded 2853 application to keep your thread from dying from an unhandled exception. 2854 +/ 2855 void swapOpenGlBuffers() { 2856 assert(openglMode == OpenGlOptions.yes); 2857 version(X11) { 2858 if (!this._visible) return; // no need to do this if window is invisible 2859 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2860 glXSwapBuffers(display, impl.window); 2861 } else version(Windows) { 2862 SwapBuffers(ghDC); 2863 } 2864 } 2865 } 2866 2867 /++ 2868 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 2869 2870 2871 --- 2872 auto window = new SimpleWindow(100, 100, "First title"); 2873 window.title = "A new title"; 2874 --- 2875 2876 You may call this function at any time. 2877 +/ 2878 @property void title(string title) { 2879 _title = title; 2880 version(OSXCocoa) throw new NotYetImplementedException(); else 2881 impl.setTitle(title); 2882 } 2883 2884 private string _title; 2885 2886 /// Gets the title 2887 @property string title() { 2888 if(_title is null) 2889 _title = getRealTitle(); 2890 return _title; 2891 } 2892 2893 /++ 2894 Get the title as set by the window manager. 2895 May not match what you attempted to set. 2896 +/ 2897 string getRealTitle() { 2898 static if(is(typeof(impl.getTitle()))) 2899 return impl.getTitle(); 2900 else 2901 return null; 2902 } 2903 2904 // don't use this generally it is not yet really released 2905 version(X11) 2906 @property Image secret_icon() { 2907 return secret_icon_inner; 2908 } 2909 private Image secret_icon_inner; 2910 2911 2912 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 2913 @property void icon(MemoryImage icon) { 2914 if(icon is null) 2915 return; 2916 auto tci = icon.getAsTrueColorImage(); 2917 version(Windows) { 2918 winIcon = new WindowsIcon(icon); 2919 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 2920 } else version(X11) { 2921 secret_icon_inner = Image.fromMemoryImage(icon); 2922 // FIXME: ensure this is correct 2923 auto display = XDisplayConnection.get; 2924 arch_ulong[] buffer; 2925 buffer ~= icon.width; 2926 buffer ~= icon.height; 2927 foreach(c; tci.imageData.colors) { 2928 arch_ulong b; 2929 b |= c.a << 24; 2930 b |= c.r << 16; 2931 b |= c.g << 8; 2932 b |= c.b; 2933 buffer ~= b; 2934 } 2935 2936 XChangeProperty( 2937 display, 2938 impl.window, 2939 GetAtom!("_NET_WM_ICON", true)(display), 2940 GetAtom!"CARDINAL"(display), 2941 32 /* bits */, 2942 0 /*PropModeReplace*/, 2943 buffer.ptr, 2944 cast(int) buffer.length); 2945 } else version(OSXCocoa) { 2946 throw new NotYetImplementedException(); 2947 } else static assert(0); 2948 } 2949 2950 version(Windows) 2951 private WindowsIcon winIcon; 2952 2953 bool _suppressDestruction; 2954 2955 ~this() { 2956 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 2957 if(_suppressDestruction) 2958 return; 2959 impl.dispose(); 2960 } 2961 2962 private bool _closed; 2963 2964 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 2965 /* 2966 ScreenPainter drawTransiently() { 2967 return impl.getPainter(); 2968 } 2969 */ 2970 2971 /// Draws an image on the window. This is meant to provide quick look 2972 /// of a static image generated elsewhere. 2973 @property void image(Image i) { 2974 /+ 2975 version(Windows) { 2976 BITMAP bm; 2977 HDC hdc = GetDC(hwnd); 2978 HDC hdcMem = CreateCompatibleDC(hdc); 2979 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 2980 2981 GetObject(i.handle, bm.sizeof, &bm); 2982 2983 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 2984 2985 SelectObject(hdcMem, hbmOld); 2986 DeleteDC(hdcMem); 2987 ReleaseDC(hwnd, hdc); 2988 2989 /* 2990 RECT r; 2991 r.right = i.width; 2992 r.bottom = i.height; 2993 InvalidateRect(hwnd, &r, false); 2994 */ 2995 } else 2996 version(X11) { 2997 if(!destroyed) { 2998 if(i.usingXshm) 2999 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3000 else 3001 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3002 } 3003 } else 3004 version(OSXCocoa) { 3005 draw().drawImage(Point(0, 0), i); 3006 setNeedsDisplay(view, true); 3007 } else static assert(0); 3008 +/ 3009 auto painter = this.draw; 3010 painter.drawImage(Point(0, 0), i); 3011 } 3012 3013 /++ 3014 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3015 3016 --- 3017 window.cursor = GenericCursor.Help; 3018 // now the window mouse cursor is set to a generic help 3019 --- 3020 3021 +/ 3022 @property void cursor(MouseCursor cursor) { 3023 version(OSXCocoa) 3024 featureNotImplemented(); 3025 else 3026 if(this.impl.curHidden <= 0) { 3027 static if(UsingSimpledisplayX11) { 3028 auto ch = cursor.cursorHandle; 3029 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3030 } else version(Windows) { 3031 auto ch = cursor.cursorHandle; 3032 impl.currentCursor = ch; 3033 SetCursor(ch); // redraw without waiting for mouse movement to update 3034 } else featureNotImplemented(); 3035 } 3036 3037 } 3038 3039 /// What follows are the event handlers. These are set automatically 3040 /// by the eventLoop function, but are still public so you can change 3041 /// them later. wasPressed == true means key down. false == key up. 3042 3043 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3044 void delegate(KeyEvent ke) handleKeyEvent; 3045 3046 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3047 void delegate(dchar c) handleCharEvent; 3048 3049 /// Handles a timer pulse. Settable through setEventHandlers. 3050 void delegate() handlePulse; 3051 3052 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3053 void delegate(bool) onFocusChange; 3054 3055 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3056 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3057 void delegate() onClosing; 3058 3059 /** Called when we received destroy notification. At this stage we cannot do much with our window 3060 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3061 * last minute cleanup. */ 3062 void delegate() onDestroyed; 3063 3064 static if (UsingSimpledisplayX11) 3065 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3066 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3067 * You will probably never need to setup this handler, it is for very low-level stuff. 3068 * 3069 * WARNING! Xlib is multithread-locked when this handles is called! */ 3070 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3071 3072 //version(Windows) 3073 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3074 3075 private { 3076 int lastMouseX = int.min; 3077 int lastMouseY = int.min; 3078 void mdx(ref MouseEvent ev) { 3079 if(lastMouseX == int.min || lastMouseY == int.min) { 3080 ev.dx = 0; 3081 ev.dy = 0; 3082 } else { 3083 ev.dx = ev.x - lastMouseX; 3084 ev.dy = ev.y - lastMouseY; 3085 } 3086 3087 lastMouseX = ev.x; 3088 lastMouseY = ev.y; 3089 } 3090 } 3091 3092 /// Mouse event handler. Settable through setEventHandlers. 3093 void delegate(MouseEvent) handleMouseEvent; 3094 3095 /// use to redraw child widgets if you use system apis to add stuff 3096 void delegate() paintingFinished; 3097 3098 void delegate() paintingFinishedDg() { 3099 return paintingFinished; 3100 } 3101 3102 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3103 /// for this to ever happen. 3104 void delegate(int width, int height) windowResized; 3105 3106 /++ 3107 Platform specific - handle any native message this window gets. 3108 3109 Note: this is called *in addition to* other event handlers, unless you either: 3110 3111 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3112 3113 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. 3114 3115 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3116 3117 On X, it takes the form of `int delegate(XEvent)`. 3118 3119 History: 3120 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3121 3122 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. 3123 +/ 3124 NativeEventHandler handleNativeEvent_; 3125 3126 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3127 return handleNativeEvent_; 3128 } 3129 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3130 handleNativeEvent_ = neh; 3131 } 3132 3133 version(Windows) 3134 // compatibility shim with the old deprecated way 3135 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3136 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) { 3137 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3138 auto ret = dg(h, m, w, l); 3139 if(ret == 0) 3140 r = 1; 3141 return ret; 3142 }; 3143 } 3144 3145 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3146 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3147 /// this instead and it will work the same way. 3148 __gshared NativeEventHandler handleNativeGlobalEvent; 3149 3150 // private: 3151 /// The native implementation is available, but you shouldn't use it unless you are 3152 /// familiar with the underlying operating system, don't mind depending on it, and 3153 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3154 /// do what you need to do with handleNativeEvent instead. 3155 /// 3156 /// This is likely to eventually change to be just a struct holding platform-specific 3157 /// handles instead of a template mixin at some point because I'm not happy with the 3158 /// code duplication here (ironically). 3159 mixin NativeSimpleWindowImplementation!() impl; 3160 3161 /** 3162 This is in-process one-way (from anything to window) event sending mechanics. 3163 It is thread-safe, so it can be used in multi-threaded applications to send, 3164 for example, "wake up and repaint" events when thread completed some operation. 3165 This will allow to avoid using timer pulse to check events with synchronization, 3166 'cause event handler will be called in UI thread. You can stop guessing which 3167 pulse frequency will be enough for your app. 3168 Note that events handlers may be called in arbitrary order, i.e. last registered 3169 handler can be called first, and vice versa. 3170 */ 3171 public: 3172 /** Is our custom event queue empty? Can be used in simple cases to prevent 3173 * "spamming" window with events it can't cope with. 3174 * It is safe to call this from non-UI threads. 3175 */ 3176 @property bool eventQueueEmpty() () { 3177 synchronized(this) { 3178 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3179 } 3180 return true; 3181 } 3182 3183 /** Does our custom event queue contains at least one with the given type? 3184 * Can be used in simple cases to prevent "spamming" window with events 3185 * it can't cope with. 3186 * It is safe to call this from non-UI threads. 3187 */ 3188 @property bool eventQueued(ET:Object) () { 3189 synchronized(this) { 3190 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3191 if (!o.doProcess) { 3192 if (cast(ET)(o.evt)) return true; 3193 } 3194 } 3195 } 3196 return false; 3197 } 3198 3199 /++ 3200 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3201 3202 History: 3203 Added May 12, 2021 3204 +/ 3205 void delegate(Exception e) nothrow eventUncaughtException; 3206 3207 /** Add listener for custom event. Can be used like this: 3208 * 3209 * --------------------- 3210 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3211 * ... 3212 * win.removeEventListener(eid); 3213 * --------------------- 3214 * 3215 * Returns: 0 on failure (should never happen, so ignore it) 3216 * 3217 * $(WARNING Don't use this method in object destructors!) 3218 * 3219 * $(WARNING It is better to register all event handlers and don't remove 'em, 3220 * 'cause if event handler id counter will overflow, you won't be able 3221 * to register any more events.) 3222 */ 3223 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3224 if (dg is null) return 0; // ignore empty handlers 3225 synchronized(this) { 3226 //FIXME: abort on overflow? 3227 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3228 EventHandlerEntry e; 3229 e.dg = delegate (Object o) { 3230 if (auto co = cast(ET)o) { 3231 try { 3232 dg(co); 3233 } catch (Exception e) { 3234 // sorry! 3235 if(eventUncaughtException) 3236 eventUncaughtException(e); 3237 } 3238 return true; 3239 } 3240 return false; 3241 }; 3242 e.id = lastUsedHandlerId; 3243 auto optr = eventHandlers.ptr; 3244 eventHandlers ~= e; 3245 if (eventHandlers.ptr !is optr) { 3246 import core.memory : GC; 3247 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3248 } 3249 return lastUsedHandlerId; 3250 } 3251 } 3252 3253 /// Remove event listener. It is safe to pass invalid event id here. 3254 /// $(WARNING Don't use this method in object destructors!) 3255 void removeEventListener() (uint id) { 3256 if (id == 0 || id > lastUsedHandlerId) return; 3257 synchronized(this) { 3258 foreach (immutable idx; 0..eventHandlers.length) { 3259 if (eventHandlers[idx].id == id) { 3260 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3261 eventHandlers[$-1].dg = null; 3262 eventHandlers.length -= 1; 3263 eventHandlers.assumeSafeAppend; 3264 return; 3265 } 3266 } 3267 } 3268 } 3269 3270 /// Post event to queue. It is safe to call this from non-UI threads. 3271 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3272 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3273 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3274 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3275 if (this.closed) return false; // closed windows can't handle events 3276 3277 // remove all events of type `ET` 3278 void removeAllET () { 3279 uint eidx = 0, ec = eventQueueUsed; 3280 auto eptr = eventQueue.ptr; 3281 while (eidx < ec) { 3282 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3283 if (cast(ET)eptr.evt !is null) { 3284 // i found her! 3285 if (inCustomEventProcessor) { 3286 // if we're in custom event processing loop, processor will clear it for us 3287 eptr.evt = null; 3288 ++eidx; 3289 ++eptr; 3290 } else { 3291 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3292 ec = --eventQueueUsed; 3293 // clear last event (it is already copied) 3294 eventQueue.ptr[ec].evt = null; 3295 } 3296 } else { 3297 ++eidx; 3298 ++eptr; 3299 } 3300 } 3301 } 3302 3303 if (evt is null) { 3304 if (replace) { synchronized(this) removeAllET(); } 3305 // ignore empty events, they can't be handled anyway 3306 return false; 3307 } 3308 3309 // add events even if no event FD/event object created yet 3310 synchronized(this) { 3311 if (replace) removeAllET(); 3312 if (eventQueueUsed == uint.max) return false; // just in case 3313 if (eventQueueUsed < eventQueue.length) { 3314 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3315 } else { 3316 if (eventQueue.capacity == eventQueue.length) { 3317 // need to reallocate; do a trick to ensure that old array is cleared 3318 auto oarr = eventQueue; 3319 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3320 // just in case, do yet another check 3321 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3322 import core.memory : GC; 3323 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3324 } else { 3325 auto optr = eventQueue.ptr; 3326 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3327 assert(eventQueue.ptr is optr); 3328 } 3329 ++eventQueueUsed; 3330 assert(eventQueueUsed == eventQueue.length); 3331 } 3332 if (!eventWakeUp()) { 3333 // can't wake up event processor, so there is no reason to keep the event 3334 assert(eventQueueUsed > 0); 3335 eventQueue[--eventQueueUsed].evt = null; 3336 return false; 3337 } 3338 return true; 3339 } 3340 } 3341 3342 /// Post event to queue. It is safe to call this from non-UI threads. 3343 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3344 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3345 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3346 return postTimeout!ET(evt, 0, replace); 3347 } 3348 3349 private: 3350 private import core.time : MonoTime; 3351 3352 version(Posix) { 3353 __gshared int customEventFDRead = -1; 3354 __gshared int customEventFDWrite = -1; 3355 __gshared int customSignalFD = -1; 3356 } else version(Windows) { 3357 __gshared HANDLE customEventH = null; 3358 } 3359 3360 // wake up event processor 3361 static bool eventWakeUp () { 3362 version(X11) { 3363 import core.sys.posix.unistd : write; 3364 ulong n = 1; 3365 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3366 return true; 3367 } else version(Windows) { 3368 if (customEventH !is null) SetEvent(customEventH); 3369 return true; 3370 } else { 3371 // not implemented for other OSes 3372 return false; 3373 } 3374 } 3375 3376 static struct QueuedEvent { 3377 Object evt; 3378 bool timed = false; 3379 MonoTime hittime = MonoTime.zero; 3380 bool doProcess = false; // process event at the current iteration (internal flag) 3381 3382 this (Object aevt, uint toutmsecs) { 3383 evt = aevt; 3384 if (toutmsecs > 0) { 3385 import core.time : msecs; 3386 timed = true; 3387 hittime = MonoTime.currTime+toutmsecs.msecs; 3388 } 3389 } 3390 } 3391 3392 alias CustomEventHandler = bool delegate (Object o) nothrow; 3393 static struct EventHandlerEntry { 3394 CustomEventHandler dg; 3395 uint id; 3396 } 3397 3398 uint lastUsedHandlerId; 3399 EventHandlerEntry[] eventHandlers; 3400 QueuedEvent[] eventQueue = null; 3401 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3402 bool inCustomEventProcessor = false; // required to properly remove events 3403 3404 // process queued events and call custom event handlers 3405 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3406 void processCustomEvents () { 3407 bool hasSomethingToDo = false; 3408 uint ecount; 3409 bool ocep; 3410 synchronized(this) { 3411 ocep = inCustomEventProcessor; 3412 inCustomEventProcessor = true; 3413 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3414 auto ctt = MonoTime.currTime; 3415 bool hasEmpty = false; 3416 // mark events to process (this is required for `eventQueued()`) 3417 foreach (ref qe; eventQueue[0..ecount]) { 3418 if (qe.evt is null) { hasEmpty = true; continue; } 3419 if (qe.timed) { 3420 qe.doProcess = (qe.hittime <= ctt); 3421 } else { 3422 qe.doProcess = true; 3423 } 3424 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3425 } 3426 if (!hasSomethingToDo) { 3427 // remove empty events 3428 if (hasEmpty) { 3429 uint eidx = 0, ec = eventQueueUsed; 3430 auto eptr = eventQueue.ptr; 3431 while (eidx < ec) { 3432 if (eptr.evt is null) { 3433 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3434 ec = --eventQueueUsed; 3435 eventQueue.ptr[ec].evt = null; // make GC life easier 3436 } else { 3437 ++eidx; 3438 ++eptr; 3439 } 3440 } 3441 } 3442 inCustomEventProcessor = ocep; 3443 return; 3444 } 3445 } 3446 // process marked events 3447 uint efree = 0; // non-processed events will be put at this index 3448 EventHandlerEntry[] eh; 3449 Object evt; 3450 foreach (immutable eidx; 0..ecount) { 3451 synchronized(this) { 3452 if (!eventQueue[eidx].doProcess) { 3453 // skip this event 3454 assert(efree <= eidx); 3455 if (efree != eidx) { 3456 // copy this event to queue start 3457 eventQueue[efree] = eventQueue[eidx]; 3458 eventQueue[eidx].evt = null; // just in case 3459 } 3460 ++efree; 3461 continue; 3462 } 3463 evt = eventQueue[eidx].evt; 3464 eventQueue[eidx].evt = null; // in case event handler will hit GC 3465 if (evt is null) continue; // just in case 3466 // try all handlers; this can be slow, but meh... 3467 eh = eventHandlers; 3468 } 3469 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3470 evt = null; 3471 eh = null; 3472 } 3473 synchronized(this) { 3474 // move all unprocessed events to queue top; efree holds first "free index" 3475 foreach (immutable eidx; ecount..eventQueueUsed) { 3476 assert(efree <= eidx); 3477 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3478 ++efree; 3479 } 3480 eventQueueUsed = efree; 3481 // wake up event processor on next event loop iteration if we have more queued events 3482 // also, remove empty events 3483 bool awaken = false; 3484 uint eidx = 0, ec = eventQueueUsed; 3485 auto eptr = eventQueue.ptr; 3486 while (eidx < ec) { 3487 if (eptr.evt is null) { 3488 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3489 ec = --eventQueueUsed; 3490 eventQueue.ptr[ec].evt = null; // make GC life easier 3491 } else { 3492 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3493 ++eidx; 3494 ++eptr; 3495 } 3496 } 3497 inCustomEventProcessor = ocep; 3498 } 3499 } 3500 3501 // for all windows in nativeMapping 3502 package static void processAllCustomEvents () { 3503 3504 cleanupQueue.process(); 3505 3506 justCommunication.processCustomEvents(); 3507 3508 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3509 if (sw is null || sw.closed) continue; 3510 sw.processCustomEvents(); 3511 } 3512 3513 runPendingRunInGuiThreadDelegates(); 3514 } 3515 3516 // 0: infinite (i.e. no scheduled events in queue) 3517 uint eventQueueTimeoutMSecs () { 3518 synchronized(this) { 3519 if (eventQueueUsed == 0) return 0; 3520 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3521 uint res = int.max; 3522 auto ctt = MonoTime.currTime; 3523 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3524 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3525 if (qe.doProcess) continue; // just in case 3526 if (!qe.timed) return 1; // minimal 3527 if (qe.hittime <= ctt) return 1; // minimal 3528 auto tms = (qe.hittime-ctt).total!"msecs"; 3529 if (tms < 1) tms = 1; // safety net 3530 if (tms >= int.max) tms = int.max-1; // and another safety net 3531 if (res > tms) res = cast(uint)tms; 3532 } 3533 return (res >= int.max ? 0 : res); 3534 } 3535 } 3536 3537 // for all windows in nativeMapping 3538 static uint eventAllQueueTimeoutMSecs () { 3539 uint res = uint.max; 3540 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3541 if (sw is null || sw.closed) continue; 3542 uint to = sw.eventQueueTimeoutMSecs(); 3543 if (to && to < res) { 3544 res = to; 3545 if (to == 1) break; // can't have less than this 3546 } 3547 } 3548 return (res >= int.max ? 0 : res); 3549 } 3550 3551 version(X11) { 3552 ResizeEvent pendingResizeEvent; 3553 } 3554 } 3555 3556 /++ 3557 Magic pseudo-window for just posting events to a global queue. 3558 3559 Not entirely supported, I might delete it at any time. 3560 3561 Added Nov 5, 2021. 3562 +/ 3563 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init); 3564 3565 /* Drag and drop support { */ 3566 version(X11) { 3567 3568 } else version(Windows) { 3569 import core.sys.windows.uuid; 3570 import core.sys.windows.ole2; 3571 import core.sys.windows.oleidl; 3572 import core.sys.windows.objidl; 3573 import core.sys.windows.wtypes; 3574 3575 pragma(lib, "ole32"); 3576 void initDnd() { 3577 auto err = OleInitialize(null); 3578 if(err != S_OK && err != S_FALSE) 3579 throw new Exception("init");//err); 3580 } 3581 } 3582 /* } End drag and drop support */ 3583 3584 3585 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 3586 /// See [GenericCursor]. 3587 class MouseCursor { 3588 int osId; 3589 bool isStockCursor; 3590 private this(int osId) { 3591 this.osId = osId; 3592 this.isStockCursor = true; 3593 } 3594 3595 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 3596 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 3597 3598 version(Windows) { 3599 HCURSOR cursor_; 3600 HCURSOR cursorHandle() { 3601 if(cursor_ is null) 3602 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 3603 return cursor_; 3604 } 3605 3606 } else static if(UsingSimpledisplayX11) { 3607 Cursor cursor_ = None; 3608 int xDisplaySequence; 3609 3610 Cursor cursorHandle() { 3611 if(this.osId == None) 3612 return None; 3613 3614 // we need to reload if we on a new X connection 3615 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 3616 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 3617 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 3618 } 3619 return cursor_; 3620 } 3621 } 3622 } 3623 3624 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 3625 // https://tronche.com/gui/x/xlib/appendix/b/ 3626 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 3627 /// 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. 3628 enum GenericCursorType { 3629 Default, /// The default arrow pointer. 3630 Wait, /// A cursor indicating something is loading and the user must wait. 3631 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3632 Help, /// A cursor indicating the user can get help about the pointer location. 3633 Cross, /// A crosshair. 3634 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3635 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 3636 UpArrow, /// An arrow pointing straight up. 3637 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3638 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3639 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 3640 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 3641 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 3642 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 3643 3644 } 3645 3646 /* 3647 X_plus == css cell == Windows ? 3648 */ 3649 3650 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3651 static struct GenericCursor { 3652 static: 3653 /// 3654 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3655 static MouseCursor mc; 3656 3657 auto type = __traits(getMember, GenericCursorType, str); 3658 3659 if(mc is null) { 3660 3661 version(Windows) { 3662 int osId; 3663 final switch(type) { 3664 case GenericCursorType.Default: osId = IDC_ARROW; break; 3665 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3666 case GenericCursorType.Hand: osId = IDC_HAND; break; 3667 case GenericCursorType.Help: osId = IDC_HELP; break; 3668 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3669 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3670 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3671 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3672 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3673 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3674 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3675 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3676 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3677 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3678 } 3679 } else static if(UsingSimpledisplayX11) { 3680 int osId; 3681 final switch(type) { 3682 case GenericCursorType.Default: osId = None; break; 3683 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3684 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3685 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3686 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3687 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3688 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3689 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3690 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3691 3692 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3693 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3694 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3695 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3696 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3697 } 3698 3699 } else featureNotImplemented(); 3700 3701 mc = new MouseCursor(osId); 3702 } 3703 return mc; 3704 } 3705 } 3706 3707 3708 /++ 3709 If you want to get more control over the event loop, you can use this. 3710 3711 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 3712 to `EventLoop.get.run`. 3713 +/ 3714 struct EventLoop { 3715 @disable this(); 3716 3717 /// Gets a reference to an existing event loop 3718 static EventLoop get() { 3719 return EventLoop(0, null); 3720 } 3721 3722 static void quitApplication() { 3723 EventLoop.get().exit(); 3724 } 3725 3726 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 3727 3728 /// Construct an application-global event loop for yourself 3729 /// See_Also: [SimpleWindow.setEventHandlers] 3730 this(long pulseTimeout, void delegate() handlePulse) { 3731 synchronized(monitor) { 3732 if(impl is null) { 3733 claimGuiThread(); 3734 version(sdpy_thread_checks) assert(thisIsGuiThread); 3735 impl = new EventLoopImpl(pulseTimeout, handlePulse); 3736 } else { 3737 if(pulseTimeout) { 3738 impl.pulseTimeout = pulseTimeout; 3739 impl.handlePulse = handlePulse; 3740 } 3741 } 3742 impl.refcount++; 3743 } 3744 } 3745 3746 ~this() { 3747 if(impl is null) 3748 return; 3749 impl.refcount--; 3750 if(impl.refcount == 0) { 3751 impl.dispose(); 3752 if(thisIsGuiThread) 3753 guiThreadFinalize(); 3754 } 3755 3756 } 3757 3758 this(this) { 3759 if(impl is null) 3760 return; 3761 impl.refcount++; 3762 } 3763 3764 /// Runs the event loop until the whileCondition, if present, returns false 3765 int run(bool delegate() whileCondition = null) { 3766 assert(impl !is null); 3767 impl.notExited = true; 3768 return impl.run(whileCondition); 3769 } 3770 3771 /// Exits the event loop 3772 void exit() { 3773 assert(impl !is null); 3774 impl.notExited = false; 3775 } 3776 3777 version(linux) 3778 ref void delegate(int) signalHandler() { 3779 assert(impl !is null); 3780 return impl.signalHandler; 3781 } 3782 3783 __gshared static EventLoopImpl* impl; 3784 } 3785 3786 version(linux) 3787 void delegate(int, int) globalHupHandler; 3788 3789 version(Posix) 3790 void makeNonBlocking(int fd) { 3791 import fcntl = core.sys.posix.fcntl; 3792 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 3793 if(flags == -1) 3794 throw new Exception("fcntl get"); 3795 flags |= fcntl.O_NONBLOCK; 3796 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 3797 if(s == -1) 3798 throw new Exception("fcntl set"); 3799 } 3800 3801 struct EventLoopImpl { 3802 int refcount; 3803 3804 bool notExited = true; 3805 3806 version(linux) { 3807 static import ep = core.sys.linux.epoll; 3808 static import unix = core.sys.posix.unistd; 3809 static import err = core.stdc.errno; 3810 import core.sys.linux.timerfd; 3811 3812 void delegate(int) signalHandler; 3813 } 3814 3815 version(X11) { 3816 int pulseFd = -1; 3817 version(linux) ep.epoll_event[16] events = void; 3818 } else version(Windows) { 3819 Timer pulser; 3820 HANDLE[] handles; 3821 } 3822 3823 3824 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3825 /// to call this, as it's not recommended to share window between threads. 3826 void mtLock () { 3827 version(X11) { 3828 XLockDisplay(this.display); 3829 } 3830 } 3831 3832 version(X11) 3833 auto display() { return XDisplayConnection.get; } 3834 3835 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 3836 /// to call this, as it's not recommended to share window between threads. 3837 void mtUnlock () { 3838 version(X11) { 3839 XUnlockDisplay(this.display); 3840 } 3841 } 3842 3843 version(with_eventloop) 3844 void initialize(long pulseTimeout) {} 3845 else 3846 void initialize(long pulseTimeout) { 3847 version(Windows) { 3848 if(pulseTimeout && handlePulse !is null) 3849 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 3850 3851 if (customEventH is null) { 3852 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 3853 if (customEventH !is null) { 3854 handles ~= customEventH; 3855 } else { 3856 // this is something that should not be; better be safe than sorry 3857 throw new Exception("can't create eventfd for custom event processing"); 3858 } 3859 } 3860 3861 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 3862 } 3863 3864 version(linux) { 3865 prepareEventLoop(); 3866 { 3867 auto display = XDisplayConnection.get; 3868 // adding Xlib file 3869 ep.epoll_event ev = void; 3870 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3871 ev.events = ep.EPOLLIN; 3872 ev.data.fd = display.fd; 3873 //import std.conv; 3874 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 3875 throw new Exception("add x fd");// ~ to!string(epollFd)); 3876 displayFd = display.fd; 3877 } 3878 3879 if(pulseTimeout && handlePulse !is null) { 3880 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 3881 if(pulseFd == -1) 3882 throw new Exception("pulse timer create failed"); 3883 3884 itimerspec value; 3885 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 3886 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 3887 3888 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 3889 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 3890 3891 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 3892 throw new Exception("couldn't make pulse timer"); 3893 3894 ep.epoll_event ev = void; 3895 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3896 ev.events = ep.EPOLLIN; 3897 ev.data.fd = pulseFd; 3898 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 3899 } 3900 3901 // eventfd for custom events 3902 if (customEventFDWrite == -1) { 3903 customEventFDWrite = eventfd(0, 0); 3904 customEventFDRead = customEventFDWrite; 3905 if (customEventFDRead >= 0) { 3906 ep.epoll_event ev = void; 3907 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3908 ev.events = ep.EPOLLIN; 3909 ev.data.fd = customEventFDRead; 3910 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 3911 } else { 3912 // this is something that should not be; better be safe than sorry 3913 throw new Exception("can't create eventfd for custom event processing"); 3914 } 3915 } 3916 3917 if (customSignalFD == -1) { 3918 import core.sys.linux.sys.signalfd; 3919 3920 sigset_t sigset; 3921 auto err = sigemptyset(&sigset); 3922 assert(!err); 3923 err = sigaddset(&sigset, SIGINT); 3924 assert(!err); 3925 err = sigaddset(&sigset, SIGHUP); 3926 assert(!err); 3927 err = sigprocmask(SIG_BLOCK, &sigset, null); 3928 assert(!err); 3929 3930 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 3931 assert(customSignalFD != -1); 3932 3933 ep.epoll_event ev = void; 3934 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3935 ev.events = ep.EPOLLIN; 3936 ev.data.fd = customSignalFD; 3937 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 3938 } 3939 } else version(Posix) { 3940 prepareEventLoop(); 3941 if (customEventFDRead == -1) { 3942 int[2] bfr; 3943 import core.sys.posix.unistd; 3944 auto ret = pipe(bfr); 3945 if(ret == -1) throw new Exception("pipe"); 3946 customEventFDRead = bfr[0]; 3947 customEventFDWrite = bfr[1]; 3948 } 3949 3950 } 3951 3952 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 3953 3954 version(linux) { 3955 this.mtLock(); 3956 scope(exit) this.mtUnlock(); 3957 XPending(display); // no, really 3958 } 3959 3960 disposed = false; 3961 } 3962 3963 bool disposed = true; 3964 version(X11) 3965 int displayFd = -1; 3966 3967 version(with_eventloop) 3968 void dispose() {} 3969 else 3970 void dispose() { 3971 disposed = true; 3972 version(X11) { 3973 if(pulseFd != -1) { 3974 import unix = core.sys.posix.unistd; 3975 unix.close(pulseFd); 3976 pulseFd = -1; 3977 } 3978 3979 version(linux) 3980 if(displayFd != -1) { 3981 // 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 3982 ep.epoll_event ev = void; 3983 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3984 ev.events = ep.EPOLLIN; 3985 ev.data.fd = displayFd; 3986 //import std.conv; 3987 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 3988 displayFd = -1; 3989 } 3990 3991 } else version(Windows) { 3992 if(pulser !is null) { 3993 pulser.destroy(); 3994 pulser = null; 3995 } 3996 if (customEventH !is null) { 3997 CloseHandle(customEventH); 3998 customEventH = null; 3999 } 4000 } 4001 } 4002 4003 this(long pulseTimeout, void delegate() handlePulse) { 4004 this.pulseTimeout = pulseTimeout; 4005 this.handlePulse = handlePulse; 4006 initialize(pulseTimeout); 4007 } 4008 4009 private long pulseTimeout; 4010 void delegate() handlePulse; 4011 4012 ~this() { 4013 dispose(); 4014 } 4015 4016 version(Posix) 4017 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4018 version(Posix) 4019 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4020 version(linux) 4021 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4022 version(Windows) 4023 ref auto customEventH() { return SimpleWindow.customEventH; } 4024 4025 version(with_eventloop) { 4026 int loopHelper(bool delegate() whileCondition) { 4027 // FIXME: whileCondition 4028 import arsd.eventloop; 4029 loop(); 4030 return 0; 4031 } 4032 } else 4033 int loopHelper(bool delegate() whileCondition) { 4034 version(X11) { 4035 bool done = false; 4036 4037 XFlush(display); 4038 insideXEventLoop = true; 4039 scope(exit) insideXEventLoop = false; 4040 4041 version(linux) { 4042 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4043 bool forceXPending = false; 4044 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4045 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4046 { 4047 this.mtLock(); 4048 scope(exit) this.mtUnlock(); 4049 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 4050 } 4051 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4052 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4053 if(nfds == -1) { 4054 if(err.errno == err.EINTR) { 4055 //if(forceXPending) goto xpending; 4056 continue; // interrupted by signal, just try again 4057 } 4058 throw new Exception("epoll wait failure"); 4059 } 4060 4061 SimpleWindow.processAllCustomEvents(); // anyway 4062 //version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4063 foreach(idx; 0 .. nfds) { 4064 if(done) break; 4065 auto fd = events[idx].data.fd; 4066 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4067 auto flags = events[idx].events; 4068 if(flags & ep.EPOLLIN) { 4069 if (fd == customSignalFD) { 4070 version(linux) { 4071 import core.sys.linux.sys.signalfd; 4072 import core.sys.posix.unistd : read; 4073 signalfd_siginfo info; 4074 read(customSignalFD, &info, info.sizeof); 4075 4076 auto sig = info.ssi_signo; 4077 4078 if(EventLoop.get.signalHandler !is null) { 4079 EventLoop.get.signalHandler()(sig); 4080 } else { 4081 EventLoop.get.exit(); 4082 } 4083 } 4084 } else if(fd == display.fd) { 4085 version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); } 4086 this.mtLock(); 4087 scope(exit) this.mtUnlock(); 4088 while(!done && XPending(display)) { 4089 done = doXNextEvent(this.display); 4090 } 4091 forceXPending = false; 4092 } else if(fd == pulseFd) { 4093 long expirationCount; 4094 // 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... 4095 4096 handlePulse(); 4097 4098 // read just to clear the buffer so poll doesn't trigger again 4099 // BTW I read AFTER the pulse because if the pulse handler takes 4100 // a lot of time to execute, we don't want the app to get stuck 4101 // in a loop of timer hits without a chance to do anything else 4102 // 4103 // IOW handlePulse happens at most once per pulse interval. 4104 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4105 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 4106 } else if (fd == customEventFDRead) { 4107 // we have some custom events; process 'em 4108 import core.sys.posix.unistd : read; 4109 ulong n; 4110 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4111 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4112 //SimpleWindow.processAllCustomEvents(); 4113 } else { 4114 // some other timer 4115 version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); } 4116 4117 if(Timer* t = fd in Timer.mapping) 4118 (*t).trigger(); 4119 4120 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4121 (*pfr).ready(flags); 4122 4123 // or i might add support for other FDs too 4124 // but for now it is just timer 4125 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4126 } 4127 } 4128 if(flags & ep.EPOLLHUP) { 4129 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4130 (*pfr).hup(flags); 4131 if(globalHupHandler) 4132 globalHupHandler(fd, flags); 4133 } 4134 /+ 4135 } else { 4136 // not interested in OUT, we are just reading here. 4137 // 4138 // error or hup might also be reported 4139 // but it shouldn't here since we are only 4140 // using a few types of FD and Xlib will report 4141 // if it dies. 4142 // so instead of thoughtfully handling it, I'll 4143 // just throw. for now at least 4144 4145 throw new Exception("epoll did something else"); 4146 } 4147 +/ 4148 } 4149 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4150 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4151 xpending: 4152 if (!done && forceXPending) { 4153 this.mtLock(); 4154 scope(exit) this.mtUnlock(); 4155 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4156 while(!done && XPending(display)) { 4157 done = doXNextEvent(this.display); 4158 } 4159 } 4160 } 4161 } else { 4162 // Generic fallback: yes to simple pulse support, 4163 // but NO timer support! 4164 4165 // FIXME: we could probably support the POSIX timer_create 4166 // signal-based option, but I'm in no rush to write it since 4167 // I prefer the fd-based functions. 4168 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4169 4170 import core.sys.posix.poll; 4171 4172 pollfd[] pfds; 4173 pollfd[32] pfdsBuffer; 4174 auto len = PosixFdReader.mapping.length + 2; 4175 // FIXME: i should just reuse the buffer 4176 if(len < pfdsBuffer.length) 4177 pfds = pfdsBuffer[0 .. len]; 4178 else 4179 pfds = new pollfd[](len); 4180 4181 pfds[0].fd = display.fd; 4182 pfds[0].events = POLLIN; 4183 pfds[0].revents = 0; 4184 4185 int slot = 1; 4186 4187 if(customEventFDRead != -1) { 4188 pfds[slot].fd = customEventFDRead; 4189 pfds[slot].events = POLLIN; 4190 pfds[slot].revents = 0; 4191 4192 slot++; 4193 } 4194 4195 foreach(fd, obj; PosixFdReader.mapping) { 4196 if(!obj.enabled) continue; 4197 pfds[slot].fd = fd; 4198 pfds[slot].events = POLLIN; 4199 pfds[slot].revents = 0; 4200 4201 slot++; 4202 } 4203 4204 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4205 if(ret == -1) throw new Exception("poll"); 4206 4207 if(ret == 0) { 4208 // FIXME it may not necessarily time out if events keep coming 4209 if(handlePulse !is null) 4210 handlePulse(); 4211 } else { 4212 foreach(s; 0 .. slot) { 4213 if(pfds[s].revents == 0) continue; 4214 4215 if(pfds[s].fd == display.fd) { 4216 while(!done && XPending(display)) { 4217 this.mtLock(); 4218 scope(exit) this.mtUnlock(); 4219 done = doXNextEvent(this.display); 4220 } 4221 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4222 4223 import core.sys.posix.unistd : read; 4224 ulong n; 4225 read(customEventFDRead, &n, n.sizeof); 4226 SimpleWindow.processAllCustomEvents(); 4227 } else { 4228 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4229 if(pfds[s].revents & POLLNVAL) { 4230 obj.dispose(); 4231 } else { 4232 obj.ready(pfds[s].revents); 4233 } 4234 } 4235 4236 ret--; 4237 if(ret == 0) break; 4238 } 4239 } 4240 } 4241 } 4242 } 4243 4244 version(Windows) { 4245 int ret = -1; 4246 MSG message; 4247 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4248 eventLoopRound++; 4249 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4250 auto waitResult = MsgWaitForMultipleObjectsEx( 4251 cast(int) handles.length, handles.ptr, 4252 (wto == 0 ? INFINITE : wto), /* timeout */ 4253 0x04FF, /* QS_ALLINPUT */ 4254 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4255 4256 SimpleWindow.processAllCustomEvents(); // anyway 4257 enum WAIT_OBJECT_0 = 0; 4258 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4259 auto h = handles[waitResult - WAIT_OBJECT_0]; 4260 if(auto e = h in WindowsHandleReader.mapping) { 4261 (*e).ready(); 4262 } 4263 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4264 // message ready 4265 int count; 4266 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 4267 ret = GetMessage(&message, null, 0, 0); 4268 if(ret == -1) 4269 throw new Exception("GetMessage failed"); 4270 TranslateMessage(&message); 4271 DispatchMessage(&message); 4272 4273 count++; 4274 if(count > 10) 4275 break; // take the opportunity to catch up on other events 4276 4277 if(ret == 0) { // WM_QUIT 4278 EventLoop.quitApplication(); 4279 break; 4280 } 4281 } 4282 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4283 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4284 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4285 // timeout, should never happen since we aren't using it 4286 } else if(waitResult == 0xFFFFFFFF) { 4287 // failed 4288 throw new Exception("MsgWaitForMultipleObjectsEx failed"); 4289 } else { 4290 // idk.... 4291 } 4292 } 4293 4294 // return message.wParam; 4295 return 0; 4296 } else { 4297 return 0; 4298 } 4299 } 4300 4301 int run(bool delegate() whileCondition = null) { 4302 if(disposed) 4303 initialize(this.pulseTimeout); 4304 4305 version(X11) { 4306 try { 4307 return loopHelper(whileCondition); 4308 } catch(XDisconnectException e) { 4309 if(e.userRequested) { 4310 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4311 item.discardConnectionState(); 4312 XCloseDisplay(XDisplayConnection.display); 4313 } 4314 4315 XDisplayConnection.display = null; 4316 4317 this.dispose(); 4318 4319 throw e; 4320 } 4321 } else { 4322 return loopHelper(whileCondition); 4323 } 4324 } 4325 } 4326 4327 4328 /++ 4329 Provides an icon on the system notification area (also known as the system tray). 4330 4331 4332 If a notification area is not available with the NotificationIcon object is created, 4333 it will silently succeed and simply attempt to create one when an area becomes available. 4334 4335 4336 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. 4337 If this is wrong, pass -version=WindowsXP to dmd when compiling and it will 4338 use the older version. 4339 +/ 4340 version(OSXCocoa) {} else // NotYetImplementedException 4341 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4342 4343 version(X11) { 4344 void recreateAfterDisconnect() { 4345 stateDiscarded = false; 4346 clippixmap = None; 4347 throw new Exception("NOT IMPLEMENTED"); 4348 } 4349 4350 bool stateDiscarded; 4351 void discardConnectionState() { 4352 stateDiscarded = true; 4353 } 4354 } 4355 4356 4357 version(X11) { 4358 Image img; 4359 4360 NativeEventHandler getNativeEventHandler() { 4361 return delegate int(XEvent e) { 4362 switch(e.type) { 4363 case EventType.Expose: 4364 //case EventType.VisibilityNotify: 4365 redraw(); 4366 break; 4367 case EventType.ClientMessage: 4368 version(sddddd) { 4369 import std.stdio; 4370 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4371 writeln("\t", e.xclient.format); 4372 writeln("\t", e.xclient.data.l); 4373 } 4374 break; 4375 case EventType.ButtonPress: 4376 auto event = e.xbutton; 4377 if (onClick !is null || onClickEx !is null) { 4378 MouseButton mb = cast(MouseButton)0; 4379 switch (event.button) { 4380 case 1: mb = MouseButton.left; break; // left 4381 case 2: mb = MouseButton.middle; break; // middle 4382 case 3: mb = MouseButton.right; break; // right 4383 case 4: mb = MouseButton.wheelUp; break; // scroll up 4384 case 5: mb = MouseButton.wheelDown; break; // scroll down 4385 case 6: break; // idk 4386 case 7: break; // idk 4387 case 8: mb = MouseButton.backButton; break; 4388 case 9: mb = MouseButton.forwardButton; break; 4389 default: 4390 } 4391 if (mb) { 4392 try { onClick()(mb); } catch (Exception) {} 4393 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 4394 } 4395 } 4396 break; 4397 case EventType.EnterNotify: 4398 if (onEnter !is null) { 4399 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 4400 } 4401 break; 4402 case EventType.LeaveNotify: 4403 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 4404 break; 4405 case EventType.DestroyNotify: 4406 active = false; 4407 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 4408 break; 4409 case EventType.ConfigureNotify: 4410 auto event = e.xconfigure; 4411 this.width = event.width; 4412 this.height = event.height; 4413 //import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y); 4414 redraw(); 4415 break; 4416 default: return 1; 4417 } 4418 return 1; 4419 }; 4420 } 4421 4422 /* private */ void hideBalloon() { 4423 balloon.close(); 4424 version(with_timer) 4425 timer.destroy(); 4426 balloon = null; 4427 version(with_timer) 4428 timer = null; 4429 } 4430 4431 void redraw() { 4432 if (!active) return; 4433 4434 auto display = XDisplayConnection.get; 4435 auto gc = DefaultGC(display, DefaultScreen(display)); 4436 XClearWindow(display, nativeHandle); 4437 4438 XSetClipMask(display, gc, clippixmap); 4439 4440 XSetForeground(display, gc, 4441 cast(uint) 0 << 16 | 4442 cast(uint) 0 << 8 | 4443 cast(uint) 0); 4444 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 4445 4446 if (img is null) { 4447 XSetForeground(display, gc, 4448 cast(uint) 0 << 16 | 4449 cast(uint) 127 << 8 | 4450 cast(uint) 0); 4451 XFillArc(display, nativeHandle, 4452 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 4453 } else { 4454 int dx = 0; 4455 int dy = 0; 4456 if(width > img.width) 4457 dx = (width - img.width) / 2; 4458 if(height > img.height) 4459 dy = (height - img.height) / 2; 4460 XSetClipOrigin(display, gc, dx, dy); 4461 4462 if (img.usingXshm) 4463 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false); 4464 else 4465 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height); 4466 } 4467 XSetClipMask(display, gc, None); 4468 flushGui(); 4469 } 4470 4471 static Window getTrayOwner() { 4472 auto display = XDisplayConnection.get; 4473 auto i = cast(int) DefaultScreen(display); 4474 if(i < 10 && i >= 0) { 4475 static Atom atom; 4476 if(atom == None) 4477 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 4478 return XGetSelectionOwner(display, atom); 4479 } 4480 return None; 4481 } 4482 4483 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 4484 auto to = getTrayOwner(); 4485 auto display = XDisplayConnection.get; 4486 XEvent ev; 4487 ev.xclient.type = EventType.ClientMessage; 4488 ev.xclient.window = to; 4489 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 4490 ev.xclient.format = 32; 4491 ev.xclient.data.l[0] = CurrentTime; 4492 ev.xclient.data.l[1] = message; 4493 ev.xclient.data.l[2] = d1; 4494 ev.xclient.data.l[3] = d2; 4495 ev.xclient.data.l[4] = d3; 4496 4497 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 4498 } 4499 4500 private static NotificationAreaIcon[] activeIcons; 4501 4502 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 4503 private void newManager() { 4504 close(); 4505 createXWin(); 4506 4507 if(this.clippixmap) 4508 XFreePixmap(XDisplayConnection.get, clippixmap); 4509 if(this.originalMemoryImage) 4510 this.icon = this.originalMemoryImage; 4511 else if(this.img) 4512 this.icon = this.img; 4513 } 4514 4515 private void createXWin () { 4516 // create window 4517 auto display = XDisplayConnection.get; 4518 4519 // to check for MANAGER on root window to catch new/changed tray owners 4520 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 4521 // so if a thing does appear, we can handle it 4522 foreach(ai; activeIcons) 4523 if(ai is this) 4524 goto alreadythere; 4525 activeIcons ~= this; 4526 alreadythere: 4527 4528 // and check for an existing tray 4529 auto trayOwner = getTrayOwner(); 4530 if(trayOwner == None) 4531 return; 4532 //throw new Exception("No notification area found"); 4533 4534 Visual* v = cast(Visual*) CopyFromParent; 4535 /+ 4536 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 4537 if(visualProp !is null) { 4538 c_ulong[] info = cast(c_ulong[]) visualProp; 4539 if(info.length == 1) { 4540 auto vid = info[0]; 4541 int returned; 4542 XVisualInfo t; 4543 t.visualid = vid; 4544 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 4545 if(got !is null) { 4546 if(returned == 1) { 4547 v = got.visual; 4548 import std.stdio; 4549 writeln("using special visual ", *got); 4550 } 4551 XFree(got); 4552 } 4553 } 4554 } 4555 +/ 4556 4557 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null); 4558 assert(nativeWindow); 4559 4560 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4561 4562 nativeHandle = nativeWindow; 4563 4564 ///+ 4565 arch_ulong[2] info; 4566 info[0] = 0; 4567 info[1] = 1; 4568 4569 string title = this.name is null ? "simpledisplay.d program" : this.name; 4570 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4571 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4572 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4573 4574 XChangeProperty( 4575 display, 4576 nativeWindow, 4577 GetAtom!("_XEMBED_INFO", true)(display), 4578 GetAtom!("_XEMBED_INFO", true)(display), 4579 32 /* bits */, 4580 0 /*PropModeReplace*/, 4581 info.ptr, 4582 2); 4583 4584 import core.sys.posix.unistd; 4585 arch_ulong pid = getpid(); 4586 4587 XChangeProperty( 4588 display, 4589 nativeWindow, 4590 GetAtom!("_NET_WM_PID", true)(display), 4591 XA_CARDINAL, 4592 32 /* bits */, 4593 0 /*PropModeReplace*/, 4594 &pid, 4595 1); 4596 4597 updateNetWmIcon(); 4598 4599 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4600 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4601 XClassHint klass; 4602 XWMHints wh; 4603 XSizeHints size; 4604 klass.res_name = sdpyWindowClassStr; 4605 klass.res_class = sdpyWindowClassStr; 4606 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4607 } 4608 4609 // believe it or not, THIS is what xfce needed for the 9999 issue 4610 XSizeHints sh; 4611 c_long spr; 4612 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4613 sh.flags |= PMaxSize | PMinSize; 4614 // FIXME maybe nicer resizing 4615 sh.min_width = 16; 4616 sh.min_height = 16; 4617 sh.max_width = 16; 4618 sh.max_height = 16; 4619 XSetWMNormalHints(display, nativeWindow, &sh); 4620 4621 4622 //+/ 4623 4624 4625 XSelectInput(display, nativeWindow, 4626 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 4627 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 4628 4629 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 4630 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 4631 active = true; 4632 } 4633 4634 void updateNetWmIcon() { 4635 if(img is null) return; 4636 auto display = XDisplayConnection.get; 4637 // FIXME: ensure this is correct 4638 arch_ulong[] buffer; 4639 auto imgMi = img.toTrueColorImage; 4640 buffer ~= imgMi.width; 4641 buffer ~= imgMi.height; 4642 foreach(c; imgMi.imageData.colors) { 4643 arch_ulong b; 4644 b |= c.a << 24; 4645 b |= c.r << 16; 4646 b |= c.g << 8; 4647 b |= c.b; 4648 buffer ~= b; 4649 } 4650 4651 XChangeProperty( 4652 display, 4653 nativeHandle, 4654 GetAtom!"_NET_WM_ICON"(display), 4655 GetAtom!"CARDINAL"(display), 4656 32 /* bits */, 4657 0 /*PropModeReplace*/, 4658 buffer.ptr, 4659 cast(int) buffer.length); 4660 } 4661 4662 4663 4664 private SimpleWindow balloon; 4665 version(with_timer) 4666 private Timer timer; 4667 4668 private Window nativeHandle; 4669 private Pixmap clippixmap = None; 4670 private int width = 16; 4671 private int height = 16; 4672 private bool active = false; 4673 4674 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 4675 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 4676 void delegate () onLeave; /// X11 only. 4677 4678 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 4679 4680 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 4681 void getWindowRect (out int x, out int y, out int width, out int height) { 4682 if (!active) { width = 1; height = 1; return; } // 1: just in case 4683 Window dummyw; 4684 auto dpy = XDisplayConnection.get; 4685 //XWindowAttributes xwa; 4686 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 4687 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 4688 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 4689 width = this.width; 4690 height = this.height; 4691 } 4692 } 4693 4694 /+ 4695 What I actually want from this: 4696 4697 * set / change: icon, tooltip 4698 * handle: mouse click, right click 4699 * show: notification bubble. 4700 +/ 4701 4702 version(Windows) { 4703 WindowsIcon win32Icon; 4704 HWND hwnd; 4705 4706 NOTIFYICONDATAW data; 4707 4708 NativeEventHandler getNativeEventHandler() { 4709 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 4710 if(msg == WM_USER) { 4711 auto event = LOWORD(lParam); 4712 auto iconId = HIWORD(lParam); 4713 //auto x = GET_X_LPARAM(wParam); 4714 //auto y = GET_Y_LPARAM(wParam); 4715 switch(event) { 4716 case WM_LBUTTONDOWN: 4717 onClick()(MouseButton.left); 4718 break; 4719 case WM_RBUTTONDOWN: 4720 onClick()(MouseButton.right); 4721 break; 4722 case WM_MBUTTONDOWN: 4723 onClick()(MouseButton.middle); 4724 break; 4725 case WM_MOUSEMOVE: 4726 // sent, we could use it. 4727 break; 4728 case WM_MOUSEWHEEL: 4729 // NOT SENT 4730 break; 4731 //case NIN_KEYSELECT: 4732 //case NIN_SELECT: 4733 //break; 4734 default: {} 4735 } 4736 } 4737 return 0; 4738 }; 4739 } 4740 4741 enum NIF_SHOWTIP = 0x00000080; 4742 4743 private static struct NOTIFYICONDATAW { 4744 DWORD cbSize; 4745 HWND hWnd; 4746 UINT uID; 4747 UINT uFlags; 4748 UINT uCallbackMessage; 4749 HICON hIcon; 4750 WCHAR[128] szTip; 4751 DWORD dwState; 4752 DWORD dwStateMask; 4753 WCHAR[256] szInfo; 4754 union { 4755 UINT uTimeout; 4756 UINT uVersion; 4757 } 4758 WCHAR[64] szInfoTitle; 4759 DWORD dwInfoFlags; 4760 GUID guidItem; 4761 HICON hBalloonIcon; 4762 } 4763 4764 } 4765 4766 /++ 4767 Note that on Windows, only left, right, and middle buttons are sent. 4768 Mouse wheel buttons are NOT set, so don't rely on those events if your 4769 program is meant to be used on Windows too. 4770 +/ 4771 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 4772 // The canonical constructor for Windows needs the MemoryImage, so it is here, 4773 // but on X, we need an Image, so its canonical ctor is there. They should 4774 // forward to each other though. 4775 version(X11) { 4776 this.name = name; 4777 this.onClick = onClick; 4778 createXWin(); 4779 this.icon = icon; 4780 } else version(Windows) { 4781 this.onClick = onClick; 4782 this.win32Icon = new WindowsIcon(icon); 4783 4784 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 4785 4786 static bool registered = false; 4787 if(!registered) { 4788 WNDCLASSEX wc; 4789 wc.cbSize = wc.sizeof; 4790 wc.hInstance = hInstance; 4791 wc.lpfnWndProc = &WndProc; 4792 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 4793 if(!RegisterClassExW(&wc)) 4794 throw new WindowsApiException("RegisterClass"); 4795 registered = true; 4796 } 4797 4798 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 4799 if(hwnd is null) 4800 throw new Exception("CreateWindow"); 4801 4802 data.cbSize = data.sizeof; 4803 data.hWnd = hwnd; 4804 data.uID = cast(uint) cast(void*) this; 4805 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 4806 // NIF_INFO means show balloon 4807 data.uCallbackMessage = WM_USER; 4808 data.hIcon = this.win32Icon.hIcon; 4809 data.szTip = ""; // FIXME 4810 data.dwState = 0; // NIS_HIDDEN; // windows vista 4811 data.dwStateMask = NIS_HIDDEN; // windows vista 4812 4813 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 4814 4815 4816 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 4817 4818 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 4819 } else version(OSXCocoa) { 4820 throw new NotYetImplementedException(); 4821 } else static assert(0); 4822 } 4823 4824 /// ditto 4825 this(string name, Image icon, void delegate(MouseButton button) onClick) { 4826 version(X11) { 4827 this.onClick = onClick; 4828 this.name = name; 4829 createXWin(); 4830 this.icon = icon; 4831 } else version(Windows) { 4832 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 4833 } else version(OSXCocoa) { 4834 throw new NotYetImplementedException(); 4835 } else static assert(0); 4836 } 4837 4838 version(X11) { 4839 /++ 4840 X-specific extension (for now at least) 4841 +/ 4842 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 4843 this.onClickEx = onClickEx; 4844 createXWin(); 4845 if (icon !is null) this.icon = icon; 4846 } 4847 4848 /// ditto 4849 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 4850 this.onClickEx = onClickEx; 4851 createXWin(); 4852 this.icon = icon; 4853 } 4854 } 4855 4856 private void delegate (MouseButton button) onClick_; 4857 4858 /// 4859 @property final void delegate(MouseButton) onClick() { 4860 if(onClick_ is null) 4861 onClick_ = delegate void(MouseButton) {}; 4862 return onClick_; 4863 } 4864 4865 /// ditto 4866 @property final void onClick(void delegate(MouseButton) handler) { 4867 // I made this a property setter so we can wrap smaller arg 4868 // delegates and just forward all to onClickEx or something. 4869 onClick_ = handler; 4870 } 4871 4872 4873 string name_; 4874 @property void name(string n) { 4875 name_ = n; 4876 } 4877 4878 @property string name() { 4879 return name_; 4880 } 4881 4882 private MemoryImage originalMemoryImage; 4883 4884 /// 4885 @property void icon(MemoryImage i) { 4886 version(X11) { 4887 this.originalMemoryImage = i; 4888 if (!active) return; 4889 if (i !is null) { 4890 this.img = Image.fromMemoryImage(i); 4891 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 4892 //import std.stdio; writeln("using pixmap ", clippixmap); 4893 updateNetWmIcon(); 4894 redraw(); 4895 } else { 4896 if (this.img !is null) { 4897 this.img = null; 4898 redraw(); 4899 } 4900 } 4901 } else version(Windows) { 4902 this.win32Icon = new WindowsIcon(i); 4903 4904 data.uFlags = NIF_ICON; 4905 data.hIcon = this.win32Icon.hIcon; 4906 4907 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4908 } else version(OSXCocoa) { 4909 throw new NotYetImplementedException(); 4910 } else static assert(0); 4911 } 4912 4913 /// ditto 4914 @property void icon (Image i) { 4915 version(X11) { 4916 if (!active) return; 4917 if (i !is img) { 4918 originalMemoryImage = null; 4919 img = i; 4920 redraw(); 4921 } 4922 } else version(Windows) { 4923 this.icon(i is null ? null : i.toTrueColorImage()); 4924 } else version(OSXCocoa) { 4925 throw new NotYetImplementedException(); 4926 } else static assert(0); 4927 } 4928 4929 /++ 4930 Shows a balloon notification. You can only show one balloon at a time, if you call 4931 it twice while one is already up, the first balloon will be replaced. 4932 4933 4934 The user is free to block notifications and they will automatically disappear after 4935 a timeout period. 4936 4937 Params: 4938 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 4939 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 4940 icon = the icon to display with the notification. If null, it uses your existing icon. 4941 onclick = delegate called if the user clicks the balloon. (not yet implemented) 4942 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 4943 +/ 4944 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 4945 bool useCustom = true; 4946 version(libnotify) { 4947 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 4948 try { 4949 if(!active) return; 4950 4951 if(libnotify is null) { 4952 libnotify = new C_DynamicLibrary("libnotify.so"); 4953 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 4954 } 4955 4956 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 4957 4958 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 4959 4960 if(onclick) { 4961 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 4962 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); 4963 libnotify_action_delegates_count++; 4964 } 4965 4966 // FIXME icon 4967 4968 // set hint image-data 4969 // set default action for onclick 4970 4971 void* error; 4972 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 4973 4974 useCustom = false; 4975 } catch(Exception e) { 4976 4977 } 4978 } 4979 4980 version(X11) { 4981 if(useCustom) { 4982 if(!active) return; 4983 if(balloon) { 4984 hideBalloon(); 4985 } 4986 // I know there are two specs for this, but one is never 4987 // implemented by any window manager I have ever seen, and 4988 // the other is a bloated mess and too complicated for simpledisplay... 4989 // so doing my own little window instead. 4990 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 4991 4992 int x, y, width, height; 4993 getWindowRect(x, y, width, height); 4994 4995 int bx = x - balloon.width; 4996 int by = y - balloon.height; 4997 if(bx < 0) 4998 bx = x + width + balloon.width; 4999 if(by < 0) 5000 by = y + height; 5001 5002 // just in case, make sure it is actually on scren 5003 if(bx < 0) 5004 bx = 0; 5005 if(by < 0) 5006 by = 0; 5007 5008 balloon.move(bx, by); 5009 auto painter = balloon.draw(); 5010 painter.fillColor = Color(220, 220, 220); 5011 painter.outlineColor = Color.black; 5012 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5013 auto iconWidth = icon is null ? 0 : icon.width; 5014 if(icon) 5015 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5016 iconWidth += 6; // margin around the icon 5017 5018 // draw a close button 5019 painter.outlineColor = Color(44, 44, 44); 5020 painter.fillColor = Color(255, 255, 255); 5021 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5022 painter.pen = Pen(Color.black, 3); 5023 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5024 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5025 painter.pen = Pen(Color.black, 1); 5026 painter.fillColor = Color(220, 220, 220); 5027 5028 // Draw the title and message 5029 painter.drawText(Point(4 + iconWidth, 4), title); 5030 painter.drawLine( 5031 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5032 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5033 ); 5034 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5035 5036 balloon.setEventHandlers( 5037 (MouseEvent ev) { 5038 if(ev.type == MouseEventType.buttonPressed) { 5039 if(ev.x > balloon.width - 16 && ev.y < 16) 5040 hideBalloon(); 5041 else if(onclick) 5042 onclick(); 5043 } 5044 } 5045 ); 5046 balloon.show(); 5047 5048 version(with_timer) 5049 timer = new Timer(timeout, &hideBalloon); 5050 else {} // FIXME 5051 } 5052 } else version(Windows) { 5053 enum NIF_INFO = 0x00000010; 5054 5055 data.uFlags = NIF_INFO; 5056 5057 // FIXME: go back to the last valid unicode code point 5058 if(title.length > 40) 5059 title = title[0 .. 40]; 5060 if(message.length > 220) 5061 message = message[0 .. 220]; 5062 5063 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5064 enum NIIF_LARGE_ICON = 0x00000020; 5065 enum NIIF_NOSOUND = 0x00000010; 5066 enum NIIF_USER = 0x00000004; 5067 enum NIIF_ERROR = 0x00000003; 5068 enum NIIF_WARNING = 0x00000002; 5069 enum NIIF_INFO = 0x00000001; 5070 enum NIIF_NONE = 0; 5071 5072 WCharzBuffer t = WCharzBuffer(title); 5073 WCharzBuffer m = WCharzBuffer(message); 5074 5075 t.copyInto(data.szInfoTitle); 5076 m.copyInto(data.szInfo); 5077 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5078 5079 if(icon !is null) { 5080 auto i = new WindowsIcon(icon); 5081 data.hBalloonIcon = i.hIcon; 5082 data.dwInfoFlags |= NIIF_USER; 5083 } 5084 5085 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5086 } else version(OSXCocoa) { 5087 throw new NotYetImplementedException(); 5088 } else static assert(0); 5089 } 5090 5091 /// 5092 //version(Windows) 5093 void show() { 5094 version(X11) { 5095 if(!hidden) 5096 return; 5097 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5098 hidden = false; 5099 } else version(Windows) { 5100 data.uFlags = NIF_STATE; 5101 data.dwState = 0; // NIS_HIDDEN; // windows vista 5102 data.dwStateMask = NIS_HIDDEN; // windows vista 5103 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5104 } else version(OSXCocoa) { 5105 throw new NotYetImplementedException(); 5106 } else static assert(0); 5107 } 5108 5109 version(X11) 5110 bool hidden = false; 5111 5112 /// 5113 //version(Windows) 5114 void hide() { 5115 version(X11) { 5116 if(hidden) 5117 return; 5118 hidden = true; 5119 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5120 } else version(Windows) { 5121 data.uFlags = NIF_STATE; 5122 data.dwState = NIS_HIDDEN; // windows vista 5123 data.dwStateMask = NIS_HIDDEN; // windows vista 5124 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5125 } else version(OSXCocoa) { 5126 throw new NotYetImplementedException(); 5127 } else static assert(0); 5128 } 5129 5130 /// 5131 void close () { 5132 version(X11) { 5133 if (active) { 5134 active = false; // event handler will set this too, but meh 5135 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5136 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5137 flushGui(); 5138 } 5139 } else version(Windows) { 5140 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5141 } else version(OSXCocoa) { 5142 throw new NotYetImplementedException(); 5143 } else static assert(0); 5144 } 5145 5146 ~this() { 5147 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5148 version(X11) 5149 if(clippixmap != None) 5150 XFreePixmap(XDisplayConnection.get, clippixmap); 5151 close(); 5152 } 5153 } 5154 5155 version(X11) 5156 /// Call `XFreePixmap` on the return value. 5157 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5158 char[] data = new char[](i.width * i.height / 8 + 2); 5159 data[] = 0; 5160 5161 int bitOffset = 0; 5162 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5163 ubyte v = c.a > 128 ? 1 : 0; 5164 data[bitOffset / 8] |= v << (bitOffset%8); 5165 bitOffset++; 5166 } 5167 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5168 return handle; 5169 } 5170 5171 5172 // basic functions to make timers 5173 /** 5174 A timer that will trigger your function on a given interval. 5175 5176 5177 You create a timer with an interval and a callback. It will continue 5178 to fire on the interval until it is destroyed. 5179 5180 There are currently no one-off timers (instead, just create one and 5181 destroy it when it is triggered) nor are there pause/resume functions - 5182 the timer must again be destroyed and recreated if you want to pause it. 5183 5184 auto timer = new Timer(50, { it happened!; }); 5185 timer.destroy(); 5186 5187 Timers can only be expected to fire when the event loop is running and only 5188 once per iteration through the event loop. 5189 5190 History: 5191 Prior to December 9, 2020, a timer pulse set too high with a handler too 5192 slow could lock up the event loop. It now guarantees other things will 5193 get a chance to run between timer calls, even if that means not keeping up 5194 with the requested interval. 5195 */ 5196 version(with_timer) { 5197 class Timer { 5198 // FIXME: needs pause and unpause 5199 // FIXME: I might add overloads for ones that take a count of 5200 // how many elapsed since last time (on Windows, it will divide 5201 // the ticks thing given, on Linux it is just available) and 5202 // maybe one that takes an instance of the Timer itself too 5203 /// Create a timer with a callback when it triggers. 5204 this(int intervalInMilliseconds, void delegate() onPulse) { 5205 assert(onPulse !is null); 5206 5207 this.intervalInMilliseconds = intervalInMilliseconds; 5208 this.onPulse = onPulse; 5209 5210 version(Windows) { 5211 /* 5212 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5213 if(handle == 0) 5214 throw new Exception("SetTimer fail"); 5215 */ 5216 5217 // thanks to Archival 998 for the WaitableTimer blocks 5218 handle = CreateWaitableTimer(null, false, null); 5219 long initialTime = -intervalInMilliseconds; 5220 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5221 throw new Exception("SetWaitableTimer Failed"); 5222 5223 mapping[handle] = this; 5224 5225 } else version(linux) { 5226 static import ep = core.sys.linux.epoll; 5227 5228 import core.sys.linux.timerfd; 5229 5230 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5231 if(fd == -1) 5232 throw new Exception("timer create failed"); 5233 5234 mapping[fd] = this; 5235 5236 itimerspec value; 5237 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5238 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5239 5240 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5241 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5242 5243 if(timerfd_settime(fd, 0, &value, null) == -1) 5244 throw new Exception("couldn't make pulse timer"); 5245 5246 version(with_eventloop) { 5247 import arsd.eventloop; 5248 addFileEventListeners(fd, &trigger, null, null); 5249 } else { 5250 prepareEventLoop(); 5251 5252 ep.epoll_event ev = void; 5253 ev.events = ep.EPOLLIN; 5254 ev.data.fd = fd; 5255 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5256 } 5257 } else featureNotImplemented(); 5258 } 5259 5260 private int intervalInMilliseconds; 5261 5262 // just cuz I sometimes call it this. 5263 alias dispose = destroy; 5264 5265 /// Stop and destroy the timer object. 5266 void destroy() { 5267 version(Windows) { 5268 staticDestroy(handle); 5269 handle = null; 5270 } else version(linux) { 5271 staticDestroy(fd); 5272 fd = -1; 5273 } else featureNotImplemented(); 5274 } 5275 5276 version(Windows) 5277 static void staticDestroy(HANDLE handle) { 5278 if(handle) { 5279 // KillTimer(null, handle); 5280 CancelWaitableTimer(cast(void*)handle); 5281 mapping.remove(handle); 5282 CloseHandle(handle); 5283 } 5284 } 5285 else version(linux) 5286 static void staticDestroy(int fd) { 5287 if(fd != -1) { 5288 import unix = core.sys.posix.unistd; 5289 static import ep = core.sys.linux.epoll; 5290 5291 version(with_eventloop) { 5292 import arsd.eventloop; 5293 removeFileEventListeners(fd); 5294 } else { 5295 ep.epoll_event ev = void; 5296 ev.events = ep.EPOLLIN; 5297 ev.data.fd = fd; 5298 5299 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5300 } 5301 unix.close(fd); 5302 mapping.remove(fd); 5303 } 5304 } 5305 5306 ~this() { 5307 version(Windows) { if(handle) 5308 cleanupQueue.queue!staticDestroy(handle); 5309 } else version(linux) { if(fd != -1) 5310 cleanupQueue.queue!staticDestroy(fd); 5311 } 5312 } 5313 5314 5315 void changeTime(int intervalInMilliseconds) 5316 { 5317 this.intervalInMilliseconds = intervalInMilliseconds; 5318 version(Windows) 5319 { 5320 if(handle) 5321 { 5322 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5323 long initialTime = -intervalInMilliseconds; 5324 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5325 throw new Exception("couldn't change pulse timer"); 5326 } 5327 } 5328 } 5329 5330 5331 private: 5332 5333 void delegate() onPulse; 5334 5335 int lastEventLoopRoundTriggered; 5336 5337 void trigger() { 5338 version(linux) { 5339 import unix = core.sys.posix.unistd; 5340 long val; 5341 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 5342 } else version(Windows) { 5343 if(this.lastEventLoopRoundTriggered == eventLoopRound) 5344 return; // never try to actually run faster than the event loop 5345 lastEventLoopRoundTriggered = eventLoopRound; 5346 } else featureNotImplemented(); 5347 5348 onPulse(); 5349 } 5350 5351 version(Windows) 5352 void rearm() { 5353 5354 } 5355 5356 version(Windows) 5357 extern(Windows) 5358 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 5359 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 5360 if(Timer* t = timer in mapping) { 5361 try 5362 (*t).trigger(); 5363 catch(Exception e) { sdpy_abort(e); assert(0); } 5364 } 5365 } 5366 5367 version(Windows) { 5368 //UINT_PTR handle; 5369 //static Timer[UINT_PTR] mapping; 5370 HANDLE handle; 5371 __gshared Timer[HANDLE] mapping; 5372 } else version(linux) { 5373 int fd = -1; 5374 __gshared Timer[int] mapping; 5375 } else static assert(0, "timer not supported"); 5376 } 5377 } 5378 5379 version(Windows) 5380 private int eventLoopRound; 5381 5382 version(Windows) 5383 /// 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 5384 class WindowsHandleReader { 5385 /// 5386 this(void delegate() onReady, HANDLE handle) { 5387 this.onReady = onReady; 5388 this.handle = handle; 5389 5390 mapping[handle] = this; 5391 5392 enable(); 5393 } 5394 5395 /// 5396 void enable() { 5397 auto el = EventLoop.get().impl; 5398 el.handles ~= handle; 5399 } 5400 5401 /// 5402 void disable() { 5403 auto el = EventLoop.get().impl; 5404 for(int i = 0; i < el.handles.length; i++) { 5405 if(el.handles[i] is handle) { 5406 el.handles[i] = el.handles[$-1]; 5407 el.handles = el.handles[0 .. $-1]; 5408 return; 5409 } 5410 } 5411 } 5412 5413 void dispose() { 5414 disable(); 5415 if(handle) 5416 mapping.remove(handle); 5417 handle = null; 5418 } 5419 5420 void ready() { 5421 if(onReady) 5422 onReady(); 5423 } 5424 5425 HANDLE handle; 5426 void delegate() onReady; 5427 5428 __gshared WindowsHandleReader[HANDLE] mapping; 5429 } 5430 5431 version(Posix) 5432 /// Lets you add files to the event loop for reading. Use at your own risk. 5433 class PosixFdReader { 5434 /// 5435 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5436 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 5437 } 5438 5439 /// 5440 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5441 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 5442 } 5443 5444 /// 5445 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5446 this.onReady = onReady; 5447 this.fd = fd; 5448 this.captureWrites = captureWrites; 5449 this.captureReads = captureReads; 5450 5451 mapping[fd] = this; 5452 5453 version(with_eventloop) { 5454 import arsd.eventloop; 5455 addFileEventListeners(fd, &readyel); 5456 } else { 5457 enable(); 5458 } 5459 } 5460 5461 bool captureReads; 5462 bool captureWrites; 5463 5464 version(with_eventloop) {} else 5465 /// 5466 void enable() { 5467 prepareEventLoop(); 5468 5469 enabled = true; 5470 5471 version(linux) { 5472 static import ep = core.sys.linux.epoll; 5473 ep.epoll_event ev = void; 5474 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5475 //import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites); 5476 ev.data.fd = fd; 5477 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5478 } else { 5479 5480 } 5481 } 5482 5483 version(with_eventloop) {} else 5484 /// 5485 void disable() { 5486 prepareEventLoop(); 5487 5488 enabled = false; 5489 5490 version(linux) { 5491 static import ep = core.sys.linux.epoll; 5492 ep.epoll_event ev = void; 5493 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5494 //import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites); 5495 ev.data.fd = fd; 5496 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5497 } 5498 } 5499 5500 version(with_eventloop) {} else 5501 /// 5502 void dispose() { 5503 if(enabled) 5504 disable(); 5505 if(fd != -1) 5506 mapping.remove(fd); 5507 fd = -1; 5508 } 5509 5510 void delegate(int, bool, bool) onReady; 5511 5512 version(with_eventloop) 5513 void readyel() { 5514 onReady(fd, true, true); 5515 } 5516 5517 void ready(uint flags) { 5518 version(linux) { 5519 static import ep = core.sys.linux.epoll; 5520 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 5521 } else { 5522 import core.sys.posix.poll; 5523 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 5524 } 5525 } 5526 5527 void hup(uint flags) { 5528 if(onHup) 5529 onHup(); 5530 } 5531 5532 void delegate() onHup; 5533 5534 int fd = -1; 5535 private bool enabled; 5536 __gshared PosixFdReader[int] mapping; 5537 } 5538 5539 // basic functions to access the clipboard 5540 /+ 5541 5542 5543 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 5544 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 5545 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5546 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 5547 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 5548 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5549 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 5550 5551 +/ 5552 5553 /++ 5554 this does a delegate because it is actually an async call on X... 5555 the receiver may never be called if the clipboard is empty or unavailable 5556 gets plain text from the clipboard. 5557 +/ 5558 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 5559 version(Windows) { 5560 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5561 if(OpenClipboard(hwndOwner) == 0) 5562 throw new Exception("OpenClipboard"); 5563 scope(exit) 5564 CloseClipboard(); 5565 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 5566 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 5567 5568 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 5569 scope(exit) 5570 GlobalUnlock(dataHandle); 5571 5572 // FIXME: CR/LF conversions 5573 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 5574 int len = 0; 5575 auto d = data; 5576 while(*d) { 5577 d++; 5578 len++; 5579 } 5580 string s; 5581 s.reserve(len); 5582 foreach(dchar ch; data[0 .. len]) { 5583 s ~= ch; 5584 } 5585 receiver(s); 5586 } 5587 } 5588 } else version(X11) { 5589 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5590 } else version(OSXCocoa) { 5591 throw new NotYetImplementedException(); 5592 } else static assert(0); 5593 } 5594 5595 // FIXME: a clipboard listener might be cool btw 5596 5597 /++ 5598 this does a delegate because it is actually an async call on X... 5599 the receiver may never be called if the clipboard is empty or unavailable 5600 gets image from the clipboard. 5601 5602 templated because it introduces an optional dependency on arsd.bmp 5603 +/ 5604 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 5605 version(Windows) { 5606 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5607 if(OpenClipboard(hwndOwner) == 0) 5608 throw new Exception("OpenClipboard"); 5609 scope(exit) 5610 CloseClipboard(); 5611 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 5612 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 5613 scope(exit) 5614 GlobalUnlock(dataHandle); 5615 5616 auto len = GlobalSize(dataHandle); 5617 5618 import arsd.bmp; 5619 auto img = readBmp(data[0 .. len], false); 5620 receiver(img); 5621 } 5622 } 5623 } else version(X11) { 5624 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5625 } else version(OSXCocoa) { 5626 throw new NotYetImplementedException(); 5627 } else static assert(0); 5628 } 5629 5630 version(Windows) 5631 struct WCharzBuffer { 5632 wchar[] buffer; 5633 wchar[256] staticBuffer = void; 5634 5635 size_t length() { 5636 return buffer.length; 5637 } 5638 5639 wchar* ptr() { 5640 return buffer.ptr; 5641 } 5642 5643 wchar[] slice() { 5644 return buffer; 5645 } 5646 5647 void copyInto(R)(ref R r) { 5648 static if(is(R == wchar[N], size_t N)) { 5649 r[0 .. this.length] = slice[]; 5650 r[this.length] = 0; 5651 } else static assert(0, "can only copy into wchar[n], not " ~ R.stringof); 5652 } 5653 5654 /++ 5655 conversionFlags = [WindowsStringConversionFlags] 5656 +/ 5657 this(in char[] data, int conversionFlags = 0) { 5658 conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name 5659 auto sz = sizeOfConvertedWstring(data, conversionFlags); 5660 if(sz > staticBuffer.length) 5661 buffer = new wchar[](sz); 5662 else 5663 buffer = staticBuffer[]; 5664 5665 buffer = makeWindowsString(data, buffer, conversionFlags); 5666 } 5667 } 5668 5669 version(Windows) 5670 int sizeOfConvertedWstring(in char[] s, int conversionFlags) { 5671 int size = 0; 5672 5673 if(conversionFlags & WindowsStringConversionFlags.convertNewLines) { 5674 // need to convert line endings, which means the length will get bigger. 5675 5676 // BTW I betcha this could be faster with some simd stuff. 5677 char last; 5678 foreach(char ch; s) { 5679 if(ch == 10 && last != 13) 5680 size++; // will add a 13 before it... 5681 size++; 5682 last = ch; 5683 } 5684 } else { 5685 // no conversion necessary, just estimate based on length 5686 /* 5687 I don't think there's any string with a longer length 5688 in code units when encoded in UTF-16 than it has in UTF-8. 5689 This will probably over allocate, but that's OK. 5690 */ 5691 size = cast(int) s.length; 5692 } 5693 5694 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) 5695 size++; 5696 5697 return size; 5698 } 5699 5700 version(Windows) 5701 enum WindowsStringConversionFlags : int { 5702 zeroTerminate = 1, 5703 convertNewLines = 2, 5704 } 5705 5706 version(Windows) 5707 class WindowsApiException : Exception { 5708 char[256] buffer; 5709 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 5710 assert(msg.length < 100); 5711 5712 auto error = GetLastError(); 5713 buffer[0 .. msg.length] = msg; 5714 buffer[msg.length] = ' '; 5715 5716 int pos = cast(int) msg.length + 1; 5717 5718 if(error == 0) 5719 buffer[pos++] = '0'; 5720 else { 5721 5722 auto ec = error; 5723 auto init = pos; 5724 while(ec) { 5725 buffer[pos++] = (ec % 10) + '0'; 5726 ec /= 10; 5727 } 5728 5729 buffer[pos++] = ' '; 5730 5731 size_t size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, null, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &(buffer[pos]), cast(DWORD) buffer.length - pos, null); 5732 5733 pos += size; 5734 } 5735 5736 5737 super(cast(string) buffer[0 .. pos], file, line, next); 5738 } 5739 } 5740 5741 class ErrnoApiException : Exception { 5742 char[256] buffer; 5743 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 5744 assert(msg.length < 100); 5745 5746 import core.stdc.errno; 5747 auto error = errno; 5748 buffer[0 .. msg.length] = msg; 5749 buffer[msg.length] = ' '; 5750 5751 int pos = cast(int) msg.length + 1; 5752 5753 if(error == 0) 5754 buffer[pos++] = '0'; 5755 else { 5756 auto init = pos; 5757 while(error) { 5758 buffer[pos++] = (error % 10) + '0'; 5759 error /= 10; 5760 } 5761 for(int i = 0; i < (pos - init) / 2; i++) { 5762 char c = buffer[i + init]; 5763 buffer[i + init] = buffer[pos - (i + init) - 1]; 5764 buffer[pos - (i + init) - 1] = c; 5765 } 5766 } 5767 5768 5769 super(cast(string) buffer[0 .. pos], file, line, next); 5770 } 5771 5772 } 5773 5774 version(Windows) 5775 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) { 5776 if(str.length == 0) 5777 return null; 5778 5779 int pos = 0; 5780 dchar last; 5781 foreach(dchar c; str) { 5782 if(c <= 0xFFFF) { 5783 if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13) 5784 buffer[pos++] = 13; 5785 buffer[pos++] = cast(wchar) c; 5786 } else if(c <= 0x10FFFF) { 5787 buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 5788 buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); 5789 } 5790 5791 last = c; 5792 } 5793 5794 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) { 5795 buffer[pos] = 0; 5796 } 5797 5798 return buffer[0 .. pos]; 5799 } 5800 5801 version(Windows) 5802 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) { 5803 if(str.length == 0) 5804 return null; 5805 5806 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null); 5807 if(got == 0) { 5808 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 5809 throw new Exception("not enough buffer"); 5810 else 5811 throw new Exception("conversion"); // FIXME: GetLastError 5812 } 5813 return buffer[0 .. got]; 5814 } 5815 5816 version(Windows) 5817 string makeUtf8StringFromWindowsString(in wchar[] str) { 5818 char[] buffer; 5819 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null); 5820 buffer.length = got; 5821 5822 // it is unique because we just allocated it above! 5823 return cast(string) makeUtf8StringFromWindowsString(str, buffer); 5824 } 5825 5826 version(Windows) 5827 string makeUtf8StringFromWindowsString(wchar* str) { 5828 char[] buffer; 5829 auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null); 5830 buffer.length = got; 5831 5832 got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null); 5833 if(got == 0) { 5834 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 5835 throw new Exception("not enough buffer"); 5836 else 5837 throw new Exception("conversion"); // FIXME: GetLastError 5838 } 5839 return cast(string) buffer[0 .. got]; 5840 } 5841 5842 int findIndexOfZero(in wchar[] str) { 5843 foreach(idx, wchar ch; str) 5844 if(ch == 0) 5845 return cast(int) idx; 5846 return cast(int) str.length; 5847 } 5848 int findIndexOfZero(in char[] str) { 5849 foreach(idx, char ch; str) 5850 if(ch == 0) 5851 return cast(int) idx; 5852 return cast(int) str.length; 5853 } 5854 5855 /// Copies some text to the clipboard. 5856 void setClipboardText(SimpleWindow clipboardOwner, string text) { 5857 assert(clipboardOwner !is null); 5858 version(Windows) { 5859 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5860 throw new Exception("OpenClipboard"); 5861 scope(exit) 5862 CloseClipboard(); 5863 EmptyClipboard(); 5864 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5865 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 5866 if(handle is null) throw new Exception("GlobalAlloc"); 5867 if(auto data = cast(wchar*) GlobalLock(handle)) { 5868 auto slice = data[0 .. sz]; 5869 scope(failure) 5870 GlobalUnlock(handle); 5871 5872 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5873 5874 GlobalUnlock(handle); 5875 SetClipboardData(CF_UNICODETEXT, handle); 5876 } 5877 } else version(X11) { 5878 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 5879 } else version(OSXCocoa) { 5880 throw new NotYetImplementedException(); 5881 } else static assert(0); 5882 } 5883 5884 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 5885 assert(clipboardOwner !is null); 5886 version(Windows) { 5887 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5888 throw new Exception("OpenClipboard"); 5889 scope(exit) 5890 CloseClipboard(); 5891 EmptyClipboard(); 5892 5893 5894 import arsd.bmp; 5895 ubyte[] mdata; 5896 mdata.reserve(img.width * img.height); 5897 void sink(ubyte b) { 5898 mdata ~= b; 5899 } 5900 writeBmpIndirect(img, &sink, false); 5901 5902 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 5903 if(handle is null) throw new Exception("GlobalAlloc"); 5904 if(auto data = cast(ubyte*) GlobalLock(handle)) { 5905 auto slice = data[0 .. mdata.length]; 5906 scope(failure) 5907 GlobalUnlock(handle); 5908 5909 slice[] = mdata[]; 5910 5911 GlobalUnlock(handle); 5912 SetClipboardData(CF_DIB, handle); 5913 } 5914 } else version(X11) { 5915 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 5916 mixin X11SetSelectionHandler_Basics; 5917 private const(ubyte)[] mdata; 5918 private const(ubyte)[] mdata_original; 5919 this(MemoryImage img) { 5920 import arsd.bmp; 5921 5922 mdata.reserve(img.width * img.height); 5923 void sink(ubyte b) { 5924 mdata ~= b; 5925 } 5926 writeBmpIndirect(img, &sink, true); 5927 5928 mdata_original = mdata; 5929 } 5930 5931 Atom[] availableFormats() { 5932 auto display = XDisplayConnection.get; 5933 return [ 5934 GetAtom!"image/bmp"(display), 5935 GetAtom!"TARGETS"(display) 5936 ]; 5937 } 5938 5939 ubyte[] getData(Atom format, return scope ubyte[] data) { 5940 if(mdata.length < data.length) { 5941 data[0 .. mdata.length] = mdata[]; 5942 auto ret = data[0 .. mdata.length]; 5943 mdata = mdata[$..$]; 5944 return ret; 5945 } else { 5946 data[] = mdata[0 .. data.length]; 5947 mdata = mdata[data.length .. $]; 5948 return data[]; 5949 } 5950 } 5951 5952 void done() { 5953 mdata = mdata_original; 5954 } 5955 } 5956 5957 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 5958 } else version(OSXCocoa) { 5959 throw new NotYetImplementedException(); 5960 } else static assert(0); 5961 } 5962 5963 5964 version(X11) { 5965 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 5966 5967 private Atom*[] interredAtoms; // for discardAndRecreate 5968 5969 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 5970 /// Platform-specific for X11. 5971 /// History: On February 21, 2021, I changed the default value of `create` to be true. 5972 @property Atom GetAtom(string name, bool create = true)(Display* display) { 5973 static Atom a; 5974 if(!a) { 5975 a = XInternAtom(display, name, !create); 5976 interredAtoms ~= &a; 5977 } 5978 if(a == None) 5979 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 5980 return a; 5981 } 5982 5983 /// Platform-specific for X11 - gets atom names as a string. 5984 string getAtomName(Atom atom, Display* display) { 5985 auto got = XGetAtomName(display, atom); 5986 scope(exit) XFree(got); 5987 import core.stdc.string; 5988 string s = got[0 .. strlen(got)].idup; 5989 return s; 5990 } 5991 5992 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 5993 void setPrimarySelection(SimpleWindow window, string text) { 5994 setX11Selection!"PRIMARY"(window, text); 5995 } 5996 5997 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 5998 void setSecondarySelection(SimpleWindow window, string text) { 5999 setX11Selection!"SECONDARY"(window, text); 6000 } 6001 6002 interface X11SetSelectionHandler { 6003 // should include TARGETS right now 6004 Atom[] availableFormats(); 6005 // Return the slice of data you filled, empty slice if done. 6006 // this is to support the incremental thing 6007 ubyte[] getData(Atom format, return scope ubyte[] data); 6008 6009 void done(); 6010 6011 void handleRequest(XEvent); 6012 6013 bool matchesIncr(Window, Atom); 6014 void sendMoreIncr(XPropertyEvent*); 6015 } 6016 6017 mixin template X11SetSelectionHandler_Basics() { 6018 Window incrWindow; 6019 Atom incrAtom; 6020 Atom selectionAtom; 6021 Atom formatAtom; 6022 ubyte[] toSend; 6023 bool matchesIncr(Window w, Atom a) { 6024 return incrAtom && incrAtom == a && w == incrWindow; 6025 } 6026 void sendMoreIncr(XPropertyEvent* event) { 6027 auto display = XDisplayConnection.get; 6028 6029 XChangeProperty (display, 6030 incrWindow, 6031 incrAtom, 6032 formatAtom, 6033 8 /* bits */, PropModeReplace, 6034 toSend.ptr, cast(int) toSend.length); 6035 6036 if(toSend.length != 0) { 6037 toSend = this.getData(formatAtom, toSend[]); 6038 } else { 6039 this.done(); 6040 incrWindow = None; 6041 incrAtom = None; 6042 selectionAtom = None; 6043 formatAtom = None; 6044 toSend = null; 6045 } 6046 } 6047 void handleRequest(XEvent ev) { 6048 6049 auto display = XDisplayConnection.get; 6050 6051 XSelectionRequestEvent* event = &ev.xselectionrequest; 6052 XSelectionEvent selectionEvent; 6053 selectionEvent.type = EventType.SelectionNotify; 6054 selectionEvent.display = event.display; 6055 selectionEvent.requestor = event.requestor; 6056 selectionEvent.selection = event.selection; 6057 selectionEvent.time = event.time; 6058 selectionEvent.target = event.target; 6059 6060 bool supportedType() { 6061 foreach(t; this.availableFormats()) 6062 if(t == event.target) 6063 return true; 6064 return false; 6065 } 6066 6067 if(event.property == None) { 6068 selectionEvent.property = event.target; 6069 6070 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6071 XFlush(display); 6072 } if(event.target == GetAtom!"TARGETS"(display)) { 6073 /* respond with the supported types */ 6074 auto tlist = this.availableFormats(); 6075 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6076 selectionEvent.property = event.property; 6077 6078 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6079 XFlush(display); 6080 } else if(supportedType()) { 6081 auto buffer = new ubyte[](1024 * 64); 6082 auto toSend = this.getData(event.target, buffer[]); 6083 6084 if(toSend.length < 32 * 1024) { 6085 // small enough to send directly... 6086 selectionEvent.property = event.property; 6087 XChangeProperty (display, 6088 selectionEvent.requestor, 6089 selectionEvent.property, 6090 event.target, 6091 8 /* bits */, 0 /* PropModeReplace */, 6092 toSend.ptr, cast(int) toSend.length); 6093 6094 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6095 XFlush(display); 6096 } else { 6097 // large, let's send incrementally 6098 arch_ulong l = toSend.length; 6099 6100 // if I wanted other events from this window don't want to clear that out.... 6101 XWindowAttributes xwa; 6102 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6103 6104 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6105 6106 incrWindow = event.requestor; 6107 incrAtom = event.property; 6108 formatAtom = event.target; 6109 selectionAtom = event.selection; 6110 this.toSend = toSend; 6111 6112 selectionEvent.property = event.property; 6113 XChangeProperty (display, 6114 selectionEvent.requestor, 6115 selectionEvent.property, 6116 GetAtom!"INCR"(display), 6117 32 /* bits */, PropModeReplace, 6118 &l, 1); 6119 6120 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6121 XFlush(display); 6122 } 6123 //if(after) 6124 //after(); 6125 } else { 6126 debug(sdpy_clip) { 6127 import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display)); 6128 } 6129 selectionEvent.property = None; // I don't know how to handle this type... 6130 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6131 XFlush(display); 6132 } 6133 } 6134 } 6135 6136 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6137 mixin X11SetSelectionHandler_Basics; 6138 private const(ubyte)[] text; 6139 private const(ubyte)[] text_original; 6140 this(string text) { 6141 this.text = cast(const ubyte[]) text; 6142 this.text_original = this.text; 6143 } 6144 Atom[] availableFormats() { 6145 auto display = XDisplayConnection.get; 6146 return [ 6147 GetAtom!"UTF8_STRING"(display), 6148 GetAtom!"text/plain"(display), 6149 XA_STRING, 6150 GetAtom!"TARGETS"(display) 6151 ]; 6152 } 6153 6154 ubyte[] getData(Atom format, return scope ubyte[] data) { 6155 if(text.length < data.length) { 6156 data[0 .. text.length] = text[]; 6157 return data[0 .. text.length]; 6158 } else { 6159 data[] = text[0 .. data.length]; 6160 text = text[data.length .. $]; 6161 return data[]; 6162 } 6163 } 6164 6165 void done() { 6166 text = text_original; 6167 } 6168 } 6169 6170 /// 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?!) 6171 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6172 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6173 } 6174 6175 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6176 assert(window !is null); 6177 6178 auto display = XDisplayConnection.get(); 6179 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6180 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6181 else Atom a = GetAtom!atomName(display); 6182 6183 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6184 6185 window.impl.setSelectionHandlers[a] = data; 6186 } 6187 6188 /// 6189 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6190 getX11Selection!"PRIMARY"(window, handler); 6191 } 6192 6193 // added July 28, 2020 6194 // undocumented as experimental tho 6195 interface X11GetSelectionHandler { 6196 void handleData(Atom target, in ubyte[] data); 6197 Atom findBestFormat(Atom[] answer); 6198 6199 void prepareIncremental(Window, Atom); 6200 bool matchesIncr(Window, Atom); 6201 void handleIncrData(Atom, in ubyte[] data); 6202 } 6203 6204 mixin template X11GetSelectionHandler_Basics() { 6205 Window incrWindow; 6206 Atom incrAtom; 6207 6208 void prepareIncremental(Window w, Atom a) { 6209 incrWindow = w; 6210 incrAtom = a; 6211 } 6212 bool matchesIncr(Window w, Atom a) { 6213 return incrWindow == w && incrAtom == a; 6214 } 6215 6216 Atom incrFormatAtom; 6217 ubyte[] incrData; 6218 void handleIncrData(Atom format, in ubyte[] data) { 6219 incrFormatAtom = format; 6220 6221 if(data.length) 6222 incrData ~= data; 6223 else 6224 handleData(incrFormatAtom, incrData); 6225 6226 } 6227 } 6228 6229 /// 6230 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6231 assert(window !is null); 6232 6233 auto display = XDisplayConnection.get(); 6234 auto atom = GetAtom!atomName(display); 6235 6236 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6237 this(void delegate(in char[]) handler) { 6238 this.handler = handler; 6239 } 6240 6241 mixin X11GetSelectionHandler_Basics; 6242 6243 void delegate(in char[]) handler; 6244 6245 void handleData(Atom target, in ubyte[] data) { 6246 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6247 handler(cast(const char[]) data); 6248 } 6249 6250 Atom findBestFormat(Atom[] answer) { 6251 Atom best = None; 6252 foreach(option; answer) { 6253 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6254 best = option; 6255 break; 6256 } else if(option == XA_STRING) { 6257 best = option; 6258 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6259 best = option; 6260 } 6261 } 6262 return best; 6263 } 6264 } 6265 6266 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6267 6268 auto target = GetAtom!"TARGETS"(display); 6269 6270 // SDD_DATA is "simpledisplay.d data" 6271 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6272 } 6273 6274 /// Gets the image on the clipboard, if there is one. Added July 2020. 6275 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6276 assert(window !is null); 6277 6278 auto display = XDisplayConnection.get(); 6279 auto atom = GetAtom!atomName(display); 6280 6281 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6282 this(void delegate(MemoryImage) handler) { 6283 this.handler = handler; 6284 } 6285 6286 mixin X11GetSelectionHandler_Basics; 6287 6288 void delegate(MemoryImage) handler; 6289 6290 void handleData(Atom target, in ubyte[] data) { 6291 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6292 import arsd.bmp; 6293 handler(readBmp(data)); 6294 } 6295 } 6296 6297 Atom findBestFormat(Atom[] answer) { 6298 Atom best = None; 6299 foreach(option; answer) { 6300 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6301 best = option; 6302 } 6303 } 6304 return best; 6305 } 6306 6307 } 6308 6309 6310 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6311 6312 auto target = GetAtom!"TARGETS"(display); 6313 6314 // SDD_DATA is "simpledisplay.d data" 6315 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6316 } 6317 6318 6319 /// 6320 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6321 Atom actualType; 6322 int actualFormat; 6323 arch_ulong actualItems; 6324 arch_ulong bytesRemaining; 6325 void* data; 6326 6327 auto display = XDisplayConnection.get(); 6328 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6329 if(actualFormat == 0) 6330 return null; 6331 else { 6332 int byteLength; 6333 if(actualFormat == 32) { 6334 // 32 means it is a C long... which is variable length 6335 actualFormat = cast(int) arch_long.sizeof * 8; 6336 } 6337 6338 // then it is just a bit count 6339 byteLength = cast(int) (actualItems * actualFormat / 8); 6340 6341 auto d = new ubyte[](byteLength); 6342 d[] = cast(ubyte[]) data[0 .. byteLength]; 6343 XFree(data); 6344 return d; 6345 } 6346 } 6347 return null; 6348 } 6349 6350 /* defined in the systray spec */ 6351 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6352 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6353 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6354 6355 6356 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6357 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6358 public class GlobalHotkey { 6359 KeyEvent key; 6360 void delegate () handler; 6361 6362 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6363 6364 /// Create from initialzed KeyEvent object 6365 this (KeyEvent akey, void delegate () ahandler=null) { 6366 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6367 key = akey; 6368 handler = ahandler; 6369 } 6370 6371 /// Create from emacs-like key name ("C-M-Y", etc.) 6372 this (const(char)[] akey, void delegate () ahandler=null) { 6373 key = KeyEvent.parse(akey); 6374 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6375 handler = ahandler; 6376 } 6377 6378 } 6379 6380 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6381 //conwriteln("failed to grab key"); 6382 GlobalHotkeyManager.ghfailed = true; 6383 return 0; 6384 } 6385 6386 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6387 Image.impl.xshmfailed = true; 6388 return 0; 6389 } 6390 6391 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6392 import core.stdc.stdio; 6393 char[265] buffer; 6394 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6395 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); 6396 return 0; 6397 } 6398 6399 /++ 6400 Global hotkey manager. It contains static methods to manage global hotkeys. 6401 6402 --- 6403 try { 6404 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6405 } catch (Exception e) { 6406 conwriteln("ERROR registering hotkey!"); 6407 } 6408 EventLoop.get.run(); 6409 --- 6410 6411 The key strings are based on Emacs. In practical terms, 6412 `M` means `alt` and `H` means the Windows logo key. `C` 6413 is `ctrl`. 6414 6415 $(WARNING 6416 This is X-specific right now. If you are on 6417 Windows, try [registerHotKey] instead. 6418 6419 We will probably merge these into a single 6420 interface later. 6421 ) 6422 +/ 6423 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6424 version(X11) { 6425 void recreateAfterDisconnect() { 6426 throw new Exception("NOT IMPLEMENTED"); 6427 } 6428 void discardConnectionState() { 6429 throw new Exception("NOT IMPLEMENTED"); 6430 } 6431 } 6432 6433 private static immutable uint[8] masklist = [ 0, 6434 KeyOrButtonMask.LockMask, 6435 KeyOrButtonMask.Mod2Mask, 6436 KeyOrButtonMask.Mod3Mask, 6437 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6438 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6439 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6440 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6441 ]; 6442 private __gshared GlobalHotkeyManager ghmanager; 6443 private __gshared bool ghfailed = false; 6444 6445 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6446 if (modmask == 0) return false; 6447 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6448 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6449 return true; 6450 } 6451 6452 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6453 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6454 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6455 return modmask; 6456 } 6457 6458 private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) { 6459 uint keycode = cast(uint)ke.key; 6460 auto dpy = XDisplayConnection.get; 6461 return XKeysymToKeycode(dpy, keycode); 6462 } 6463 6464 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6465 6466 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6467 6468 NativeEventHandler getNativeEventHandler () { 6469 return delegate int (XEvent e) { 6470 if (e.type != EventType.KeyPress) return 1; 6471 auto kev = cast(const(XKeyEvent)*)&e; 6472 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6473 if (auto ghkp = hash in globalHotkeyList) { 6474 try { 6475 ghkp.doHandle(); 6476 } catch (Exception e) { 6477 import core.stdc.stdio : stderr, fprintf; 6478 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6479 } 6480 } 6481 return 1; 6482 }; 6483 } 6484 6485 private this () { 6486 auto dpy = XDisplayConnection.get; 6487 auto root = RootWindow(dpy, DefaultScreen(dpy)); 6488 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 6489 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 6490 } 6491 6492 /// Register new global hotkey with initialized `GlobalHotkey` object. 6493 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 6494 static void register (GlobalHotkey gh) { 6495 if (gh is null) return; 6496 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 6497 6498 auto dpy = XDisplayConnection.get; 6499 immutable keycode = keyEvent2KeyCode(gh.key); 6500 6501 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 6502 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 6503 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 6504 XSync(dpy, 0/*False*/); 6505 6506 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6507 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6508 ghfailed = false; 6509 foreach (immutable uint ormask; masklist[]) { 6510 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 6511 } 6512 XSync(dpy, 0/*False*/); 6513 XSetErrorHandler(savedErrorHandler); 6514 6515 if (ghfailed) { 6516 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6517 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 6518 XSync(dpy, 0/*False*/); 6519 XSetErrorHandler(savedErrorHandler); 6520 throw new Exception("cannot register global hotkey"); 6521 } 6522 6523 globalHotkeyList[hash] = gh; 6524 } 6525 6526 /// Ditto 6527 static void register (const(char)[] akey, void delegate () ahandler) { 6528 register(new GlobalHotkey(akey, ahandler)); 6529 } 6530 6531 private static void removeByHash (ulong hash) { 6532 if (auto ghp = hash in globalHotkeyList) { 6533 auto dpy = XDisplayConnection.get; 6534 immutable keycode = keyEvent2KeyCode(ghp.key); 6535 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6536 XSync(dpy, 0/*False*/); 6537 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6538 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 6539 XSync(dpy, 0/*False*/); 6540 XSetErrorHandler(savedErrorHandler); 6541 globalHotkeyList.remove(hash); 6542 } 6543 } 6544 6545 /// Register new global hotkey with previously used `GlobalHotkey` object. 6546 /// It is safe to unregister unknown or invalid hotkey. 6547 static void unregister (GlobalHotkey gh) { 6548 //TODO: add second AA for faster search? prolly doesn't worth it. 6549 if (gh is null) return; 6550 foreach (const ref kv; globalHotkeyList.byKeyValue) { 6551 if (kv.value is gh) { 6552 removeByHash(kv.key); 6553 return; 6554 } 6555 } 6556 } 6557 6558 /// Ditto. 6559 static void unregister (const(char)[] key) { 6560 auto kev = KeyEvent.parse(key); 6561 immutable keycode = keyEvent2KeyCode(kev); 6562 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 6563 } 6564 } 6565 } 6566 6567 version(Windows) { 6568 /++ 6569 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 6570 6571 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). 6572 +/ 6573 void sendSyntheticInput(wstring s) { 6574 INPUT[] inputs; 6575 inputs.reserve(s.length * 2); 6576 6577 foreach(wchar c; s) { 6578 INPUT input; 6579 input.type = INPUT_KEYBOARD; 6580 input.ki.wScan = c; 6581 input.ki.dwFlags = KEYEVENTF_UNICODE; 6582 inputs ~= input; 6583 6584 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6585 inputs ~= input; 6586 } 6587 6588 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6589 throw new Exception("SendInput failed"); 6590 } 6591 6592 } 6593 6594 6595 // global hotkey helper function 6596 6597 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 6598 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6599 __gshared int hotkeyId = 0; 6600 int id = ++hotkeyId; 6601 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6602 throw new Exception("RegisterHotKey failed"); 6603 6604 __gshared void delegate()[WPARAM][HWND] handlers; 6605 6606 handlers[window.impl.hwnd][id] = handler; 6607 6608 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 6609 6610 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 6611 switch(msg) { 6612 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6613 case WM_HOTKEY: 6614 if(auto list = hwnd in handlers) { 6615 if(auto h = wParam in *list) { 6616 (*h)(); 6617 return 0; 6618 } 6619 } 6620 goto default; 6621 default: 6622 } 6623 if(oldHandler) 6624 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 6625 return 1; // pass it on 6626 }; 6627 6628 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6629 oldHandler = window.handleNativeEvent; 6630 window.handleNativeEvent = nativeEventHandler; 6631 } 6632 6633 return id; 6634 } 6635 6636 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 6637 void unregisterHotKey(SimpleWindow window, int id) { 6638 if(!UnregisterHotKey(window.impl.hwnd, id)) 6639 throw new Exception("UnregisterHotKey"); 6640 } 6641 } 6642 6643 version (X11) { 6644 pragma(lib, "dl"); 6645 import core.sys.posix.dlfcn; 6646 } 6647 6648 /++ 6649 Allows for sending synthetic input to the X server via the Xtst 6650 extension or on Windows using SendInput. 6651 6652 Please remember user input is meant to be user - don't use this 6653 if you have some other alternative! 6654 6655 History: 6656 Added May 17, 2020 with the X implementation. 6657 6658 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.) 6659 Bugs: 6660 All methods on OSX Cocoa will throw not yet implemented exceptions. 6661 +/ 6662 struct SyntheticInput { 6663 @disable this(); 6664 6665 private int* refcount; 6666 6667 version(X11) { 6668 private void* lib; 6669 6670 private extern(C) { 6671 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6672 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6673 } 6674 } 6675 6676 /// The dummy param must be 0. 6677 this(int dummy) { 6678 version(X11) { 6679 lib = dlopen("libXtst.so", RTLD_NOW); 6680 if(lib is null) 6681 throw new Exception("cannot load xtest lib extension"); 6682 scope(failure) 6683 dlclose(lib); 6684 6685 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6686 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6687 6688 if(XTestFakeKeyEvent is null) 6689 throw new Exception("No XTestFakeKeyEvent"); 6690 if(XTestFakeButtonEvent is null) 6691 throw new Exception("No XTestFakeButtonEvent"); 6692 } 6693 6694 refcount = new int; 6695 *refcount = 1; 6696 } 6697 6698 this(this) { 6699 if(refcount) 6700 *refcount += 1; 6701 } 6702 6703 ~this() { 6704 if(refcount) { 6705 *refcount -= 1; 6706 if(*refcount == 0) 6707 // I commented this because if I close the lib before 6708 // XCloseDisplay, it is liable to segfault... so just 6709 // gonna keep it loaded if it is loaded, no big deal 6710 // anyway. 6711 {} // dlclose(lib); 6712 } 6713 } 6714 6715 /++ 6716 Simulates typing a string into the keyboard. 6717 6718 Bugs: 6719 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 6720 6721 Not implemented except on Windows and X11. 6722 +/ 6723 void sendSyntheticInput(string s) { 6724 version(Windows) { 6725 INPUT[] inputs; 6726 inputs.reserve(s.length * 2); 6727 6728 auto ei = GetMessageExtraInfo(); 6729 6730 foreach(wchar c; s) { 6731 INPUT input; 6732 input.type = INPUT_KEYBOARD; 6733 input.ki.wScan = c; 6734 input.ki.dwFlags = KEYEVENTF_UNICODE; 6735 input.ki.dwExtraInfo = ei; 6736 inputs ~= input; 6737 6738 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6739 inputs ~= input; 6740 } 6741 6742 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6743 throw new Exception("SendInput failed"); 6744 } 6745 } else version(X11) { 6746 int delay = 0; 6747 foreach(ch; s) { 6748 pressKey(cast(Key) ch, true, delay); 6749 pressKey(cast(Key) ch, false, delay); 6750 delay += 5; 6751 } 6752 } else throw new NotYetImplementedException(); 6753 } 6754 6755 /++ 6756 Sends a fake press or release key event. 6757 6758 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6759 6760 Bugs: 6761 The `delay` parameter is not implemented yet on Windows. 6762 6763 Not implemented except on Windows and X11. 6764 +/ 6765 void pressKey(Key key, bool pressed, int delay = 0) { 6766 version(Windows) { 6767 INPUT input; 6768 input.type = INPUT_KEYBOARD; 6769 input.ki.wVk = cast(ushort) key; 6770 6771 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 6772 input.ki.dwExtraInfo = GetMessageExtraInfo(); 6773 6774 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6775 throw new Exception("SendInput failed"); 6776 } 6777 } else version(X11) { 6778 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6779 } else throw new NotYetImplementedException(); 6780 } 6781 6782 /++ 6783 Sends a fake mouse button press or release event. 6784 6785 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6786 6787 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 6788 6789 Bugs: 6790 The `delay` parameter is not implemented yet on Windows. 6791 6792 The backButton and forwardButton will throw NotYetImplementedException on Windows. 6793 6794 All arguments will throw NotYetImplementedException on OSX Cocoa. 6795 +/ 6796 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6797 version(Windows) { 6798 INPUT input; 6799 input.type = INPUT_MOUSE; 6800 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6801 6802 // input.mi.mouseData for a wheel event 6803 6804 switch(button) { 6805 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 6806 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 6807 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 6808 case MouseButton.wheelUp: 6809 case MouseButton.wheelDown: 6810 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 6811 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 6812 break; 6813 case MouseButton.backButton: throw new NotYetImplementedException(); 6814 case MouseButton.forwardButton: throw new NotYetImplementedException(); 6815 default: 6816 } 6817 6818 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6819 throw new Exception("SendInput failed"); 6820 } 6821 } else version(X11) { 6822 int btn; 6823 6824 switch(button) { 6825 case MouseButton.left: btn = 1; break; 6826 case MouseButton.middle: btn = 2; break; 6827 case MouseButton.right: btn = 3; break; 6828 case MouseButton.wheelUp: btn = 4; break; 6829 case MouseButton.wheelDown: btn = 5; break; 6830 case MouseButton.backButton: btn = 8; break; 6831 case MouseButton.forwardButton: btn = 9; break; 6832 default: 6833 } 6834 6835 assert(btn); 6836 6837 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 6838 } else throw new NotYetImplementedException(); 6839 } 6840 6841 /// 6842 static void moveMouseArrowBy(int dx, int dy) { 6843 version(Windows) { 6844 INPUT input; 6845 input.type = INPUT_MOUSE; 6846 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6847 input.mi.dx = dx; 6848 input.mi.dy = dy; 6849 input.mi.dwFlags = MOUSEEVENTF_MOVE; 6850 6851 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6852 throw new Exception("SendInput failed"); 6853 } 6854 } else version(X11) { 6855 auto disp = XDisplayConnection.get(); 6856 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 6857 XFlush(disp); 6858 } else throw new NotYetImplementedException(); 6859 } 6860 6861 /// 6862 static void moveMouseArrowTo(int x, int y) { 6863 version(Windows) { 6864 INPUT input; 6865 input.type = INPUT_MOUSE; 6866 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6867 input.mi.dx = x; 6868 input.mi.dy = y; 6869 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 6870 6871 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6872 throw new Exception("SendInput failed"); 6873 } 6874 } else version(X11) { 6875 auto disp = XDisplayConnection.get(); 6876 auto root = RootWindow(disp, DefaultScreen(disp)); 6877 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 6878 XFlush(disp); 6879 } else throw new NotYetImplementedException(); 6880 } 6881 } 6882 6883 6884 6885 /++ 6886 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 6887 6888 See_Also: 6889 $(LIST 6890 *[ScreenPainter] 6891 *[ScreenPainter.rasterOp] 6892 ) 6893 +/ 6894 enum RasterOp { 6895 normal, /// Replaces the pixel. 6896 xor, /// Uses bitwise xor to draw. 6897 } 6898 6899 // being phobos-free keeps the size WAY down 6900 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 6901 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 6902 package(arsd) const(wchar)* toWStringz(string s) { 6903 wstring r; 6904 foreach(dchar c; s) 6905 r ~= c; 6906 r ~= '\0'; 6907 return r.ptr; 6908 } 6909 private string[] split(in void[] a, char c) { 6910 string[] ret; 6911 size_t previous = 0; 6912 foreach(i, char ch; cast(ubyte[]) a) { 6913 if(ch == c) { 6914 ret ~= cast(string) a[previous .. i]; 6915 previous = i + 1; 6916 } 6917 } 6918 if(previous != a.length) 6919 ret ~= cast(string) a[previous .. $]; 6920 return ret; 6921 } 6922 6923 version(without_opengl) { 6924 enum OpenGlOptions { 6925 no, 6926 } 6927 } else { 6928 /++ 6929 Determines if you want an OpenGL context created on the new window. 6930 6931 6932 See more: [#topics-3d|in the 3d topic]. 6933 6934 --- 6935 import arsd.simpledisplay; 6936 void main() { 6937 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 6938 6939 // Set up the matrix 6940 window.setAsCurrentOpenGlContext(); // make this window active 6941 6942 // This is called on each frame, we will draw our scene 6943 window.redrawOpenGlScene = delegate() { 6944 6945 }; 6946 6947 window.eventLoop(0); 6948 } 6949 --- 6950 +/ 6951 enum OpenGlOptions { 6952 no, /// No OpenGL context is created 6953 yes, /// Yes, create an OpenGL context 6954 } 6955 6956 version(X11) { 6957 static if (!SdpyIsUsingIVGLBinds) { 6958 6959 6960 struct __GLXFBConfigRec {} 6961 alias GLXFBConfig = __GLXFBConfigRec*; 6962 6963 //pragma(lib, "GL"); 6964 //pragma(lib, "GLU"); 6965 interface GLX { 6966 extern(C) nothrow @nogc { 6967 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 6968 const int *attrib_list); 6969 6970 void glXCopyContext(Display *dpy, GLXContext src, 6971 GLXContext dst, arch_ulong mask); 6972 6973 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 6974 GLXContext share_list, Bool direct); 6975 6976 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 6977 Pixmap pixmap); 6978 6979 void glXDestroyContext(Display *dpy, GLXContext ctx); 6980 6981 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 6982 6983 int glXGetConfig(Display *dpy, XVisualInfo *vis, 6984 int attrib, int *value); 6985 6986 GLXContext glXGetCurrentContext(); 6987 6988 GLXDrawable glXGetCurrentDrawable(); 6989 6990 Bool glXIsDirect(Display *dpy, GLXContext ctx); 6991 6992 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 6993 GLXContext ctx); 6994 6995 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 6996 6997 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 6998 6999 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7000 7001 void glXUseXFont(Font font, int first, int count, int list_base); 7002 7003 void glXWaitGL(); 7004 7005 void glXWaitX(); 7006 7007 7008 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7009 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7010 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7011 7012 char* glXQueryExtensionsString (Display*, int); 7013 void* glXGetProcAddress (const(char)*); 7014 7015 } 7016 } 7017 7018 version(OSX) 7019 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7020 else 7021 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7022 shared static this() { 7023 glx.loadDynamicLibrary(); 7024 } 7025 7026 alias glbindGetProcAddress = glXGetProcAddress; 7027 } 7028 } else version(Windows) { 7029 /* it is done below by interface GL */ 7030 } else 7031 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."); 7032 } 7033 7034 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7035 alias Resizablity = Resizability; 7036 7037 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7038 enum Resizability { 7039 fixedSize, /// the window cannot be resized 7040 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. 7041 automaticallyScaleIfPossible, /// if possible, your drawing buffer will remain the same size and simply be automatically scaled to the new window size. If this is impossible, it will not allow the user to resize the window at all. Note: window.width and window.height WILL be adjusted, which might throw you off if you draw based on them, so keep track of your expected width and height separately. That way, when it is scaled, things won't be thrown off. 7042 7043 // FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events 7044 } 7045 7046 7047 /++ 7048 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7049 +/ 7050 enum TextAlignment : uint { 7051 Left = 0, /// 7052 Center = 1, /// 7053 Right = 2, /// 7054 7055 VerticalTop = 0, /// 7056 VerticalCenter = 4, /// 7057 VerticalBottom = 8, /// 7058 } 7059 7060 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7061 alias Rectangle = arsd.color.Rectangle; 7062 7063 7064 /++ 7065 Keyboard press and release events. 7066 +/ 7067 struct KeyEvent { 7068 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7069 Key key; 7070 ubyte hardwareCode; /// A platform and hardware specific code for the key 7071 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7072 7073 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7074 7075 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7076 7077 SimpleWindow window; /// associated Window 7078 7079 /++ 7080 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7081 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7082 to predict if char events are actually coming.. 7083 7084 Only available on X systems since this information is not given ahead of time elsewhere. 7085 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7086 7087 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7088 and potential quirks I'd recommend avoiding it. 7089 7090 History: 7091 Added April 26, 2021 (dub v9.5) 7092 +/ 7093 version(X11) 7094 dchar[] charsPossible; 7095 7096 // convert key event to simplified string representation a-la emacs 7097 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7098 uint dpos = 0; 7099 void put (const(char)[] s...) nothrow @trusted { 7100 static if (growdest) { 7101 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7102 } else { 7103 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7104 } 7105 } 7106 7107 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7108 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7109 } 7110 7111 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7112 7113 // put modifiers 7114 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7115 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7116 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7117 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7118 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7119 7120 if (this.key) { 7121 foreach (string kn; __traits(allMembers, Key)) { 7122 if (this.key == __traits(getMember, Key, kn)) { 7123 // HACK! 7124 static if (kn == "N0") put("0"); 7125 else static if (kn == "N1") put("1"); 7126 else static if (kn == "N2") put("2"); 7127 else static if (kn == "N3") put("3"); 7128 else static if (kn == "N4") put("4"); 7129 else static if (kn == "N5") put("5"); 7130 else static if (kn == "N6") put("6"); 7131 else static if (kn == "N7") put("7"); 7132 else static if (kn == "N8") put("8"); 7133 else static if (kn == "N9") put("9"); 7134 else put(kn); 7135 return dest[0..dpos]; 7136 } 7137 } 7138 put("Unknown"); 7139 } else { 7140 if (dpos && dest[dpos-1] == '+') --dpos; 7141 } 7142 return dest[0..dpos]; 7143 } 7144 7145 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7146 7147 /** Parse string into key name with modifiers. It accepts things like: 7148 * 7149 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7150 * 7151 * Ctrl+Win+1 -- windows style 7152 * 7153 * Ctrl-Win-1 -- '-' is a valid delimiter too 7154 * 7155 * Ctrl Win 1 -- and space 7156 * 7157 * and even "Win + 1 + Ctrl". 7158 */ 7159 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7160 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7161 7162 // remove trailing spaces 7163 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7164 7165 // tokens delimited by blank, '+', or '-' 7166 // null on eol 7167 const(char)[] getToken () nothrow @trusted @nogc { 7168 // remove leading spaces and delimiters 7169 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7170 if (name.length == 0) return null; // oops, no more tokens 7171 // get token 7172 size_t epos = 0; 7173 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7174 assert(epos > 0 && epos <= name.length); 7175 auto res = name[0..epos]; 7176 name = name[epos..$]; 7177 return res; 7178 } 7179 7180 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7181 if (s0.length != s1.length) return false; 7182 foreach (immutable ci, char c0; s0) { 7183 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7184 char c1 = s1[ci]; 7185 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7186 if (c0 != c1) return false; 7187 } 7188 return true; 7189 } 7190 7191 if (ignoreModsOut !is null) *ignoreModsOut = false; 7192 if (updown !is null) *updown = -1; 7193 KeyEvent res; 7194 res.key = cast(Key)0; // just in case 7195 const(char)[] tk, tkn; // last token 7196 bool allowEmascStyle = true; 7197 bool ignoreModifiers = false; 7198 tokenloop: for (;;) { 7199 tk = tkn; 7200 tkn = getToken(); 7201 //k8: yay, i took "Bloody Mess" trait from Fallout! 7202 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7203 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7204 if (allowEmascStyle && tkn.length != 0) { 7205 if (tk.length == 1) { 7206 char mdc = tk[0]; 7207 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7208 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7209 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7210 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7211 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7212 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7213 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7214 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7215 } 7216 } 7217 allowEmascStyle = false; 7218 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7219 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7220 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7221 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7222 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7223 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7224 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7225 if (tk.length == 0) continue; 7226 // try key name 7227 if (res.key == 0) { 7228 // little hack 7229 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7230 final switch (tk[0]) { 7231 case '0': tk = "N0"; break; 7232 case '1': tk = "N1"; break; 7233 case '2': tk = "N2"; break; 7234 case '3': tk = "N3"; break; 7235 case '4': tk = "N4"; break; 7236 case '5': tk = "N5"; break; 7237 case '6': tk = "N6"; break; 7238 case '7': tk = "N7"; break; 7239 case '8': tk = "N8"; break; 7240 case '9': tk = "N9"; break; 7241 } 7242 } 7243 foreach (string kn; __traits(allMembers, Key)) { 7244 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7245 } 7246 } 7247 // unknown or duplicate key name, get out of here 7248 break; 7249 } 7250 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7251 return res; // something 7252 } 7253 7254 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7255 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7256 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7257 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7258 } 7259 bool ignoreMods; 7260 int updown; 7261 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7262 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7263 if (this.key != ke.key) { 7264 // things like "ctrl+alt" are complicated 7265 uint tkm = this.modifierState&modmask; 7266 uint kkm = ke.modifierState&modmask; 7267 Key tk = this.key; 7268 // ke 7269 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7270 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7271 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7272 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7273 // this 7274 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7275 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7276 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7277 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7278 return (tk == ke.key && tkm == kkm); 7279 } 7280 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7281 } 7282 } 7283 7284 /// Sets the application name. 7285 @property string ApplicationName(string name) { 7286 return _applicationName = name; 7287 } 7288 7289 string _applicationName; 7290 7291 /// ditto 7292 @property string ApplicationName() { 7293 if(_applicationName is null) { 7294 import core.runtime; 7295 return Runtime.args[0]; 7296 } 7297 return _applicationName; 7298 } 7299 7300 7301 /// Type of a [MouseEvent]. 7302 enum MouseEventType : int { 7303 motion = 0, /// The mouse moved inside the window 7304 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7305 buttonReleased = 2, /// A mouse button was released 7306 } 7307 7308 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7309 /++ 7310 Listen for this on your event listeners if you are interested in mouse action. 7311 7312 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. 7313 7314 Examples: 7315 7316 This will draw boxes on the window with the mouse as you hold the left button. 7317 --- 7318 import arsd.simpledisplay; 7319 7320 void main() { 7321 auto window = new SimpleWindow(); 7322 7323 window.eventLoop(0, 7324 (MouseEvent ev) { 7325 if(ev.modifierState & ModifierState.leftButtonDown) { 7326 auto painter = window.draw(); 7327 painter.fillColor = Color.red; 7328 painter.outlineColor = Color.black; 7329 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7330 } 7331 } 7332 ); 7333 } 7334 --- 7335 +/ 7336 struct MouseEvent { 7337 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7338 7339 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. 7340 int y; /// Current Y position of the cursor when the event fired. 7341 7342 int dx; /// Change in X position since last report 7343 int dy; /// Change in Y position since last report 7344 7345 MouseButton button; /// See [MouseButton] 7346 int modifierState; /// See [ModifierState] 7347 7348 version(X11) 7349 private Time timestamp; 7350 7351 /// Returns a linear representation of mouse button, 7352 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7353 /// 7354 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7355 @property ubyte buttonLinear() const { 7356 import core.bitop; 7357 if(button == 0) 7358 return 0; 7359 return (bsf(button) + 1) & 0b1111; 7360 } 7361 7362 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7363 7364 SimpleWindow window; /// The window in which the event happened. 7365 7366 Point globalCoordinates() { 7367 Point p; 7368 if(window is null) 7369 throw new Exception("wtf"); 7370 static if(UsingSimpledisplayX11) { 7371 Window child; 7372 XTranslateCoordinates( 7373 XDisplayConnection.get, 7374 window.impl.window, 7375 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7376 x, y, &p.x, &p.y, &child); 7377 return p; 7378 } else version(Windows) { 7379 POINT[1] points; 7380 points[0].x = x; 7381 points[0].y = y; 7382 MapWindowPoints( 7383 window.impl.hwnd, 7384 null, 7385 points.ptr, 7386 points.length 7387 ); 7388 p.x = points[0].x; 7389 p.y = points[0].y; 7390 7391 return p; 7392 } else version(OSXCocoa) { 7393 throw new NotYetImplementedException(); 7394 } else static assert(0); 7395 } 7396 7397 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7398 7399 /** 7400 can contain emacs-like modifier prefix 7401 case-insensitive names: 7402 lmbX/leftX 7403 rmbX/rightX 7404 mmbX/middleX 7405 wheelX 7406 motion (no prefix allowed) 7407 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7408 */ 7409 static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7410 if (str.length == 0) return false; // just in case 7411 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7412 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7413 auto anchor = str; 7414 uint mods = 0; // uint.max == any 7415 // interesting bits in kmod 7416 uint kmodmask = 7417 ModifierState.shift| 7418 ModifierState.ctrl| 7419 ModifierState.alt| 7420 ModifierState.windows| 7421 ModifierState.leftButtonDown| 7422 ModifierState.middleButtonDown| 7423 ModifierState.rightButtonDown| 7424 0; 7425 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7426 bool wasButtons = false; 7427 while (str.length) { 7428 if (str.ptr[0] <= ' ') { 7429 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7430 continue; 7431 } 7432 // one-letter modifier? 7433 if (str.length >= 2 && str.ptr[1] == '-') { 7434 switch (str.ptr[0]) { 7435 case '*': // "any" modifier (cannot be undone) 7436 mods = mods.max; 7437 break; 7438 case 'C': case 'c': // emacs "ctrl" 7439 if (mods != mods.max) mods |= ModifierState.ctrl; 7440 break; 7441 case 'M': case 'm': // emacs "meta" 7442 if (mods != mods.max) mods |= ModifierState.alt; 7443 break; 7444 case 'S': case 's': // emacs "shift" 7445 if (mods != mods.max) mods |= ModifierState.shift; 7446 break; 7447 case 'H': case 'h': // emacs "hyper" (aka winkey) 7448 if (mods != mods.max) mods |= ModifierState.windows; 7449 break; 7450 default: 7451 return false; // unknown modifier 7452 } 7453 str = str[2..$]; 7454 continue; 7455 } 7456 // word 7457 char[16] buf = void; // locased 7458 auto wep = 0; 7459 while (str.length) { 7460 immutable char ch = str.ptr[0]; 7461 if (ch <= ' ' || ch == '-') break; 7462 str = str[1..$]; 7463 if (wep > buf.length) return false; // too long 7464 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7465 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7466 else return false; // invalid char 7467 } 7468 if (wep == 0) return false; // just in case 7469 uint bnum; 7470 enum UpDown { None = -1, Up, Down, Any } 7471 auto updown = UpDown.None; // 0: up; 1: down 7472 switch (buf[0..wep]) { 7473 // left button 7474 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 7475 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 7476 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 7477 case "lmb": case "left": bnum = 0; break; 7478 // middle button 7479 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 7480 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 7481 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 7482 case "mmb": case "middle": bnum = 1; break; 7483 // right button 7484 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 7485 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 7486 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 7487 case "rmb": case "right": bnum = 2; break; 7488 // wheel 7489 case "wheelup": updown = UpDown.Up; goto case "wheel"; 7490 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 7491 case "wheelany": updown = UpDown.Any; goto case "wheel"; 7492 case "wheel": bnum = 3; break; 7493 // motion 7494 case "motion": bnum = 7; break; 7495 // unknown 7496 default: return false; 7497 } 7498 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7499 // parse possible "-up" or "-down" 7500 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 7501 wep = 0; 7502 foreach (immutable idx, immutable char ch; str[1..$]) { 7503 if (ch <= ' ' || ch == '-') break; 7504 assert(idx == wep); // for now; trick 7505 if (wep > buf.length) { wep = 0; break; } // too long 7506 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7507 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7508 else { wep = 0; break; } // invalid char 7509 } 7510 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 7511 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 7512 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 7513 // remove parsed part 7514 if (updown != UpDown.None) str = str[wep+1..$]; 7515 } 7516 if (updown == UpDown.None) { 7517 updown = UpDown.Down; 7518 } 7519 wasButtons = wasButtons || (bnum <= 2); 7520 //assert(updown != UpDown.None); 7521 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7522 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 7523 if (lastButt != lastButt.max) { 7524 if ((lastButt&0xff) >= 3) return false; // wheel or motion 7525 if (mods != mods.max) { 7526 uint butbit = 0; 7527 final switch (lastButt&0x03) { 7528 case 0: butbit = ModifierState.leftButtonDown; break; 7529 case 1: butbit = ModifierState.middleButtonDown; break; 7530 case 2: butbit = ModifierState.rightButtonDown; break; 7531 } 7532 if (lastButt&Flag.Down) mods |= butbit; 7533 else if (lastButt&Flag.Up) mods &= ~butbit; 7534 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 7535 } 7536 } 7537 // remember last button 7538 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 7539 } 7540 // no button -- nothing to do 7541 if (lastButt == lastButt.max) return false; 7542 // done parsing, check if something's left 7543 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 7544 // remove action button from mask 7545 if ((lastButt&0xff) < 3) { 7546 final switch (lastButt&0x03) { 7547 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 7548 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 7549 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 7550 } 7551 } 7552 // special case: "Motion" means "ignore buttons" 7553 if ((lastButt&0xff) == 7 && !wasButtons) { 7554 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 7555 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 7556 } 7557 uint kmod = event.modifierState&kmodmask; 7558 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 7559 // check modifier state 7560 if (mods != mods.max) { 7561 if (kmod != mods) return false; 7562 } 7563 // now check type 7564 if ((lastButt&0xff) == 7) { 7565 // motion 7566 if (event.type != MouseEventType.motion) return false; 7567 } else if ((lastButt&0xff) == 3) { 7568 // wheel 7569 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 7570 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 7571 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 7572 return false; 7573 } else { 7574 // buttons 7575 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 7576 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 7577 { 7578 return false; 7579 } 7580 // button number 7581 switch (lastButt&0x03) { 7582 case 0: if (event.button != MouseButton.left) return false; break; 7583 case 1: if (event.button != MouseButton.middle) return false; break; 7584 case 2: if (event.button != MouseButton.right) return false; break; 7585 default: return false; 7586 } 7587 } 7588 return true; 7589 } 7590 } 7591 7592 version(arsd_mevent_strcmp_test) unittest { 7593 MouseEvent event; 7594 event.type = MouseEventType.buttonPressed; 7595 event.button = MouseButton.left; 7596 event.modifierState = ModifierState.ctrl; 7597 assert(event == "C-LMB"); 7598 assert(event != "C-LMBUP"); 7599 assert(event != "C-LMB-UP"); 7600 assert(event != "C-S-LMB"); 7601 assert(event == "*-LMB"); 7602 assert(event != "*-LMB-UP"); 7603 7604 event.type = MouseEventType.buttonReleased; 7605 assert(event != "C-LMB"); 7606 assert(event == "C-LMBUP"); 7607 assert(event == "C-LMB-UP"); 7608 assert(event != "C-S-LMB"); 7609 assert(event != "*-LMB"); 7610 assert(event == "*-LMB-UP"); 7611 7612 event.button = MouseButton.right; 7613 event.modifierState |= ModifierState.shift; 7614 event.type = MouseEventType.buttonPressed; 7615 assert(event != "C-LMB"); 7616 assert(event != "C-LMBUP"); 7617 assert(event != "C-LMB-UP"); 7618 assert(event != "C-S-LMB"); 7619 assert(event != "*-LMB"); 7620 assert(event != "*-LMB-UP"); 7621 7622 assert(event != "C-RMB"); 7623 assert(event != "C-RMBUP"); 7624 assert(event != "C-RMB-UP"); 7625 assert(event == "C-S-RMB"); 7626 assert(event == "*-RMB"); 7627 assert(event != "*-RMB-UP"); 7628 } 7629 7630 /// This gives a few more options to drawing lines and such 7631 struct Pen { 7632 Color color; /// the foreground color 7633 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. 7634 Style style; /// See [Style] 7635 /+ 7636 // From X.h 7637 7638 #define LineSolid 0 7639 #define LineOnOffDash 1 7640 #define LineDoubleDash 2 7641 LineDou- The full path of the line is drawn, but the 7642 bleDash even dashes are filled differently from the 7643 odd dashes (see fill-style) with CapButt 7644 style used where even and odd dashes meet. 7645 7646 7647 7648 /* capStyle */ 7649 7650 #define CapNotLast 0 7651 #define CapButt 1 7652 #define CapRound 2 7653 #define CapProjecting 3 7654 7655 /* joinStyle */ 7656 7657 #define JoinMiter 0 7658 #define JoinRound 1 7659 #define JoinBevel 2 7660 7661 /* fillStyle */ 7662 7663 #define FillSolid 0 7664 #define FillTiled 1 7665 #define FillStippled 2 7666 #define FillOpaqueStippled 3 7667 7668 7669 +/ 7670 /// Style of lines drawn 7671 enum Style { 7672 Solid, /// a solid line 7673 Dashed, /// a dashed line 7674 Dotted, /// a dotted line 7675 } 7676 } 7677 7678 7679 /++ 7680 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 7681 7682 7683 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 7684 7685 $(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.) 7686 7687 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. 7688 7689 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 7690 7691 $(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. 7692 7693 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! 7694 7695 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!) 7696 7697 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 7698 7699 --- 7700 auto image = new Image(256, 256); 7701 scope(exit) destroy(image); 7702 --- 7703 7704 As long as you don't hold on to it outside the scope. 7705 7706 I might change it to be an owned pointer at some point in the future. 7707 7708 ) 7709 7710 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7711 you can also often get a fair amount of speedup by getting the raw data format and 7712 writing some custom code. 7713 7714 FIXME INSERT EXAMPLES HERE 7715 7716 7717 +/ 7718 final class Image { 7719 /// 7720 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7721 this.width = width; 7722 this.height = height; 7723 this.enableAlpha = enableAlpha; 7724 7725 impl.createImage(width, height, forcexshm, enableAlpha); 7726 } 7727 7728 /// 7729 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7730 this(size.width, size.height, forcexshm, enableAlpha); 7731 } 7732 7733 private bool suppressDestruction; 7734 7735 version(X11) 7736 this(XImage* handle) { 7737 this.handle = handle; 7738 this.rawData = cast(ubyte*) handle.data; 7739 this.width = handle.width; 7740 this.height = handle.height; 7741 this.enableAlpha = handle.depth == 32; 7742 suppressDestruction = true; 7743 } 7744 7745 ~this() { 7746 if(suppressDestruction) return; 7747 impl.dispose(); 7748 } 7749 7750 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7751 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7752 pure const @system nothrow { 7753 /* 7754 To use these to draw a blue rectangle with size WxH at position X,Y... 7755 7756 // make certain that it will fit before we proceed 7757 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! 7758 7759 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7760 // (though calculating them isn't really that expensive). 7761 auto nextLineAdjustment = img.adjustmentForNextLine(); 7762 auto offR = img.redByteOffset(); 7763 auto offB = img.blueByteOffset(); 7764 auto offG = img.greenByteOffset(); 7765 auto bpp = img.bytesPerPixel(); 7766 7767 auto data = img.getDataPointer(); 7768 7769 // figure out the starting byte offset 7770 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7771 7772 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7773 7774 // and now our drawing loop for the rectangle 7775 foreach(y; 0 .. H) { 7776 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7777 foreach(x; 0 .. W) { 7778 // write our color 7779 data[offR] = 0; 7780 data[offG] = 0; 7781 data[offB] = 255; 7782 7783 data += bpp; // moving to the next pixel is just an addition... 7784 } 7785 startOfLine += nextLineAdjustment; 7786 } 7787 7788 7789 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7790 7791 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7792 can be made into a bitmask or something so we can write them as *uint... 7793 */ 7794 7795 /// 7796 int offsetForTopLeftPixel() { 7797 version(X11) { 7798 return 0; 7799 } else version(Windows) { 7800 if(enableAlpha) { 7801 return (width * 4) * (height - 1); 7802 } else { 7803 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7804 } 7805 } else version(OSXCocoa) { 7806 return 0 ; //throw new NotYetImplementedException(); 7807 } else static assert(0, "fill in this info for other OSes"); 7808 } 7809 7810 /// 7811 int offsetForPixel(int x, int y) { 7812 version(X11) { 7813 auto offset = (y * width + x) * 4; 7814 return offset; 7815 } else version(Windows) { 7816 if(enableAlpha) { 7817 auto itemsPerLine = width * 4; 7818 // remember, bmps are upside down 7819 auto offset = itemsPerLine * (height - y - 1) + x * 4; 7820 return offset; 7821 } else { 7822 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 7823 // remember, bmps are upside down 7824 auto offset = itemsPerLine * (height - y - 1) + x * 3; 7825 return offset; 7826 } 7827 } else version(OSXCocoa) { 7828 return 0 ; //throw new NotYetImplementedException(); 7829 } else static assert(0, "fill in this info for other OSes"); 7830 } 7831 7832 /// 7833 int adjustmentForNextLine() { 7834 version(X11) { 7835 return width * 4; 7836 } else version(Windows) { 7837 // windows bmps are upside down, so the adjustment is actually negative 7838 if(enableAlpha) 7839 return - (cast(int) width * 4); 7840 else 7841 return -((cast(int) width * 3 + 3) / 4) * 4; 7842 } else version(OSXCocoa) { 7843 return 0 ; //throw new NotYetImplementedException(); 7844 } else static assert(0, "fill in this info for other OSes"); 7845 } 7846 7847 /// once you have the position of a pixel, use these to get to the proper color 7848 int redByteOffset() { 7849 version(X11) { 7850 return 2; 7851 } else version(Windows) { 7852 return 2; 7853 } else version(OSXCocoa) { 7854 return 0 ; //throw new NotYetImplementedException(); 7855 } else static assert(0, "fill in this info for other OSes"); 7856 } 7857 7858 /// 7859 int greenByteOffset() { 7860 version(X11) { 7861 return 1; 7862 } else version(Windows) { 7863 return 1; 7864 } else version(OSXCocoa) { 7865 return 0 ; //throw new NotYetImplementedException(); 7866 } else static assert(0, "fill in this info for other OSes"); 7867 } 7868 7869 /// 7870 int blueByteOffset() { 7871 version(X11) { 7872 return 0; 7873 } else version(Windows) { 7874 return 0; 7875 } else version(OSXCocoa) { 7876 return 0 ; //throw new NotYetImplementedException(); 7877 } else static assert(0, "fill in this info for other OSes"); 7878 } 7879 7880 /// Only valid if [enableAlpha] is true 7881 int alphaByteOffset() { 7882 version(X11) { 7883 return 3; 7884 } else version(Windows) { 7885 return 3; 7886 } else version(OSXCocoa) { 7887 return 3; //throw new NotYetImplementedException(); 7888 } else static assert(0, "fill in this info for other OSes"); 7889 } 7890 } 7891 7892 /// 7893 final void putPixel(int x, int y, Color c) { 7894 if(x < 0 || x >= width) 7895 return; 7896 if(y < 0 || y >= height) 7897 return; 7898 7899 impl.setPixel(x, y, c); 7900 } 7901 7902 /// 7903 final Color getPixel(int x, int y) { 7904 if(x < 0 || x >= width) 7905 return Color.transparent; 7906 if(y < 0 || y >= height) 7907 return Color.transparent; 7908 7909 version(OSXCocoa) throw new NotYetImplementedException(); else 7910 return impl.getPixel(x, y); 7911 } 7912 7913 /// 7914 final void opIndexAssign(Color c, int x, int y) { 7915 putPixel(x, y, c); 7916 } 7917 7918 /// 7919 TrueColorImage toTrueColorImage() { 7920 auto tci = new TrueColorImage(width, height); 7921 convertToRgbaBytes(tci.imageData.bytes); 7922 return tci; 7923 } 7924 7925 /// 7926 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) { 7927 auto tci = i.getAsTrueColorImage(); 7928 auto img = new Image(tci.width, tci.height, false, enableAlpha); 7929 img.setRgbaBytes(tci.imageData.bytes); 7930 return img; 7931 } 7932 7933 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 7934 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 7935 /// if you pass null, it will allocate a new one. 7936 ubyte[] getRgbaBytes(ubyte[] where = null) { 7937 if(where is null) 7938 where = new ubyte[this.width*this.height*4]; 7939 convertToRgbaBytes(where); 7940 return where; 7941 } 7942 7943 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 7944 void setRgbaBytes(in ubyte[] from ) { 7945 assert(from.length == this.width * this.height * 4); 7946 setFromRgbaBytes(from); 7947 } 7948 7949 // FIXME: make properly cross platform by getting rgba right 7950 7951 /// warning: this is not portable across platforms because the data format can change 7952 ubyte* getDataPointer() { 7953 return impl.rawData; 7954 } 7955 7956 /// for use with getDataPointer 7957 final int bytesPerLine() const pure @safe nothrow { 7958 version(Windows) 7959 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 7960 else version(X11) 7961 return 4 * width; 7962 else version(OSXCocoa) 7963 return 4 * width; 7964 else static assert(0); 7965 } 7966 7967 /// for use with getDataPointer 7968 final int bytesPerPixel() const pure @safe nothrow { 7969 version(Windows) 7970 return enableAlpha ? 4 : 3; 7971 else version(X11) 7972 return 4; 7973 else version(OSXCocoa) 7974 return 4; 7975 else static assert(0); 7976 } 7977 7978 /// 7979 immutable int width; 7980 7981 /// 7982 immutable int height; 7983 7984 /// 7985 immutable bool enableAlpha; 7986 //private: 7987 mixin NativeImageImplementation!() impl; 7988 } 7989 7990 /++ 7991 A convenience function to pop up a window displaying the image. 7992 If you pass a win, it will draw the image in it. Otherwise, it will 7993 create a window with the size of the image and run its event loop, closing 7994 when a key is pressed. 7995 7996 History: 7997 `BlockingMode` parameter added on December 8, 2021. Previously, it would 7998 always block until the application quit which could cause bizarre behavior 7999 inside a more complex application. Now, the default is to block until 8000 this window closes if it is the only event loop running, and otherwise, 8001 not to block at all and just pop up the display window asynchronously. 8002 +/ 8003 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8004 if(win is null) { 8005 win = new SimpleWindow(image); 8006 { 8007 auto p = win.draw; 8008 p.drawImage(Point(0, 0), image); 8009 } 8010 win.eventLoopWithBlockingMode( 8011 bm, 0, 8012 (KeyEvent ev) { 8013 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8014 } ); 8015 } else { 8016 win.image = image; 8017 } 8018 } 8019 8020 enum FontWeight : int { 8021 dontcare = 0, 8022 thin = 100, 8023 extralight = 200, 8024 light = 300, 8025 regular = 400, 8026 medium = 500, 8027 semibold = 600, 8028 bold = 700, 8029 extrabold = 800, 8030 heavy = 900 8031 } 8032 8033 /++ 8034 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8035 8036 History: 8037 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8038 +/ 8039 interface MeasurableFont { 8040 /++ 8041 Returns true if it is a monospace font, meaning each of the 8042 glyphs (at least the ascii characters) have matching width 8043 and no kerning, so you can determine the display width of some 8044 strings by simply multiplying the string width by [averageWidth]. 8045 8046 (Please note that multiply doesn't $(I actually) work in general, 8047 consider characters like tab and newline, but it does sometimes.) 8048 +/ 8049 bool isMonospace(); 8050 8051 /++ 8052 The average width of glyphs in the font, traditionally equal to the 8053 width of the lowercase x. Can be used to estimate bounding boxes, 8054 especially if the font [isMonospace]. 8055 8056 Given in pixels. 8057 +/ 8058 int averageWidth(); 8059 /++ 8060 The height of the bounding box of a line. 8061 +/ 8062 int height(); 8063 /++ 8064 The maximum ascent of a glyph above the baseline. 8065 8066 Given in pixels. 8067 +/ 8068 int ascent(); 8069 /++ 8070 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8071 8072 Given in pixels. 8073 +/ 8074 int descent(); 8075 /++ 8076 The display width of the given string, and if you provide a window, it will use it to 8077 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8078 8079 Given in pixels. 8080 +/ 8081 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8082 8083 } 8084 8085 // FIXME: i need a font cache and it needs to handle disconnects. 8086 8087 /++ 8088 Represents a font loaded off the operating system or the X server. 8089 8090 8091 While the api here is unified cross platform, the fonts are not necessarily 8092 available, even across machines of the same platform, so be sure to always check 8093 for null (using [isNull]) and have a fallback plan. 8094 8095 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8096 8097 Worst case, a null font will automatically fall back to the default font loaded 8098 for your system. 8099 +/ 8100 class OperatingSystemFont : MeasurableFont { 8101 // FIXME: when the X Connection is lost, these need to be invalidated! 8102 // that means I need to store the original stuff again to reconstruct it too. 8103 8104 version(X11) { 8105 XFontStruct* font; 8106 XFontSet fontset; 8107 8108 version(with_xft) { 8109 XftFont* xftFont; 8110 bool isXft; 8111 } 8112 } else version(Windows) { 8113 HFONT font; 8114 int width_; 8115 int height_; 8116 } else version(OSXCocoa) { 8117 // FIXME 8118 } else static assert(0); 8119 8120 /++ 8121 Constructs the class and immediately calls [load]. 8122 +/ 8123 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8124 load(name, size, weight, italic); 8125 } 8126 8127 /++ 8128 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8129 8130 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8131 8132 History: 8133 Added January 24, 2021. 8134 +/ 8135 this() { 8136 // this space intentionally left blank 8137 } 8138 8139 /++ 8140 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8141 8142 History: 8143 Added November 13, 2020. 8144 +/ 8145 version(with_xft) 8146 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8147 unload(); 8148 8149 if(!XftLibrary.attempted) { 8150 XftLibrary.loadDynamicLibrary(); 8151 } 8152 8153 if(!XftLibrary.loadSuccessful) 8154 return false; 8155 8156 auto display = XDisplayConnection.get; 8157 8158 char[256] nameBuffer = void; 8159 int nbp = 0; 8160 8161 void add(in char[] a) { 8162 nameBuffer[nbp .. nbp + a.length] = a[]; 8163 nbp += a.length; 8164 } 8165 add(name); 8166 8167 if(size) { 8168 add(":size="); 8169 add(toInternal!string(size)); 8170 } 8171 if(weight != FontWeight.dontcare) { 8172 add(":weight="); 8173 add(weightToString(weight)); 8174 } 8175 if(italic) 8176 add(":slant=100"); 8177 8178 nameBuffer[nbp] = 0; 8179 8180 this.xftFont = XftFontOpenName( 8181 display, 8182 DefaultScreen(display), 8183 nameBuffer.ptr 8184 ); 8185 8186 this.isXft = true; 8187 8188 if(xftFont !is null) { 8189 isMonospace_ = stringWidth("x") == stringWidth("M"); 8190 ascent_ = xftFont.ascent; 8191 descent_ = xftFont.descent; 8192 } 8193 8194 return !isNull(); 8195 } 8196 8197 /++ 8198 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8199 8200 8201 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. 8202 8203 If `pattern` is null, it returns all available font families. 8204 8205 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. 8206 8207 The format of the pattern is platform-specific. 8208 8209 History: 8210 Added May 1, 2021 (dub v9.5) 8211 +/ 8212 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8213 version(Windows) { 8214 auto hdc = GetDC(null); 8215 scope(exit) ReleaseDC(null, hdc); 8216 LOGFONT logfont; 8217 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8218 auto localHandler = *(cast(typeof(handler)*) p); 8219 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8220 } 8221 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8222 } else version(X11) { 8223 //import core.stdc.stdio; 8224 bool done = false; 8225 version(with_xft) { 8226 if(!XftLibrary.attempted) { 8227 XftLibrary.loadDynamicLibrary(); 8228 } 8229 8230 if(!XftLibrary.loadSuccessful) 8231 goto skipXft; 8232 8233 if(!FontConfigLibrary.attempted) 8234 FontConfigLibrary.loadDynamicLibrary(); 8235 if(!FontConfigLibrary.loadSuccessful) 8236 goto skipXft; 8237 8238 { 8239 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8240 if(got is null) 8241 goto skipXft; 8242 scope(exit) FcFontSetDestroy(got); 8243 8244 auto fontPatterns = got.fonts[0 .. got.nfont]; 8245 foreach(candidate; fontPatterns) { 8246 char* where, whereStyle; 8247 8248 char* pmg = FcNameUnparse(candidate); 8249 8250 //FcPatternGetString(candidate, "family", 0, &where); 8251 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8252 //if(where && whereStyle) { 8253 if(pmg) { 8254 if(!handler(pmg.sliceCString)) 8255 return; 8256 //printf("%s || %s %s\n", pmg, where, whereStyle); 8257 } 8258 } 8259 } 8260 } 8261 8262 skipXft: 8263 8264 if(pattern is null) 8265 pattern = "*"; 8266 8267 int count; 8268 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8269 scope(exit) XFreeFontNames(coreFontsRaw); 8270 8271 auto coreFonts = coreFontsRaw[0 .. count]; 8272 8273 foreach(font; coreFonts) { 8274 char[128] tmp; 8275 tmp[0 ..5] = "core:"; 8276 auto cf = font.sliceCString; 8277 if(5 + cf.length > tmp.length) 8278 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8279 tmp[5 .. 5 + cf.length] = cf; 8280 if(!handler(tmp[0 .. 5 + cf.length])) 8281 return; 8282 } 8283 } 8284 } 8285 8286 /++ 8287 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8288 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8289 8290 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8291 underlying system doesn't support returning the raw bytes. 8292 8293 History: 8294 Added September 10, 2021 (dub v10.3) 8295 +/ 8296 ubyte[] getTtfBytes() { 8297 if(isNull) 8298 return null; 8299 8300 version(Windows) { 8301 auto dc = GetDC(null); 8302 auto orig = SelectObject(dc, font); 8303 8304 scope(exit) { 8305 SelectObject(dc, orig); 8306 ReleaseDC(null, dc); 8307 } 8308 8309 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8310 if(res == GDI_ERROR) 8311 return null; 8312 8313 ubyte[] buffer = new ubyte[](res); 8314 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8315 if(res == GDI_ERROR) 8316 return null; // wtf really tbh 8317 8318 return buffer; 8319 } else version(with_xft) { 8320 if(isXft && xftFont) { 8321 if(!FontConfigLibrary.attempted) 8322 FontConfigLibrary.loadDynamicLibrary(); 8323 if(!FontConfigLibrary.loadSuccessful) 8324 return null; 8325 8326 char* file; 8327 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8328 if (file !is null && file[0]) { 8329 import core.stdc.stdio; 8330 auto fp = fopen(file, "rb"); 8331 if(fp is null) 8332 return null; 8333 scope(exit) 8334 fclose(fp); 8335 fseek(fp, 0, SEEK_END); 8336 ubyte[] buffer = new ubyte[](ftell(fp)); 8337 fseek(fp, 0, SEEK_SET); 8338 8339 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8340 if(got != buffer.length) 8341 return null; 8342 8343 return buffer; 8344 } 8345 } 8346 } 8347 return null; 8348 } 8349 } 8350 8351 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8352 8353 private string weightToString(FontWeight weight) { 8354 with(FontWeight) 8355 final switch(weight) { 8356 case dontcare: return "*"; 8357 case thin: return "extralight"; 8358 case extralight: return "extralight"; 8359 case light: return "light"; 8360 case regular: return "regular"; 8361 case medium: return "medium"; 8362 case semibold: return "demibold"; 8363 case bold: return "bold"; 8364 case extrabold: return "demibold"; 8365 case heavy: return "black"; 8366 } 8367 } 8368 8369 /++ 8370 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 8371 8372 History: 8373 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8374 +/ 8375 version(X11) 8376 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8377 unload(); 8378 8379 string xfontstr; 8380 8381 if(name.length > 3 && name[0 .. 3] == "-*-") { 8382 // this is kinda a disgusting hack but if the user sends an exact 8383 // string I'd like to honor it... 8384 xfontstr = name; 8385 } else { 8386 string weightstr = weightToString(weight); 8387 string sizestr; 8388 if(size == 0) 8389 sizestr = "*"; 8390 else 8391 sizestr = toInternal!string(size); 8392 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 8393 } 8394 8395 //import std.stdio; writeln(xfontstr); 8396 8397 auto display = XDisplayConnection.get; 8398 8399 font = XLoadQueryFont(display, xfontstr.ptr); 8400 if(font is null) 8401 return false; 8402 8403 char** lol; 8404 int lol2; 8405 char* lol3; 8406 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8407 8408 prepareFontInfo(); 8409 8410 return !isNull(); 8411 } 8412 8413 version(X11) 8414 private void prepareFontInfo() { 8415 if(font !is null) { 8416 isMonospace_ = stringWidth("l") == stringWidth("M"); 8417 ascent_ = font.max_bounds.ascent; 8418 descent_ = font.max_bounds.descent; 8419 } 8420 } 8421 8422 /++ 8423 Loads a Windows font. You probably want to use [load] instead to be more generic. 8424 8425 History: 8426 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8427 +/ 8428 version(Windows) 8429 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8430 unload(); 8431 8432 WCharzBuffer buffer = WCharzBuffer(name); 8433 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8434 8435 prepareFontInfo(hdc); 8436 8437 return !isNull(); 8438 } 8439 8440 version(Windows) 8441 void prepareFontInfo(HDC hdc = null) { 8442 if(font is null) 8443 return; 8444 8445 TEXTMETRIC tm; 8446 auto dc = hdc ? hdc : GetDC(null); 8447 auto orig = SelectObject(dc, font); 8448 GetTextMetrics(dc, &tm); 8449 SelectObject(dc, orig); 8450 if(hdc is null) 8451 ReleaseDC(null, dc); 8452 8453 width_ = tm.tmAveCharWidth; 8454 height_ = tm.tmHeight; 8455 ascent_ = tm.tmAscent; 8456 descent_ = tm.tmDescent; 8457 // 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. 8458 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 8459 } 8460 8461 8462 /++ 8463 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 8464 8465 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 8466 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 8467 8468 On Windows, it forwards directly to [loadWin32]. 8469 8470 Params: 8471 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 8472 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. 8473 weight = approximate boldness, results may vary. 8474 italic = try to get a slanted version of the given font. 8475 8476 History: 8477 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. 8478 +/ 8479 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8480 version(X11) { 8481 version(with_xft) { 8482 if(name.length > 5 && name[0 .. 5] == "core:") { 8483 goto core; 8484 } 8485 8486 if(loadXft(name, size, weight, italic)) 8487 return true; 8488 // if xft fails, fallback to core to avoid breaking 8489 // code that already depended on this. 8490 } 8491 8492 core: 8493 8494 if(name.length > 5 && name[0 .. 5] == "core:") { 8495 name = name[5 .. $]; 8496 } 8497 8498 return loadCoreX(name, size, weight, italic); 8499 } else version(Windows) { 8500 return loadWin32(name, size, weight, italic); 8501 } else version(OSXCocoa) { 8502 // FIXME 8503 return false; 8504 } else static assert(0); 8505 } 8506 8507 /// 8508 void unload() { 8509 if(isNull()) 8510 return; 8511 8512 version(X11) { 8513 auto display = XDisplayConnection.display; 8514 8515 if(display is null) 8516 return; 8517 8518 version(with_xft) { 8519 if(isXft) { 8520 if(xftFont) 8521 XftFontClose(display, xftFont); 8522 isXft = false; 8523 xftFont = null; 8524 return; 8525 } 8526 } 8527 8528 if(font && font !is ScreenPainterImplementation.defaultfont) 8529 XFreeFont(display, font); 8530 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 8531 XFreeFontSet(display, fontset); 8532 8533 font = null; 8534 fontset = null; 8535 } else version(Windows) { 8536 DeleteObject(font); 8537 font = null; 8538 } else version(OSXCocoa) { 8539 // FIXME 8540 } else static assert(0); 8541 } 8542 8543 private bool isMonospace_; 8544 8545 /++ 8546 History: 8547 Added January 16, 2021 8548 +/ 8549 bool isMonospace() { 8550 return isMonospace_; 8551 } 8552 8553 /++ 8554 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 8555 8556 History: 8557 Added March 26, 2020 8558 Documented January 16, 2021 8559 +/ 8560 int averageWidth() { 8561 version(X11) { 8562 return stringWidth("x"); 8563 } else version(Windows) 8564 return width_; 8565 else assert(0); 8566 } 8567 8568 /++ 8569 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 8570 8571 History: 8572 Added January 16, 2021 8573 +/ 8574 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 8575 // FIXME: what about tab? 8576 if(isNull) 8577 return 0; 8578 8579 version(X11) { 8580 version(with_xft) 8581 if(isXft && xftFont !is null) { 8582 //return xftFont.max_advance_width; 8583 XGlyphInfo extents; 8584 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 8585 //import std.stdio; writeln(extents); 8586 return extents.xOff; 8587 } 8588 if(font is null) 8589 return 0; 8590 else if(fontset) { 8591 XRectangle rect; 8592 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 8593 8594 return rect.width; 8595 } else { 8596 return XTextWidth(font, s.ptr, cast(int) s.length); 8597 } 8598 } else version(Windows) { 8599 WCharzBuffer buffer = WCharzBuffer(s); 8600 8601 return stringWidth(buffer.slice, window); 8602 } 8603 else assert(0); 8604 } 8605 8606 version(Windows) 8607 /// ditto 8608 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 8609 if(isNull) 8610 return 0; 8611 version(Windows) { 8612 SIZE size; 8613 8614 prepareContext(window); 8615 scope(exit) releaseContext(); 8616 8617 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 8618 8619 return size.cx; 8620 } else { 8621 // std.conv can do this easily but it is slow to import and i don't think it is worth it 8622 static assert(0, "not implemented yet"); 8623 //return stringWidth(s, window); 8624 } 8625 } 8626 8627 private { 8628 int prepRefcount; 8629 8630 version(Windows) { 8631 HDC dc; 8632 HANDLE orig; 8633 HWND hwnd; 8634 } 8635 } 8636 /++ 8637 [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. 8638 8639 History: 8640 Added January 23, 2021 8641 +/ 8642 void prepareContext(SimpleWindow window = null) { 8643 prepRefcount++; 8644 if(prepRefcount == 1) { 8645 version(Windows) { 8646 hwnd = window is null ? null : window.impl.hwnd; 8647 dc = GetDC(hwnd); 8648 orig = SelectObject(dc, font); 8649 } 8650 } 8651 } 8652 /// ditto 8653 void releaseContext() { 8654 prepRefcount--; 8655 if(prepRefcount == 0) { 8656 version(Windows) { 8657 SelectObject(dc, orig); 8658 ReleaseDC(hwnd, dc); 8659 hwnd = null; 8660 dc = null; 8661 orig = null; 8662 } 8663 } 8664 } 8665 8666 /+ 8667 FIXME: I think I need advance and kerning pair 8668 8669 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 8670 +/ 8671 8672 /++ 8673 Returns the height of the font. 8674 8675 History: 8676 Added March 26, 2020 8677 Documented January 16, 2021 8678 +/ 8679 int height() { 8680 version(X11) { 8681 version(with_xft) 8682 if(isXft && xftFont !is null) { 8683 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 8684 } 8685 if(font is null) 8686 return 0; 8687 return font.max_bounds.ascent + font.max_bounds.descent; 8688 } else version(Windows) 8689 return height_; 8690 else assert(0); 8691 } 8692 8693 private int ascent_; 8694 private int descent_; 8695 8696 /++ 8697 Max ascent above the baseline. 8698 8699 History: 8700 Added January 22, 2021 8701 +/ 8702 int ascent() { 8703 return ascent_; 8704 } 8705 8706 /++ 8707 Max descent below the baseline. 8708 8709 History: 8710 Added January 22, 2021 8711 +/ 8712 int descent() { 8713 return descent_; 8714 } 8715 8716 /++ 8717 Loads the default font used by [ScreenPainter] if none others are loaded. 8718 8719 Returns: 8720 This method mutates the `this` object, but then returns `this` for 8721 easy chaining like: 8722 8723 --- 8724 auto font = foo.isNull ? foo : foo.loadDefault 8725 --- 8726 8727 History: 8728 Added previously, but left unimplemented until January 24, 2021. 8729 +/ 8730 OperatingSystemFont loadDefault() { 8731 unload(); 8732 8733 version(X11) { 8734 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 8735 // but meh since sdpy does its own thing, this should be ok too 8736 8737 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8738 this.font = ScreenPainterImplementation.defaultfont; 8739 this.fontset = ScreenPainterImplementation.defaultfontset; 8740 8741 prepareFontInfo(); 8742 } else version(Windows) { 8743 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8744 this.font = ScreenPainterImplementation.defaultGuiFont; 8745 8746 prepareFontInfo(); 8747 } else throw new NotYetImplementedException(); 8748 8749 return this; 8750 } 8751 8752 /// 8753 bool isNull() { 8754 version(OSXCocoa) throw new NotYetImplementedException(); else { 8755 version(with_xft) 8756 if(isXft) 8757 return xftFont is null; 8758 return font is null; 8759 } 8760 } 8761 8762 /* Metrics */ 8763 /+ 8764 GetABCWidth 8765 GetKerningPairs 8766 8767 if I do it right, I can size it all here, and match 8768 what happens when I draw the full string with the OS functions. 8769 8770 subclasses might do the same thing while getting the glyphs on images 8771 struct GlyphInfo { 8772 int glyph; 8773 8774 size_t stringIdxStart; 8775 size_t stringIdxEnd; 8776 8777 Rectangle boundingBox; 8778 } 8779 GlyphInfo[] getCharBoxes() { 8780 // XftTextExtentsUtf8 8781 return null; 8782 8783 } 8784 +/ 8785 8786 ~this() { 8787 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 8788 unload(); 8789 } 8790 } 8791 8792 version(Windows) 8793 private string sliceCString(const(wchar)[] w) { 8794 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 8795 } 8796 8797 private inout(char)[] sliceCString(inout(char)* s) { 8798 import core.stdc.string; 8799 auto len = strlen(s); 8800 return s[0 .. len]; 8801 } 8802 8803 /** 8804 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 8805 than constructing it directly. Then, it is reference counted so you can pass it 8806 at around and when the last ref goes out of scope, the buffered drawing activities 8807 are all carried out. 8808 8809 8810 Most functions use the outlineColor instead of taking a color themselves. 8811 ScreenPainter is reference counted and draws its buffer to the screen when its 8812 final reference goes out of scope. 8813 */ 8814 struct ScreenPainter { 8815 CapableOfBeingDrawnUpon window; 8816 this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle, bool manualInvalidations) { 8817 this.window = window; 8818 if(window.closed) 8819 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 8820 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 8821 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 8822 if(window.activeScreenPainter !is null) { 8823 impl = window.activeScreenPainter; 8824 if(impl.referenceCount == 0) { 8825 impl.window = window; 8826 impl.create(handle); 8827 } 8828 impl.manualInvalidations = manualInvalidations; 8829 impl.referenceCount++; 8830 // writeln("refcount ++ ", impl.referenceCount); 8831 } else { 8832 impl = new ScreenPainterImplementation; 8833 impl.window = window; 8834 impl.create(handle); 8835 impl.referenceCount = 1; 8836 impl.manualInvalidations = manualInvalidations; 8837 window.activeScreenPainter = impl; 8838 //import std.stdio; writeln("constructed"); 8839 } 8840 8841 copyActiveOriginals(); 8842 } 8843 8844 /++ 8845 If you are using manual invalidations, this informs the 8846 window system that a section needs to be redrawn. 8847 8848 If you didn't opt into manual invalidation, you don't 8849 have to call this. 8850 8851 History: 8852 Added December 30, 2021 (dub v10.5) 8853 +/ 8854 void invalidateRect(Rectangle rect) { 8855 if(impl is null) return; 8856 8857 // transform(rect) 8858 rect.left += _originX; 8859 rect.right += _originX; 8860 rect.top += _originY; 8861 rect.bottom += _originY; 8862 8863 impl.invalidateRect(rect); 8864 } 8865 8866 private Pen originalPen; 8867 private Color originalFillColor; 8868 private arsd.color.Rectangle originalClipRectangle; 8869 void copyActiveOriginals() { 8870 if(impl is null) return; 8871 originalPen = impl._activePen; 8872 originalFillColor = impl._fillColor; 8873 originalClipRectangle = impl._clipRectangle; 8874 } 8875 8876 ~this() { 8877 if(impl is null) return; 8878 impl.referenceCount--; 8879 //writeln("refcount -- ", impl.referenceCount); 8880 if(impl.referenceCount == 0) { 8881 //import std.stdio; writeln("destructed"); 8882 impl.dispose(); 8883 *window.activeScreenPainter = ScreenPainterImplementation.init; 8884 //import std.stdio; writeln("paint finished"); 8885 } else { 8886 // there is still an active reference, reset stuff so the 8887 // next user doesn't get weirdness via the reference 8888 this.rasterOp = RasterOp.normal; 8889 pen = originalPen; 8890 fillColor = originalFillColor; 8891 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 8892 } 8893 } 8894 8895 this(this) { 8896 if(impl is null) return; 8897 impl.referenceCount++; 8898 //writeln("refcount ++ ", impl.referenceCount); 8899 8900 copyActiveOriginals(); 8901 } 8902 8903 private int _originX; 8904 private int _originY; 8905 @property int originX() { return _originX; } 8906 @property int originY() { return _originY; } 8907 @property int originX(int a) { 8908 _originX = a; 8909 return _originX; 8910 } 8911 @property int originY(int a) { 8912 _originY = a; 8913 return _originY; 8914 } 8915 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 8916 private void transform(ref Point p) { 8917 if(impl is null) return; 8918 p.x += _originX; 8919 p.y += _originY; 8920 } 8921 8922 // this needs to be checked BEFORE the originX/Y transformation 8923 private bool isClipped(Point p) { 8924 return !currentClipRectangle.contains(p); 8925 } 8926 private bool isClipped(Point p, int width, int height) { 8927 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 8928 } 8929 private bool isClipped(Point p, Size s) { 8930 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 8931 } 8932 private bool isClipped(Point p, Point p2) { 8933 // need to ensure the end points are actually included inside, so the +1 does that 8934 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 8935 } 8936 8937 8938 /++ 8939 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 8940 8941 Returns: 8942 The old clip rectangle. 8943 8944 History: 8945 Return value was `void` prior to May 10, 2021. 8946 8947 +/ 8948 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 8949 if(impl is null) return currentClipRectangle; 8950 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 8951 return currentClipRectangle; // no need to do anything 8952 auto old = currentClipRectangle; 8953 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 8954 transform(pt); 8955 8956 impl.setClipRectangle(pt.x, pt.y, width, height); 8957 8958 return old; 8959 } 8960 8961 /// ditto 8962 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 8963 if(impl is null) return currentClipRectangle; 8964 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 8965 } 8966 8967 /// 8968 void setFont(OperatingSystemFont font) { 8969 if(impl is null) return; 8970 impl.setFont(font); 8971 } 8972 8973 /// 8974 int fontHeight() { 8975 if(impl is null) return 0; 8976 return impl.fontHeight(); 8977 } 8978 8979 private Pen activePen; 8980 8981 /// 8982 @property void pen(Pen p) { 8983 if(impl is null) return; 8984 activePen = p; 8985 impl.pen(p); 8986 } 8987 8988 /// 8989 @scriptable 8990 @property void outlineColor(Color c) { 8991 if(impl is null) return; 8992 if(activePen.color == c) 8993 return; 8994 activePen.color = c; 8995 impl.pen(activePen); 8996 } 8997 8998 /// 8999 @scriptable 9000 @property void fillColor(Color c) { 9001 if(impl is null) return; 9002 impl.fillColor(c); 9003 } 9004 9005 /// 9006 @property void rasterOp(RasterOp op) { 9007 if(impl is null) return; 9008 impl.rasterOp(op); 9009 } 9010 9011 9012 void updateDisplay() { 9013 // FIXME this should do what the dtor does 9014 } 9015 9016 /// 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) 9017 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9018 if(impl is null) return; 9019 if(isClipped(upperLeft, width, height)) return; 9020 transform(upperLeft); 9021 version(Windows) { 9022 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9023 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9024 RECT clip = scroll; 9025 RECT uncovered; 9026 HRGN hrgn; 9027 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9028 throw new Exception("ScrollDC"); 9029 9030 } else version(X11) { 9031 // FIXME: clip stuff outside this rectangle 9032 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9033 } else version(OSXCocoa) { 9034 throw new NotYetImplementedException(); 9035 } else static assert(0); 9036 } 9037 9038 /// 9039 void clear(Color color = Color.white()) { 9040 if(impl is null) return; 9041 fillColor = color; 9042 outlineColor = color; 9043 drawRectangle(Point(0, 0), window.width, window.height); 9044 } 9045 9046 /++ 9047 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9048 9049 Params: 9050 upperLeft = point on the window where the upper left corner of the image will be drawn 9051 imageUpperLeft = point on the image to start the slice to draw 9052 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. 9053 History: 9054 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9055 +/ 9056 version(OSXCocoa) {} else // NotYetImplementedException 9057 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9058 if(impl is null) return; 9059 if(isClipped(upperLeft, s.width, s.height)) return; 9060 transform(upperLeft); 9061 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9062 } 9063 9064 /// 9065 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9066 if(impl is null) return; 9067 //if(isClipped(upperLeft, w, h)) return; // FIXME 9068 transform(upperLeft); 9069 if(w == 0 || w > i.width) 9070 w = i.width; 9071 if(h == 0 || h > i.height) 9072 h = i.height; 9073 if(upperLeftOfImage.x < 0) 9074 upperLeftOfImage.x = 0; 9075 if(upperLeftOfImage.y < 0) 9076 upperLeftOfImage.y = 0; 9077 9078 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9079 } 9080 9081 /// 9082 Size textSize(in char[] text) { 9083 if(impl is null) return Size(0, 0); 9084 return impl.textSize(text); 9085 } 9086 9087 /++ 9088 Draws a string in the window with the set font (see [setFont] to change it). 9089 9090 Params: 9091 upperLeft = the upper left point of the bounding box of the text 9092 text = the string to draw 9093 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9094 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9095 +/ 9096 @scriptable 9097 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9098 if(impl is null) return; 9099 if(lowerRight.x != 0 || lowerRight.y != 0) { 9100 if(isClipped(upperLeft, lowerRight)) return; 9101 transform(lowerRight); 9102 } else { 9103 if(isClipped(upperLeft, textSize(text))) return; 9104 } 9105 transform(upperLeft); 9106 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9107 } 9108 9109 /++ 9110 Draws text using a custom font. 9111 9112 This is still MAJOR work in progress. 9113 9114 Creating a [DrawableFont] can be tricky and require additional dependencies. 9115 +/ 9116 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9117 if(impl is null) return; 9118 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9119 transform(upperLeft); 9120 font.drawString(this, upperLeft, text); 9121 } 9122 9123 version(Windows) 9124 void drawText(Point upperLeft, scope const(wchar)[] text) { 9125 if(impl is null) return; 9126 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9127 transform(upperLeft); 9128 9129 if(text.length && text[$-1] == '\n') 9130 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9131 9132 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9133 } 9134 9135 static struct TextDrawingContext { 9136 Point boundingBoxUpperLeft; 9137 Point boundingBoxLowerRight; 9138 9139 Point currentLocation; 9140 9141 Point lastDrewUpperLeft; 9142 Point lastDrewLowerRight; 9143 9144 // how do i do right aligned rich text? 9145 // i kinda want to do a pre-made drawing then right align 9146 // draw the whole block. 9147 // 9148 // That's exactly the diff: inline vs block stuff. 9149 9150 // I need to get coordinates of an inline section out too, 9151 // not just a bounding box, but a series of bounding boxes 9152 // should be ok. Consider what's needed to detect a click 9153 // on a link in the middle of a paragraph breaking a line. 9154 // 9155 // Generally, we should be able to get the rectangles of 9156 // any portion we draw. 9157 // 9158 // It also needs to tell what text is left if it overflows 9159 // out of the box, so we can do stuff like float images around 9160 // it. It should not attempt to draw a letter that would be 9161 // clipped. 9162 // 9163 // I might also turn off word wrap stuff. 9164 } 9165 9166 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9167 if(impl is null) return; 9168 // FIXME 9169 } 9170 9171 /// Drawing an individual pixel is slow. Avoid it if possible. 9172 void drawPixel(Point where) { 9173 if(impl is null) return; 9174 if(isClipped(where)) return; 9175 transform(where); 9176 impl.drawPixel(where.x, where.y); 9177 } 9178 9179 9180 /// Draws a pen using the current pen / outlineColor 9181 @scriptable 9182 void drawLine(Point starting, Point ending) { 9183 if(impl is null) return; 9184 if(isClipped(starting, ending)) return; 9185 transform(starting); 9186 transform(ending); 9187 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9188 } 9189 9190 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9191 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9192 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9193 @scriptable 9194 void drawRectangle(Point upperLeft, int width, int height) { 9195 if(impl is null) return; 9196 if(isClipped(upperLeft, width, height)) return; 9197 transform(upperLeft); 9198 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9199 } 9200 9201 /// ditto 9202 void drawRectangle(Point upperLeft, Size size) { 9203 if(impl is null) return; 9204 if(isClipped(upperLeft, size.width, size.height)) return; 9205 transform(upperLeft); 9206 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9207 } 9208 9209 /// ditto 9210 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9211 if(impl is null) return; 9212 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9213 transform(upperLeft); 9214 transform(lowerRightInclusive); 9215 impl.drawRectangle(upperLeft.x, upperLeft.y, 9216 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9217 } 9218 9219 // overload added on May 12, 2021 9220 /// ditto 9221 void drawRectangle(Rectangle rect) { 9222 drawRectangle(rect.upperLeft, rect.size); 9223 } 9224 9225 /// Arguments are the points of the bounding rectangle 9226 void drawEllipse(Point upperLeft, Point lowerRight) { 9227 if(impl is null) return; 9228 if(isClipped(upperLeft, lowerRight)) return; 9229 transform(upperLeft); 9230 transform(lowerRight); 9231 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9232 } 9233 9234 /++ 9235 start and finish are units of degrees * 64 9236 9237 History: 9238 The Windows implementation didn't match the Linux implementation until September 24, 2021. 9239 9240 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9241 +/ 9242 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 9243 if(impl is null) return; 9244 // FIXME: not actually implemented 9245 if(isClipped(upperLeft, width, height)) return; 9246 transform(upperLeft); 9247 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 9248 } 9249 9250 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9251 void drawCircle(Point upperLeft, int diameter) { 9252 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9253 } 9254 9255 /// . 9256 void drawPolygon(Point[] vertexes) { 9257 if(impl is null) return; 9258 assert(vertexes.length); 9259 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 9260 foreach(ref vertex; vertexes) { 9261 if(vertex.x < minX) 9262 minX = vertex.x; 9263 if(vertex.y < minY) 9264 minY = vertex.y; 9265 if(vertex.x > maxX) 9266 maxX = vertex.x; 9267 if(vertex.y > maxY) 9268 maxY = vertex.y; 9269 transform(vertex); 9270 } 9271 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 9272 impl.drawPolygon(vertexes); 9273 } 9274 9275 /// ditto 9276 void drawPolygon(Point[] vertexes...) { 9277 if(impl is null) return; 9278 drawPolygon(vertexes); 9279 } 9280 9281 9282 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 9283 9284 //mixin NativeScreenPainterImplementation!() impl; 9285 9286 9287 // HACK: if I mixin the impl directly, it won't let me override the copy 9288 // constructor! The linker complains about there being multiple definitions. 9289 // I'll make the best of it and reference count it though. 9290 ScreenPainterImplementation* impl; 9291 } 9292 9293 // HACK: I need a pointer to the implementation so it's separate 9294 struct ScreenPainterImplementation { 9295 CapableOfBeingDrawnUpon window; 9296 int referenceCount; 9297 mixin NativeScreenPainterImplementation!(); 9298 } 9299 9300 // FIXME: i haven't actually tested the sprite class on MS Windows 9301 9302 /** 9303 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 9304 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 9305 9306 9307 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 9308 though I'm not sure that's ideal and the implementation might change. 9309 9310 You create one by giving a window and an image. It optimizes for that window, 9311 and copies the image into it to use as the initial picture. Creating a sprite 9312 can be quite slow (especially over a network connection) so you should do it 9313 as little as possible and just hold on to your sprite handles after making them. 9314 simpledisplay does try to do its best though, using the XSHM extension if available, 9315 but you should still write your code as if it will always be slow. 9316 9317 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 9318 a fast operation - much faster than drawing the Image itself every time. 9319 9320 `Sprite` represents a scarce resource which should be freed when you 9321 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 9322 after it has been disposed. If you are unsure about this, don't take chances, 9323 just let the garbage collector do it for you. But ideally, you can manage its 9324 lifetime more efficiently. 9325 9326 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 9327 support alpha blending in its drawing at this time. That might change in the 9328 future, but if you need alpha blending right now, use OpenGL instead. See 9329 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 9330 9331 Update: on April 23, 2021, I finally added alpha blending support. You must opt 9332 in by setting the enableAlpha = true in the constructor. 9333 */ 9334 version(OSXCocoa) {} else // NotYetImplementedException 9335 class Sprite : CapableOfBeingDrawnUpon { 9336 9337 /// 9338 ScreenPainter draw() { 9339 return ScreenPainter(this, handle, false); 9340 } 9341 9342 /++ 9343 Copies the sprite's current state into a [TrueColorImage]. 9344 9345 Be warned: this can be a very slow operation 9346 9347 History: 9348 Actually implemented on March 14, 2021 9349 +/ 9350 TrueColorImage takeScreenshot() { 9351 return trueColorImageFromNativeHandle(handle, width, height); 9352 } 9353 9354 void delegate() paintingFinishedDg() { return null; } 9355 bool closed() { return false; } 9356 ScreenPainterImplementation* activeScreenPainter_; 9357 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 9358 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 9359 9360 version(Windows) 9361 private ubyte* rawData; 9362 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 9363 // ditto on the XPicture stuff 9364 9365 version(X11) { 9366 private static XRenderPictFormat* RGB24; 9367 private static XRenderPictFormat* ARGB32; 9368 9369 private Picture xrenderPicture; 9370 } 9371 9372 version(X11) 9373 private static void requireXRender() { 9374 if(!XRenderLibrary.loadAttempted) { 9375 XRenderLibrary.loadDynamicLibrary(); 9376 } 9377 9378 if(!XRenderLibrary.loadSuccessful) 9379 throw new Exception("XRender library load failure"); 9380 9381 auto display = XDisplayConnection.get; 9382 9383 // FIXME: if we migrate X displays, these need to be changed 9384 if(RGB24 is null) 9385 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 9386 if(ARGB32 is null) 9387 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 9388 } 9389 9390 protected this() {} 9391 9392 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 9393 this._width = width; 9394 this._height = height; 9395 this.enableAlpha = enableAlpha; 9396 9397 version(X11) { 9398 auto display = XDisplayConnection.get(); 9399 9400 if(enableAlpha) { 9401 requireXRender(); 9402 } 9403 9404 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 9405 9406 if(enableAlpha) { 9407 XRenderPictureAttributes attrs; 9408 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 9409 } 9410 } else version(Windows) { 9411 version(CRuntime_DigitalMars) { 9412 //if(enableAlpha) 9413 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 9414 } 9415 9416 BITMAPINFO infoheader; 9417 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 9418 infoheader.bmiHeader.biWidth = width; 9419 infoheader.bmiHeader.biHeight = height; 9420 infoheader.bmiHeader.biPlanes = 1; 9421 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 9422 infoheader.bmiHeader.biCompression = BI_RGB; 9423 9424 // FIXME: this should prolly be a device dependent bitmap... 9425 handle = CreateDIBSection( 9426 null, 9427 &infoheader, 9428 DIB_RGB_COLORS, 9429 cast(void**) &rawData, 9430 null, 9431 0); 9432 9433 if(handle is null) 9434 throw new Exception("couldn't create pixmap"); 9435 } 9436 } 9437 9438 /// Makes a sprite based on the image with the initial contents from the Image 9439 this(SimpleWindow win, Image i) { 9440 this(win, i.width, i.height, i.enableAlpha); 9441 9442 version(X11) { 9443 auto display = XDisplayConnection.get(); 9444 auto gc = XCreateGC(display, this.handle, 0, null); 9445 scope(exit) XFreeGC(display, gc); 9446 if(i.usingXshm) 9447 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 9448 else 9449 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 9450 } else version(Windows) { 9451 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 9452 auto arrLength = itemsPerLine * height; 9453 rawData[0..arrLength] = i.rawData[0..arrLength]; 9454 } else version(OSXCocoa) { 9455 // FIXME: I have no idea if this is even any good 9456 ubyte* rawData; 9457 9458 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 9459 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 9460 colorSpace, 9461 kCGImageAlphaPremultipliedLast 9462 |kCGBitmapByteOrder32Big); 9463 CGColorSpaceRelease(colorSpace); 9464 rawData = CGBitmapContextGetData(context); 9465 9466 auto rdl = (width * height * 4); 9467 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 9468 } else static assert(0); 9469 } 9470 9471 /++ 9472 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 9473 9474 Params: 9475 where = point on the window where the upper left corner of the image will be drawn 9476 imageUpperLeft = point on the image to start the slice to draw 9477 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. 9478 History: 9479 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9480 +/ 9481 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9482 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 9483 } 9484 9485 /// Call this when you're ready to get rid of it 9486 void dispose() { 9487 version(X11) { 9488 staticDispose(xrenderPicture, handle); 9489 xrenderPicture = None; 9490 handle = None; 9491 } else version(Windows) { 9492 staticDispose(handle); 9493 handle = null; 9494 } else version(OSXCocoa) { 9495 staticDispose(context); 9496 context = null; 9497 } else static assert(0); 9498 9499 } 9500 9501 version(X11) 9502 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 9503 if(xrenderPicture) 9504 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 9505 if(handle) 9506 XFreePixmap(XDisplayConnection.get(), handle); 9507 } 9508 else version(Windows) 9509 static void staticDispose(HBITMAP handle) { 9510 if(handle) 9511 DeleteObject(handle); 9512 } 9513 else version(OSXCocoa) 9514 static void staticDispose(CGContextRef context) { 9515 if(context) 9516 CGContextRelease(context); 9517 } 9518 9519 ~this() { 9520 version(X11) { if(xrenderPicture || handle) 9521 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 9522 } else version(Windows) { if(handle) 9523 cleanupQueue.queue!staticDispose(handle); 9524 } else version(OSXCocoa) { if(context) 9525 cleanupQueue.queue!staticDispose(context); 9526 } else static assert(0); 9527 } 9528 9529 /// 9530 final @property int width() { return _width; } 9531 9532 /// 9533 final @property int height() { return _height; } 9534 9535 /// 9536 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 9537 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 9538 } 9539 9540 auto nativeHandle() { 9541 return handle; 9542 } 9543 9544 private: 9545 9546 int _width; 9547 int _height; 9548 bool enableAlpha; 9549 version(X11) 9550 Pixmap handle; 9551 else version(Windows) 9552 HBITMAP handle; 9553 else version(OSXCocoa) 9554 CGContextRef context; 9555 else static assert(0); 9556 } 9557 9558 /++ 9559 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 9560 9561 History: 9562 Added November 20, 2021 (dub v10.4) 9563 +/ 9564 abstract class Gradient : Sprite { 9565 protected this(int w, int h) { 9566 version(X11) { 9567 Sprite.requireXRender(); 9568 9569 super(); 9570 enableAlpha = true; 9571 _width = w; 9572 _height = h; 9573 } else version(Windows) { 9574 super(null, w, h, true); // on Windows i'm just making a bitmap myself 9575 } 9576 } 9577 9578 version(Windows) 9579 final void forEachPixel(scope Color delegate(int x, int y) dg) { 9580 auto ptr = rawData; 9581 foreach(j; 0 .. _height) 9582 foreach(i; 0 .. _width) { 9583 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 9584 *rawData = (color.a * color.b) / 255; rawData++; 9585 *rawData = (color.a * color.g) / 255; rawData++; 9586 *rawData = (color.a * color.r) / 255; rawData++; 9587 *rawData = color.a; rawData++; 9588 } 9589 } 9590 9591 version(X11) 9592 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 9593 assert(stops.length > 0); 9594 assert(stops.length <= 16, "I got lazy with buffers"); 9595 9596 XFixed[16] stopsPositions = void; 9597 XRenderColor[16] colors = void; 9598 9599 foreach(idx, stop; stops) { 9600 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 9601 auto c = stop.c; 9602 colors[idx] = XRenderColor( 9603 cast(ushort)(c.r * ushort.max / 255), 9604 cast(ushort)(c.g * ushort.max / 255), 9605 cast(ushort)(c.b * ushort.max / 255), 9606 cast(ushort)(c.a * ubyte.max) // max value here is fractional 9607 ); 9608 } 9609 9610 xrenderPicture = dg(stopsPositions, colors); 9611 } 9612 9613 /// 9614 static struct Stop { 9615 float percentage; /// between 0 and 1.0 9616 Color c; 9617 } 9618 } 9619 9620 /++ 9621 Creates a linear gradient between p1 and p2. 9622 9623 X ONLY RIGHT NOW 9624 9625 History: 9626 Added November 20, 2021 (dub v10.4) 9627 9628 Bugs: 9629 Not yet implemented on Windows. 9630 +/ 9631 class LinearGradient : Gradient { 9632 /++ 9633 9634 +/ 9635 this(Point p1, Point p2, Stop[] stops...) { 9636 super(p2.x, p2.y); 9637 9638 version(X11) { 9639 XLinearGradient gradient; 9640 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 9641 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 9642 9643 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9644 return XRenderCreateLinearGradient( 9645 XDisplayConnection.get, 9646 &gradient, 9647 stopsPositions.ptr, 9648 colors.ptr, 9649 cast(int) stops.length); 9650 }); 9651 } else version(Windows) { 9652 // FIXME 9653 forEachPixel((int x, int y) { 9654 import core.stdc.math; 9655 9656 //sqrtf( 9657 9658 return Color.transparent; 9659 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9660 }); 9661 } 9662 } 9663 } 9664 9665 /++ 9666 A conical gradient goes from color to color around a circumference from a center point. 9667 9668 X ONLY RIGHT NOW 9669 9670 History: 9671 Added November 20, 2021 (dub v10.4) 9672 9673 Bugs: 9674 Not yet implemented on Windows. 9675 +/ 9676 class ConicalGradient : Gradient { 9677 /++ 9678 9679 +/ 9680 this(Point center, float angleInDegrees, Stop[] stops...) { 9681 super(center.x * 2, center.y * 2); 9682 9683 version(X11) { 9684 XConicalGradient gradient; 9685 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 9686 gradient.angle = cast(int)(angleInDegrees * ushort.max); 9687 9688 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9689 return XRenderCreateConicalGradient( 9690 XDisplayConnection.get, 9691 &gradient, 9692 stopsPositions.ptr, 9693 colors.ptr, 9694 cast(int) stops.length); 9695 }); 9696 } else version(Windows) { 9697 // FIXME 9698 forEachPixel((int x, int y) { 9699 import core.stdc.math; 9700 9701 //sqrtf( 9702 9703 return Color.transparent; 9704 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9705 }); 9706 9707 } 9708 } 9709 } 9710 9711 /++ 9712 A radial gradient goes from color to color based on distance from the center. 9713 It is like rings of color. 9714 9715 X ONLY RIGHT NOW 9716 9717 9718 More specifically, you create two circles: an inner circle and an outer circle. 9719 The gradient is only drawn in the area outside the inner circle but inside the outer 9720 circle. The closest line between those two circles forms the line for the gradient 9721 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 9722 9723 History: 9724 Added November 20, 2021 (dub v10.4) 9725 9726 Bugs: 9727 Not yet implemented on Windows. 9728 +/ 9729 class RadialGradient : Gradient { 9730 /++ 9731 9732 +/ 9733 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 9734 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 9735 9736 version(X11) { 9737 XRadialGradient gradient; 9738 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 9739 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 9740 9741 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9742 return XRenderCreateRadialGradient( 9743 XDisplayConnection.get, 9744 &gradient, 9745 stopsPositions.ptr, 9746 colors.ptr, 9747 cast(int) stops.length); 9748 }); 9749 } else version(Windows) { 9750 // FIXME 9751 forEachPixel((int x, int y) { 9752 import core.stdc.math; 9753 9754 //sqrtf( 9755 9756 return Color.transparent; 9757 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9758 }); 9759 } 9760 } 9761 } 9762 9763 9764 9765 /+ 9766 NOT IMPLEMENTED 9767 9768 A display-stored image optimized for relatively quick drawing, like 9769 [Sprite], but this one supports alpha channel blending and does NOT 9770 support direct drawing upon it with a [ScreenPainter]. 9771 9772 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 9773 plain [ScreenPainter]... sort of. 9774 9775 On X11, it requires the Xrender extension and library. This is available 9776 almost everywhere though. 9777 9778 History: 9779 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 9780 +/ 9781 version(none) 9782 class AlphaSprite { 9783 /++ 9784 Copies the given image into it. 9785 +/ 9786 this(MemoryImage img) { 9787 9788 if(!XRenderLibrary.loadAttempted) { 9789 XRenderLibrary.loadDynamicLibrary(); 9790 9791 // FIXME: this needs to be reconstructed when the X server changes 9792 repopulateX(); 9793 } 9794 if(!XRenderLibrary.loadSuccessful) 9795 throw new Exception("XRender library load failure"); 9796 9797 // I probably need to put the alpha mask in a separate Picture 9798 // ugh 9799 // maybe the Sprite itself can have an alpha bitmask anyway 9800 9801 9802 auto display = XDisplayConnection.get(); 9803 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 9804 9805 9806 XRenderPictureAttributes attrs; 9807 9808 handle = XRenderCreatePicture( 9809 XDisplayConnection.get, 9810 pixmap, 9811 RGBA, 9812 0, 9813 &attrs 9814 ); 9815 9816 } 9817 9818 // maybe i'll use the create gradient functions too with static factories.. 9819 9820 void drawAt(ScreenPainter painter, Point where) { 9821 //painter.drawPixmap(this, where); 9822 9823 XRenderPictureAttributes attrs; 9824 9825 auto pic = XRenderCreatePicture( 9826 XDisplayConnection.get, 9827 painter.impl.d, 9828 RGB, 9829 0, 9830 &attrs 9831 ); 9832 9833 XRenderComposite( 9834 XDisplayConnection.get, 9835 3, // PictOpOver 9836 handle, 9837 None, 9838 pic, 9839 0, // src 9840 0, 9841 0, // mask 9842 0, 9843 10, // dest 9844 10, 9845 100, // width 9846 100 9847 ); 9848 9849 /+ 9850 XRenderFreePicture( 9851 XDisplayConnection.get, 9852 pic 9853 ); 9854 9855 XRenderFreePicture( 9856 XDisplayConnection.get, 9857 fill 9858 ); 9859 +/ 9860 // on Windows you can stretch but Xrender still can't :( 9861 } 9862 9863 static XRenderPictFormat* RGB; 9864 static XRenderPictFormat* RGBA; 9865 static void repopulateX() { 9866 auto display = XDisplayConnection.get; 9867 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 9868 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 9869 } 9870 9871 XPixmap pixmap; 9872 Picture handle; 9873 } 9874 9875 /// 9876 interface CapableOfBeingDrawnUpon { 9877 /// 9878 ScreenPainter draw(); 9879 /// 9880 int width(); 9881 /// 9882 int height(); 9883 protected ScreenPainterImplementation* activeScreenPainter(); 9884 protected void activeScreenPainter(ScreenPainterImplementation*); 9885 bool closed(); 9886 9887 void delegate() paintingFinishedDg(); 9888 9889 /// Be warned: this can be a very slow operation 9890 TrueColorImage takeScreenshot(); 9891 } 9892 9893 /// 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]. 9894 void flushGui() { 9895 version(X11) { 9896 auto dpy = XDisplayConnection.get(); 9897 XLockDisplay(dpy); 9898 scope(exit) XUnlockDisplay(dpy); 9899 XFlush(dpy); 9900 } 9901 } 9902 9903 /++ 9904 Runs the given code in the GUI thread when its event loop 9905 is available, blocking until it completes. This allows you 9906 to create and manipulate windows from another thread without 9907 invoking undefined behavior. 9908 9909 If this is the gui thread, it runs the code immediately. 9910 9911 If no gui thread exists yet, the current thread is assumed 9912 to be it. Attempting to create windows or run the event loop 9913 in any other thread will cause an assertion failure. 9914 9915 9916 $(TIP 9917 Did you know you can use UFCS on delegate literals? 9918 9919 () { 9920 // code here 9921 }.runInGuiThread; 9922 ) 9923 9924 Returns: 9925 `true` if the function was called, `false` if it was not. 9926 The function may not be called because the gui thread had 9927 already terminated by the time you called this. 9928 9929 History: 9930 Added April 10, 2020 (v7.2.0) 9931 9932 Return value added and implementation tweaked to avoid locking 9933 at program termination on February 24, 2021 (v9.2.1). 9934 +/ 9935 bool runInGuiThread(scope void delegate() dg) @trusted { 9936 claimGuiThread(); 9937 9938 if(thisIsGuiThread) { 9939 dg(); 9940 return true; 9941 } 9942 9943 if(guiThreadTerminating) 9944 return false; 9945 9946 import core.sync.semaphore; 9947 static Semaphore sc; 9948 if(sc is null) 9949 sc = new Semaphore(); 9950 9951 static RunQueueMember* rqm; 9952 if(rqm is null) 9953 rqm = new RunQueueMember; 9954 rqm.dg = cast(typeof(rqm.dg)) dg; 9955 rqm.signal = sc; 9956 rqm.thrown = null; 9957 9958 synchronized(runInGuiThreadLock) { 9959 runInGuiThreadQueue ~= rqm; 9960 } 9961 9962 if(!SimpleWindow.eventWakeUp()) 9963 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 9964 9965 rqm.signal.wait(); 9966 auto t = rqm.thrown; 9967 9968 if(t) 9969 throw t; 9970 9971 return true; 9972 } 9973 9974 // note it runs sync if this is the gui thread.... 9975 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 9976 claimGuiThread(); 9977 9978 try { 9979 9980 if(thisIsGuiThread) { 9981 dg(); 9982 return; 9983 } 9984 9985 if(guiThreadTerminating) 9986 return; 9987 9988 RunQueueMember* rqm = new RunQueueMember; 9989 rqm.dg = cast(typeof(rqm.dg)) dg; 9990 rqm.signal = null; 9991 rqm.thrown = null; 9992 9993 synchronized(runInGuiThreadLock) { 9994 runInGuiThreadQueue ~= rqm; 9995 } 9996 9997 if(!SimpleWindow.eventWakeUp()) 9998 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 9999 } catch(Exception e) { 10000 if(handleError) 10001 handleError(e); 10002 } 10003 } 10004 10005 private void runPendingRunInGuiThreadDelegates() { 10006 more: 10007 RunQueueMember* next; 10008 synchronized(runInGuiThreadLock) { 10009 if(runInGuiThreadQueue.length) { 10010 next = runInGuiThreadQueue[0]; 10011 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10012 } else { 10013 next = null; 10014 } 10015 } 10016 10017 if(next) { 10018 try { 10019 next.dg(); 10020 next.thrown = null; 10021 } catch(Throwable t) { 10022 next.thrown = t; 10023 } 10024 10025 if(next.signal) 10026 next.signal.notify(); 10027 10028 goto more; 10029 } 10030 } 10031 10032 private void claimGuiThread() nothrow { 10033 import core.atomic; 10034 if(cas(&guiThreadExists_, false, true)) 10035 thisIsGuiThread = true; 10036 } 10037 10038 private struct RunQueueMember { 10039 void delegate() dg; 10040 import core.sync.semaphore; 10041 Semaphore signal; 10042 Throwable thrown; 10043 } 10044 10045 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10046 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10047 private bool thisIsGuiThread = false; 10048 private shared bool guiThreadExists_ = false; 10049 private shared bool guiThreadTerminating = false; 10050 10051 /++ 10052 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10053 event loop. All windows must be exclusively created and managed by a single thread. 10054 10055 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10056 when you call one of its constructors. 10057 10058 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10059 one. If so, you can run gui functions on it. If not, don't. The helper functions 10060 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10061 10062 The reason this function is available is in case you want to message pass between a gui 10063 thread and your current thread. If no gui thread exists or if this is the gui thread, 10064 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10065 10066 History: 10067 Added December 3, 2021 (dub v10.5) 10068 +/ 10069 public bool guiThreadExists() { 10070 return guiThreadExists_; 10071 } 10072 10073 /++ 10074 Returns `true` if this thread is either running or set to be running the 10075 simpledisplay.d gui core event loop because it owns windows. 10076 10077 It is important to keep gui-related functionality in the right thread, so you will 10078 want to `runInGuiThread` when you call them (with some specific exceptions called 10079 out in those specific functions' documentation). Notably, all windows must be 10080 created and managed only from the gui thread. 10081 10082 Will return false if simpledisplay's other functions haven't been called 10083 yet; check [guiThreadExists] in addition to this. 10084 10085 History: 10086 Added December 3, 2021 (dub v10.5) 10087 +/ 10088 public bool thisThreadRunningGui() { 10089 return thisIsGuiThread; 10090 } 10091 10092 /++ 10093 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10094 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10095 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10096 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10097 file instead if you are in one of those situations). 10098 10099 It does not support outputting very many types; just strings and ints are likely to actually work. 10100 10101 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10102 is unspecified meaning I can change it at any time. The only point of this function is to help 10103 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10104 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10105 in those contexts. 10106 10107 $(WARNING 10108 I reserve the right to change this function at any time. You can use it if it helps you 10109 but do not rely on it for anything permanent. 10110 ) 10111 10112 History: 10113 Added December 3, 2021. Not formally supported under any stable tag. 10114 +/ 10115 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10116 try { 10117 version(Windows) { 10118 import core.sys.windows.wincon; 10119 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10120 AllocConsole(); 10121 const(char)* fn = "CONOUT$"; 10122 } else version(Posix) { 10123 const(char)* fn = "/dev/tty"; 10124 } else static assert(0, "Function not implemented for your system"); 10125 10126 if(fileOverride.length) 10127 fn = fileOverride.ptr; 10128 10129 import core.stdc.stdio; 10130 auto fp = fopen(fn, "wt"); 10131 if(fp is null) return; 10132 scope(exit) fclose(fp); 10133 10134 string str; 10135 foreach(item; t) { 10136 static if(is(typeof(item) : const(char)[])) 10137 str ~= item; 10138 else 10139 str ~= toInternal!string(item); 10140 str ~= " "; 10141 } 10142 str ~= "\n"; 10143 10144 fwrite(str.ptr, 1, str.length, fp); 10145 fflush(fp); 10146 } catch(Exception e) { 10147 // sorry no hope 10148 } 10149 } 10150 10151 private void guiThreadFinalize() { 10152 assert(thisIsGuiThread); 10153 10154 guiThreadTerminating = true; // don't add any more from this point on 10155 runPendingRunInGuiThreadDelegates(); 10156 } 10157 10158 /+ 10159 interface IPromise { 10160 void reportProgress(int current, int max, string message); 10161 10162 /+ // not formally in cuz of templates but still 10163 IPromise Then(); 10164 IPromise Catch(); 10165 IPromise Finally(); 10166 +/ 10167 } 10168 10169 /+ 10170 auto promise = async({ ... }); 10171 promise.Then(whatever). 10172 Then(whateverelse). 10173 Catch((exception) { }); 10174 10175 10176 A promise is run inside a fiber and it looks something like: 10177 10178 try { 10179 auto res = whatever(); 10180 auto res2 = whateverelse(res); 10181 } catch(Exception e) { 10182 { }(e); 10183 } 10184 10185 When a thing succeeds, it is passed as an arg to the next 10186 +/ 10187 class Promise(T) : IPromise { 10188 auto Then() { return null; } 10189 auto Catch() { return null; } 10190 auto Finally() { return null; } 10191 10192 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10193 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10194 T await(); 10195 } 10196 10197 interface Task { 10198 } 10199 10200 interface Resolvable(T) : Task { 10201 void run(); 10202 10203 void resolve(T); 10204 10205 Resolvable!T then(void delegate(T)); // returns a new promise 10206 Resolvable!T error(Throwable); // js catch 10207 Resolvable!T completed(); // js finally 10208 10209 } 10210 10211 /++ 10212 Runs `work` in a helper thread and sends its return value back to the main gui 10213 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10214 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10215 kill the program. 10216 10217 You can call reportProgress(position, max, message) to update your parent window 10218 on your progress. 10219 10220 I should also use `shared` methods. FIXME 10221 10222 History: 10223 Added March 6, 2021 (dub version 9.3). 10224 +/ 10225 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 10226 uponCompletion(work(null)); 10227 } 10228 10229 +/ 10230 10231 /// Used internal to dispatch events to various classes. 10232 interface CapableOfHandlingNativeEvent { 10233 NativeEventHandler getNativeEventHandler(); 10234 10235 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 10236 10237 version(X11) { 10238 // if this is impossible, you are allowed to just throw from it 10239 // Note: if you call it from another object, set a flag cuz the manger will call you again 10240 void recreateAfterDisconnect(); 10241 // discard any *connection specific* state, but keep enough that you 10242 // can be recreated if possible. discardConnectionState() is always called immediately 10243 // before recreateAfterDisconnect(), so you can set a flag there to decide if 10244 // you need initialization order 10245 void discardConnectionState(); 10246 } 10247 } 10248 10249 version(X11) 10250 /++ 10251 State of keys on mouse events, especially motion. 10252 10253 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 10254 +/ 10255 enum ModifierState : uint { 10256 shift = 1, /// 10257 capsLock = 2, /// 10258 ctrl = 4, /// 10259 alt = 8, /// Not always available on Windows 10260 windows = 64, /// ditto 10261 numLock = 16, /// 10262 10263 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10264 middleButtonDown = 512, /// ditto 10265 rightButtonDown = 1024, /// ditto 10266 } 10267 else version(Windows) 10268 /// ditto 10269 enum ModifierState : uint { 10270 shift = 4, /// 10271 ctrl = 8, /// 10272 10273 // i'm not sure if the next two are available 10274 alt = 256, /// not always available on Windows 10275 windows = 512, /// ditto 10276 10277 capsLock = 1024, /// 10278 numLock = 2048, /// 10279 10280 leftButtonDown = 1, /// not available on key events 10281 middleButtonDown = 16, /// ditto 10282 rightButtonDown = 2, /// ditto 10283 10284 backButtonDown = 0x20, /// not available on X 10285 forwardButtonDown = 0x40, /// ditto 10286 } 10287 else version(OSXCocoa) 10288 // FIXME FIXME NotYetImplementedException 10289 enum ModifierState : uint { 10290 shift = 1, /// 10291 capsLock = 2, /// 10292 ctrl = 4, /// 10293 alt = 8, /// Not always available on Windows 10294 windows = 64, /// ditto 10295 numLock = 16, /// 10296 10297 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10298 middleButtonDown = 512, /// ditto 10299 rightButtonDown = 1024, /// ditto 10300 } 10301 10302 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 10303 enum MouseButton : int { 10304 none = 0, 10305 left = 1, /// 10306 right = 2, /// 10307 middle = 4, /// 10308 wheelUp = 8, /// 10309 wheelDown = 16, /// 10310 backButton = 32, /// often found on the thumb and used for back in browsers 10311 forwardButton = 64, /// often found on the thumb and used for forward in browsers 10312 } 10313 10314 version(X11) { 10315 // FIXME: match ASCII whenever we can. Most of it is already there, 10316 // but there's a few exceptions and mismatches with Windows 10317 10318 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 10319 enum Key { 10320 Escape = 0xff1b, /// 10321 F1 = 0xffbe, /// 10322 F2 = 0xffbf, /// 10323 F3 = 0xffc0, /// 10324 F4 = 0xffc1, /// 10325 F5 = 0xffc2, /// 10326 F6 = 0xffc3, /// 10327 F7 = 0xffc4, /// 10328 F8 = 0xffc5, /// 10329 F9 = 0xffc6, /// 10330 F10 = 0xffc7, /// 10331 F11 = 0xffc8, /// 10332 F12 = 0xffc9, /// 10333 PrintScreen = 0xff61, /// 10334 ScrollLock = 0xff14, /// 10335 Pause = 0xff13, /// 10336 Grave = 0x60, /// The $(BACKTICK) ~ key 10337 // number keys across the top of the keyboard 10338 N1 = 0x31, /// Number key atop the keyboard 10339 N2 = 0x32, /// 10340 N3 = 0x33, /// 10341 N4 = 0x34, /// 10342 N5 = 0x35, /// 10343 N6 = 0x36, /// 10344 N7 = 0x37, /// 10345 N8 = 0x38, /// 10346 N9 = 0x39, /// 10347 N0 = 0x30, /// 10348 Dash = 0x2d, /// 10349 Equals = 0x3d, /// 10350 Backslash = 0x5c, /// The \ | key 10351 Backspace = 0xff08, /// 10352 Insert = 0xff63, /// 10353 Home = 0xff50, /// 10354 PageUp = 0xff55, /// 10355 Delete = 0xffff, /// 10356 End = 0xff57, /// 10357 PageDown = 0xff56, /// 10358 Up = 0xff52, /// 10359 Down = 0xff54, /// 10360 Left = 0xff51, /// 10361 Right = 0xff53, /// 10362 10363 Tab = 0xff09, /// 10364 Q = 0x71, /// 10365 W = 0x77, /// 10366 E = 0x65, /// 10367 R = 0x72, /// 10368 T = 0x74, /// 10369 Y = 0x79, /// 10370 U = 0x75, /// 10371 I = 0x69, /// 10372 O = 0x6f, /// 10373 P = 0x70, /// 10374 LeftBracket = 0x5b, /// the [ { key 10375 RightBracket = 0x5d, /// the ] } key 10376 CapsLock = 0xffe5, /// 10377 A = 0x61, /// 10378 S = 0x73, /// 10379 D = 0x64, /// 10380 F = 0x66, /// 10381 G = 0x67, /// 10382 H = 0x68, /// 10383 J = 0x6a, /// 10384 K = 0x6b, /// 10385 L = 0x6c, /// 10386 Semicolon = 0x3b, /// 10387 Apostrophe = 0x27, /// 10388 Enter = 0xff0d, /// 10389 Shift = 0xffe1, /// 10390 Z = 0x7a, /// 10391 X = 0x78, /// 10392 C = 0x63, /// 10393 V = 0x76, /// 10394 B = 0x62, /// 10395 N = 0x6e, /// 10396 M = 0x6d, /// 10397 Comma = 0x2c, /// 10398 Period = 0x2e, /// 10399 Slash = 0x2f, /// the / ? key 10400 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 10401 Ctrl = 0xffe3, /// 10402 Windows = 0xffeb, /// 10403 Alt = 0xffe9, /// 10404 Space = 0x20, /// 10405 Alt_r = 0xffea, /// ditto of shift_r 10406 Windows_r = 0xffec, /// 10407 Menu = 0xff67, /// 10408 Ctrl_r = 0xffe4, /// 10409 10410 NumLock = 0xff7f, /// 10411 Divide = 0xffaf, /// The / key on the number pad 10412 Multiply = 0xffaa, /// The * key on the number pad 10413 Minus = 0xffad, /// The - key on the number pad 10414 Plus = 0xffab, /// The + key on the number pad 10415 PadEnter = 0xff8d, /// Numberpad enter key 10416 Pad1 = 0xff9c, /// Numberpad keys 10417 Pad2 = 0xff99, /// 10418 Pad3 = 0xff9b, /// 10419 Pad4 = 0xff96, /// 10420 Pad5 = 0xff9d, /// 10421 Pad6 = 0xff98, /// 10422 Pad7 = 0xff95, /// 10423 Pad8 = 0xff97, /// 10424 Pad9 = 0xff9a, /// 10425 Pad0 = 0xff9e, /// 10426 PadDot = 0xff9f, /// 10427 } 10428 } else version(Windows) { 10429 // the character here is for en-us layouts and for illustration only 10430 // if you actually want to get characters, wait for character events 10431 // (the argument to your event handler is simply a dchar) 10432 // those will be converted by the OS for the right locale. 10433 10434 enum Key { 10435 Escape = 0x1b, 10436 F1 = 0x70, 10437 F2 = 0x71, 10438 F3 = 0x72, 10439 F4 = 0x73, 10440 F5 = 0x74, 10441 F6 = 0x75, 10442 F7 = 0x76, 10443 F8 = 0x77, 10444 F9 = 0x78, 10445 F10 = 0x79, 10446 F11 = 0x7a, 10447 F12 = 0x7b, 10448 PrintScreen = 0x2c, 10449 ScrollLock = 0x91, 10450 Pause = 0x13, 10451 Grave = 0xc0, 10452 // number keys across the top of the keyboard 10453 N1 = 0x31, 10454 N2 = 0x32, 10455 N3 = 0x33, 10456 N4 = 0x34, 10457 N5 = 0x35, 10458 N6 = 0x36, 10459 N7 = 0x37, 10460 N8 = 0x38, 10461 N9 = 0x39, 10462 N0 = 0x30, 10463 Dash = 0xbd, 10464 Equals = 0xbb, 10465 Backslash = 0xdc, 10466 Backspace = 0x08, 10467 Insert = 0x2d, 10468 Home = 0x24, 10469 PageUp = 0x21, 10470 Delete = 0x2e, 10471 End = 0x23, 10472 PageDown = 0x22, 10473 Up = 0x26, 10474 Down = 0x28, 10475 Left = 0x25, 10476 Right = 0x27, 10477 10478 Tab = 0x09, 10479 Q = 0x51, 10480 W = 0x57, 10481 E = 0x45, 10482 R = 0x52, 10483 T = 0x54, 10484 Y = 0x59, 10485 U = 0x55, 10486 I = 0x49, 10487 O = 0x4f, 10488 P = 0x50, 10489 LeftBracket = 0xdb, 10490 RightBracket = 0xdd, 10491 CapsLock = 0x14, 10492 A = 0x41, 10493 S = 0x53, 10494 D = 0x44, 10495 F = 0x46, 10496 G = 0x47, 10497 H = 0x48, 10498 J = 0x4a, 10499 K = 0x4b, 10500 L = 0x4c, 10501 Semicolon = 0xba, 10502 Apostrophe = 0xde, 10503 Enter = 0x0d, 10504 Shift = 0x10, 10505 Z = 0x5a, 10506 X = 0x58, 10507 C = 0x43, 10508 V = 0x56, 10509 B = 0x42, 10510 N = 0x4e, 10511 M = 0x4d, 10512 Comma = 0xbc, 10513 Period = 0xbe, 10514 Slash = 0xbf, 10515 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10516 Ctrl = 0x11, 10517 Windows = 0x5b, 10518 Alt = -5, // FIXME 10519 Space = 0x20, 10520 Alt_r = 0xffea, // ditto of shift_r 10521 Windows_r = 0x5c, // ditto of shift_r 10522 Menu = 0x5d, 10523 Ctrl_r = 0xa3, // ditto of shift_r 10524 10525 NumLock = 0x90, 10526 Divide = 0x6f, 10527 Multiply = 0x6a, 10528 Minus = 0x6d, 10529 Plus = 0x6b, 10530 PadEnter = -8, // FIXME 10531 Pad1 = 0x61, 10532 Pad2 = 0x62, 10533 Pad3 = 0x63, 10534 Pad4 = 0x64, 10535 Pad5 = 0x65, 10536 Pad6 = 0x66, 10537 Pad7 = 0x67, 10538 Pad8 = 0x68, 10539 Pad9 = 0x69, 10540 Pad0 = 0x60, 10541 PadDot = 0x6e, 10542 } 10543 10544 // I'm keeping this around for reference purposes 10545 // ideally all these buttons will be listed for all platforms, 10546 // but now now I'm just focusing on my US keyboard 10547 version(none) 10548 enum Key { 10549 LBUTTON = 0x01, 10550 RBUTTON = 0x02, 10551 CANCEL = 0x03, 10552 MBUTTON = 0x04, 10553 //static if (_WIN32_WINNT > = 0x500) { 10554 XBUTTON1 = 0x05, 10555 XBUTTON2 = 0x06, 10556 //} 10557 BACK = 0x08, 10558 TAB = 0x09, 10559 CLEAR = 0x0C, 10560 RETURN = 0x0D, 10561 SHIFT = 0x10, 10562 CONTROL = 0x11, 10563 MENU = 0x12, 10564 PAUSE = 0x13, 10565 CAPITAL = 0x14, 10566 KANA = 0x15, 10567 HANGEUL = 0x15, 10568 HANGUL = 0x15, 10569 JUNJA = 0x17, 10570 FINAL = 0x18, 10571 HANJA = 0x19, 10572 KANJI = 0x19, 10573 ESCAPE = 0x1B, 10574 CONVERT = 0x1C, 10575 NONCONVERT = 0x1D, 10576 ACCEPT = 0x1E, 10577 MODECHANGE = 0x1F, 10578 SPACE = 0x20, 10579 PRIOR = 0x21, 10580 NEXT = 0x22, 10581 END = 0x23, 10582 HOME = 0x24, 10583 LEFT = 0x25, 10584 UP = 0x26, 10585 RIGHT = 0x27, 10586 DOWN = 0x28, 10587 SELECT = 0x29, 10588 PRINT = 0x2A, 10589 EXECUTE = 0x2B, 10590 SNAPSHOT = 0x2C, 10591 INSERT = 0x2D, 10592 DELETE = 0x2E, 10593 HELP = 0x2F, 10594 LWIN = 0x5B, 10595 RWIN = 0x5C, 10596 APPS = 0x5D, 10597 SLEEP = 0x5F, 10598 NUMPAD0 = 0x60, 10599 NUMPAD1 = 0x61, 10600 NUMPAD2 = 0x62, 10601 NUMPAD3 = 0x63, 10602 NUMPAD4 = 0x64, 10603 NUMPAD5 = 0x65, 10604 NUMPAD6 = 0x66, 10605 NUMPAD7 = 0x67, 10606 NUMPAD8 = 0x68, 10607 NUMPAD9 = 0x69, 10608 MULTIPLY = 0x6A, 10609 ADD = 0x6B, 10610 SEPARATOR = 0x6C, 10611 SUBTRACT = 0x6D, 10612 DECIMAL = 0x6E, 10613 DIVIDE = 0x6F, 10614 F1 = 0x70, 10615 F2 = 0x71, 10616 F3 = 0x72, 10617 F4 = 0x73, 10618 F5 = 0x74, 10619 F6 = 0x75, 10620 F7 = 0x76, 10621 F8 = 0x77, 10622 F9 = 0x78, 10623 F10 = 0x79, 10624 F11 = 0x7A, 10625 F12 = 0x7B, 10626 F13 = 0x7C, 10627 F14 = 0x7D, 10628 F15 = 0x7E, 10629 F16 = 0x7F, 10630 F17 = 0x80, 10631 F18 = 0x81, 10632 F19 = 0x82, 10633 F20 = 0x83, 10634 F21 = 0x84, 10635 F22 = 0x85, 10636 F23 = 0x86, 10637 F24 = 0x87, 10638 NUMLOCK = 0x90, 10639 SCROLL = 0x91, 10640 LSHIFT = 0xA0, 10641 RSHIFT = 0xA1, 10642 LCONTROL = 0xA2, 10643 RCONTROL = 0xA3, 10644 LMENU = 0xA4, 10645 RMENU = 0xA5, 10646 //static if (_WIN32_WINNT > = 0x500) { 10647 BROWSER_BACK = 0xA6, 10648 BROWSER_FORWARD = 0xA7, 10649 BROWSER_REFRESH = 0xA8, 10650 BROWSER_STOP = 0xA9, 10651 BROWSER_SEARCH = 0xAA, 10652 BROWSER_FAVORITES = 0xAB, 10653 BROWSER_HOME = 0xAC, 10654 VOLUME_MUTE = 0xAD, 10655 VOLUME_DOWN = 0xAE, 10656 VOLUME_UP = 0xAF, 10657 MEDIA_NEXT_TRACK = 0xB0, 10658 MEDIA_PREV_TRACK = 0xB1, 10659 MEDIA_STOP = 0xB2, 10660 MEDIA_PLAY_PAUSE = 0xB3, 10661 LAUNCH_MAIL = 0xB4, 10662 LAUNCH_MEDIA_SELECT = 0xB5, 10663 LAUNCH_APP1 = 0xB6, 10664 LAUNCH_APP2 = 0xB7, 10665 //} 10666 OEM_1 = 0xBA, 10667 //static if (_WIN32_WINNT > = 0x500) { 10668 OEM_PLUS = 0xBB, 10669 OEM_COMMA = 0xBC, 10670 OEM_MINUS = 0xBD, 10671 OEM_PERIOD = 0xBE, 10672 //} 10673 OEM_2 = 0xBF, 10674 OEM_3 = 0xC0, 10675 OEM_4 = 0xDB, 10676 OEM_5 = 0xDC, 10677 OEM_6 = 0xDD, 10678 OEM_7 = 0xDE, 10679 OEM_8 = 0xDF, 10680 //static if (_WIN32_WINNT > = 0x500) { 10681 OEM_102 = 0xE2, 10682 //} 10683 PROCESSKEY = 0xE5, 10684 //static if (_WIN32_WINNT > = 0x500) { 10685 PACKET = 0xE7, 10686 //} 10687 ATTN = 0xF6, 10688 CRSEL = 0xF7, 10689 EXSEL = 0xF8, 10690 EREOF = 0xF9, 10691 PLAY = 0xFA, 10692 ZOOM = 0xFB, 10693 NONAME = 0xFC, 10694 PA1 = 0xFD, 10695 OEM_CLEAR = 0xFE, 10696 } 10697 10698 } else version(OSXCocoa) { 10699 // FIXME 10700 enum Key { 10701 Escape = 0x1b, 10702 F1 = 0x70, 10703 F2 = 0x71, 10704 F3 = 0x72, 10705 F4 = 0x73, 10706 F5 = 0x74, 10707 F6 = 0x75, 10708 F7 = 0x76, 10709 F8 = 0x77, 10710 F9 = 0x78, 10711 F10 = 0x79, 10712 F11 = 0x7a, 10713 F12 = 0x7b, 10714 PrintScreen = 0x2c, 10715 ScrollLock = -2, // FIXME 10716 Pause = -3, // FIXME 10717 Grave = 0xc0, 10718 // number keys across the top of the keyboard 10719 N1 = 0x31, 10720 N2 = 0x32, 10721 N3 = 0x33, 10722 N4 = 0x34, 10723 N5 = 0x35, 10724 N6 = 0x36, 10725 N7 = 0x37, 10726 N8 = 0x38, 10727 N9 = 0x39, 10728 N0 = 0x30, 10729 Dash = 0xbd, 10730 Equals = 0xbb, 10731 Backslash = 0xdc, 10732 Backspace = 0x08, 10733 Insert = 0x2d, 10734 Home = 0x24, 10735 PageUp = 0x21, 10736 Delete = 0x2e, 10737 End = 0x23, 10738 PageDown = 0x22, 10739 Up = 0x26, 10740 Down = 0x28, 10741 Left = 0x25, 10742 Right = 0x27, 10743 10744 Tab = 0x09, 10745 Q = 0x51, 10746 W = 0x57, 10747 E = 0x45, 10748 R = 0x52, 10749 T = 0x54, 10750 Y = 0x59, 10751 U = 0x55, 10752 I = 0x49, 10753 O = 0x4f, 10754 P = 0x50, 10755 LeftBracket = 0xdb, 10756 RightBracket = 0xdd, 10757 CapsLock = 0x14, 10758 A = 0x41, 10759 S = 0x53, 10760 D = 0x44, 10761 F = 0x46, 10762 G = 0x47, 10763 H = 0x48, 10764 J = 0x4a, 10765 K = 0x4b, 10766 L = 0x4c, 10767 Semicolon = 0xba, 10768 Apostrophe = 0xde, 10769 Enter = 0x0d, 10770 Shift = 0x10, 10771 Z = 0x5a, 10772 X = 0x58, 10773 C = 0x43, 10774 V = 0x56, 10775 B = 0x42, 10776 N = 0x4e, 10777 M = 0x4d, 10778 Comma = 0xbc, 10779 Period = 0xbe, 10780 Slash = 0xbf, 10781 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10782 Ctrl = 0x11, 10783 Windows = 0x5b, 10784 Alt = -5, // FIXME 10785 Space = 0x20, 10786 Alt_r = 0xffea, // ditto of shift_r 10787 Windows_r = -6, // FIXME 10788 Menu = 0x5d, 10789 Ctrl_r = -7, // FIXME 10790 10791 NumLock = 0x90, 10792 Divide = 0x6f, 10793 Multiply = 0x6a, 10794 Minus = 0x6d, 10795 Plus = 0x6b, 10796 PadEnter = -8, // FIXME 10797 // FIXME for the rest of these: 10798 Pad1 = 0xff9c, 10799 Pad2 = 0xff99, 10800 Pad3 = 0xff9b, 10801 Pad4 = 0xff96, 10802 Pad5 = 0xff9d, 10803 Pad6 = 0xff98, 10804 Pad7 = 0xff95, 10805 Pad8 = 0xff97, 10806 Pad9 = 0xff9a, 10807 Pad0 = 0xff9e, 10808 PadDot = 0xff9f, 10809 } 10810 10811 } 10812 10813 /* Additional utilities */ 10814 10815 10816 Color fromHsl(real h, real s, real l) { 10817 return arsd.color.fromHsl([h,s,l]); 10818 } 10819 10820 10821 10822 /* ********** What follows is the system-specific implementations *********/ 10823 version(Windows) { 10824 10825 10826 // helpers for making HICONs from MemoryImages 10827 class WindowsIcon { 10828 struct Win32Icon(int colorCount) { 10829 align(1): 10830 uint biSize; 10831 int biWidth; 10832 int biHeight; 10833 ushort biPlanes; 10834 ushort biBitCount; 10835 uint biCompression; 10836 uint biSizeImage; 10837 int biXPelsPerMeter; 10838 int biYPelsPerMeter; 10839 uint biClrUsed; 10840 uint biClrImportant; 10841 RGBQUAD[colorCount] biColors; 10842 /* Pixels: 10843 Uint8 pixels[] 10844 */ 10845 /* Mask: 10846 Uint8 mask[] 10847 */ 10848 10849 ubyte[4096] data; 10850 10851 void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 10852 width = mi.width; 10853 height = mi.height; 10854 10855 auto indexedImage = cast(IndexedImage) mi; 10856 if(indexedImage is null) 10857 indexedImage = quantize(mi.getAsTrueColorImage()); 10858 10859 assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy 10860 assert(height %4 == 0); 10861 10862 int icon_plen = height*((width+3)&~3); 10863 int icon_mlen = height*((((width+7)/8)+3)&~3); 10864 icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 10865 10866 biSize = 40; 10867 biWidth = width; 10868 biHeight = height*2; 10869 biPlanes = 1; 10870 biBitCount = 8; 10871 biSizeImage = icon_plen+icon_mlen; 10872 10873 int offset = 0; 10874 int andOff = icon_plen * 8; // the and offset is in bits 10875 for(int y = height - 1; y >= 0; y--) { 10876 int off2 = y * width; 10877 foreach(x; 0 .. width) { 10878 const b = indexedImage.data[off2 + x]; 10879 data[offset] = b; 10880 offset++; 10881 10882 const andBit = andOff % 8; 10883 const andIdx = andOff / 8; 10884 assert(b < indexedImage.palette.length); 10885 // this is anded to the destination, since and 0 means erase, 10886 // we want that to be opaque, and 1 for transparent 10887 auto transparent = (indexedImage.palette[b].a <= 127); 10888 data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0); 10889 10890 andOff++; 10891 } 10892 10893 andOff += andOff % 32; 10894 } 10895 10896 foreach(idx, entry; indexedImage.palette) { 10897 if(entry.a > 127) { 10898 biColors[idx].rgbBlue = entry.b; 10899 biColors[idx].rgbGreen = entry.g; 10900 biColors[idx].rgbRed = entry.r; 10901 } else { 10902 biColors[idx].rgbBlue = 255; 10903 biColors[idx].rgbGreen = 255; 10904 biColors[idx].rgbRed = 255; 10905 } 10906 } 10907 10908 /* 10909 data[0..icon_plen] = getFlippedUnfilteredDatastream(png); 10910 data[icon_plen..icon_plen+icon_mlen] = getANDMask(png); 10911 //icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0); 10912 auto pngMap = fetchPaletteWin32(png); 10913 biColors[0..pngMap.length] = pngMap[]; 10914 */ 10915 } 10916 } 10917 10918 10919 Win32Icon!(256) icon_win32; 10920 10921 10922 this(MemoryImage mi) { 10923 int icon_len, width, height; 10924 10925 icon_win32.fromMemoryImage(mi, icon_len, width, height); 10926 10927 /* 10928 PNG* png = readPnpngData); 10929 PNGHeader pngh = getHeader(png); 10930 void* icon_win32; 10931 if(pngh.depth == 4) { 10932 auto i = new Win32Icon!(16); 10933 i.fromPNG(png, pngh, icon_len, width, height); 10934 icon_win32 = i; 10935 } 10936 else if(pngh.depth == 8) { 10937 auto i = new Win32Icon!(256); 10938 i.fromPNG(png, pngh, icon_len, width, height); 10939 icon_win32 = i; 10940 } else assert(0); 10941 */ 10942 10943 hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0); 10944 10945 if(hIcon is null) throw new Exception("CreateIconFromResourceEx"); 10946 } 10947 10948 ~this() { 10949 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 10950 DestroyIcon(hIcon); 10951 } 10952 10953 HICON hIcon; 10954 } 10955 10956 10957 10958 10959 10960 10961 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 10962 alias HWND NativeWindowHandle; 10963 10964 extern(Windows) 10965 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 10966 try { 10967 if(SimpleWindow.handleNativeGlobalEvent !is null) { 10968 // it returns zero if the message is handled, so we won't do anything more there 10969 // do I like that though? 10970 int mustReturn; 10971 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 10972 if(mustReturn) 10973 return ret; 10974 } 10975 10976 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 10977 if(window.getNativeEventHandler !is null) { 10978 int mustReturn; 10979 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 10980 if(mustReturn) 10981 return ret; 10982 } 10983 if(auto w = cast(SimpleWindow) (*window)) 10984 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 10985 else 10986 return DefWindowProc(hWnd, iMessage, wParam, lParam); 10987 } else { 10988 return DefWindowProc(hWnd, iMessage, wParam, lParam); 10989 } 10990 } catch (Exception e) { 10991 try { 10992 sdpy_abort(e); 10993 return 0; 10994 } catch(Exception e) { assert(0); } 10995 } 10996 } 10997 10998 void sdpy_abort(Throwable e) nothrow { 10999 try 11000 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11001 catch(Exception e) 11002 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11003 ExitProcess(1); 11004 } 11005 11006 mixin template NativeScreenPainterImplementation() { 11007 HDC hdc; 11008 HWND hwnd; 11009 //HDC windowHdc; 11010 HBITMAP oldBmp; 11011 11012 void create(NativeWindowHandle window) { 11013 hwnd = window; 11014 11015 if(auto sw = cast(SimpleWindow) this.window) { 11016 // drawing on a window, double buffer 11017 auto windowHdc = GetDC(hwnd); 11018 11019 auto buffer = sw.impl.buffer; 11020 if(buffer is null) { 11021 hdc = windowHdc; 11022 windowDc = true; 11023 } else { 11024 hdc = CreateCompatibleDC(windowHdc); 11025 11026 ReleaseDC(hwnd, windowHdc); 11027 11028 oldBmp = SelectObject(hdc, buffer); 11029 } 11030 } else { 11031 // drawing on something else, draw directly 11032 hdc = CreateCompatibleDC(null); 11033 SelectObject(hdc, window); 11034 } 11035 11036 // X doesn't draw a text background, so neither should we 11037 SetBkMode(hdc, TRANSPARENT); 11038 11039 ensureDefaultFontLoaded(); 11040 11041 if(defaultGuiFont) { 11042 SelectObject(hdc, defaultGuiFont); 11043 // DeleteObject(defaultGuiFont); 11044 } 11045 } 11046 11047 static HFONT defaultGuiFont; 11048 static void ensureDefaultFontLoaded() { 11049 static bool triedDefaultGuiFont = false; 11050 if(!triedDefaultGuiFont) { 11051 NONCLIENTMETRICS params; 11052 params.cbSize = params.sizeof; 11053 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11054 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11055 } 11056 triedDefaultGuiFont = true; 11057 } 11058 } 11059 11060 void setFont(OperatingSystemFont font) { 11061 if(font && font.font) { 11062 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11063 // error... how to handle tho? 11064 } 11065 } 11066 else if(defaultGuiFont) 11067 SelectObject(hdc, defaultGuiFont); 11068 } 11069 11070 arsd.color.Rectangle _clipRectangle; 11071 11072 void setClipRectangle(int x, int y, int width, int height) { 11073 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11074 11075 if(width == 0 || height == 0) { 11076 SelectClipRgn(hdc, null); 11077 } else { 11078 auto region = CreateRectRgn(x, y, x + width, y + height); 11079 SelectClipRgn(hdc, region); 11080 DeleteObject(region); 11081 } 11082 } 11083 11084 11085 // just because we can on Windows... 11086 //void create(Image image); 11087 11088 void invalidateRect(Rectangle invalidRect) { 11089 RECT rect; 11090 rect.left = invalidRect.left; 11091 rect.right = invalidRect.right; 11092 rect.top = invalidRect.top; 11093 rect.bottom = invalidRect.bottom; 11094 InvalidateRect(hwnd, &rect, false); 11095 } 11096 bool manualInvalidations; 11097 11098 void dispose() { 11099 // FIXME: this.window.width/height is probably wrong 11100 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11101 // ReleaseDC(hwnd, windowHdc); 11102 11103 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11104 if(cast(SimpleWindow) this.window) { 11105 if(!manualInvalidations) 11106 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11107 } 11108 11109 if(originalPen !is null) 11110 SelectObject(hdc, originalPen); 11111 if(currentPen !is null) 11112 DeleteObject(currentPen); 11113 if(originalBrush !is null) 11114 SelectObject(hdc, originalBrush); 11115 if(currentBrush !is null) 11116 DeleteObject(currentBrush); 11117 11118 SelectObject(hdc, oldBmp); 11119 11120 if(windowDc) 11121 ReleaseDC(hwnd, hdc); 11122 else 11123 DeleteDC(hdc); 11124 11125 if(window.paintingFinishedDg !is null) 11126 window.paintingFinishedDg()(); 11127 } 11128 11129 bool windowDc; 11130 HPEN originalPen; 11131 HPEN currentPen; 11132 11133 Pen _activePen; 11134 11135 Color _outlineColor; 11136 11137 @property void pen(Pen p) { 11138 _activePen = p; 11139 _outlineColor = p.color; 11140 11141 HPEN pen; 11142 if(p.color.a == 0) { 11143 pen = GetStockObject(NULL_PEN); 11144 } else { 11145 int style = PS_SOLID; 11146 final switch(p.style) { 11147 case Pen.Style.Solid: 11148 style = PS_SOLID; 11149 break; 11150 case Pen.Style.Dashed: 11151 style = PS_DASH; 11152 break; 11153 case Pen.Style.Dotted: 11154 style = PS_DOT; 11155 break; 11156 } 11157 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11158 } 11159 auto orig = SelectObject(hdc, pen); 11160 if(originalPen is null) 11161 originalPen = orig; 11162 11163 if(currentPen !is null) 11164 DeleteObject(currentPen); 11165 11166 currentPen = pen; 11167 11168 // the outline is like a foreground since it's done that way on X 11169 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11170 11171 } 11172 11173 @property void rasterOp(RasterOp op) { 11174 int mode; 11175 final switch(op) { 11176 case RasterOp.normal: 11177 mode = R2_COPYPEN; 11178 break; 11179 case RasterOp.xor: 11180 mode = R2_XORPEN; 11181 break; 11182 } 11183 SetROP2(hdc, mode); 11184 } 11185 11186 HBRUSH originalBrush; 11187 HBRUSH currentBrush; 11188 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11189 @property void fillColor(Color c) { 11190 if(c == _fillColor) 11191 return; 11192 _fillColor = c; 11193 HBRUSH brush; 11194 if(c.a == 0) { 11195 brush = GetStockObject(HOLLOW_BRUSH); 11196 } else { 11197 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11198 } 11199 auto orig = SelectObject(hdc, brush); 11200 if(originalBrush is null) 11201 originalBrush = orig; 11202 11203 if(currentBrush !is null) 11204 DeleteObject(currentBrush); 11205 11206 currentBrush = brush; 11207 11208 // background color is NOT set because X doesn't draw text backgrounds 11209 // SetBkColor(hdc, RGB(255, 255, 255)); 11210 } 11211 11212 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11213 BITMAP bm; 11214 11215 HDC hdcMem = CreateCompatibleDC(hdc); 11216 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11217 11218 GetObject(i.handle, bm.sizeof, &bm); 11219 11220 // or should I AlphaBlend!??!?! 11221 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11222 11223 SelectObject(hdcMem, hbmOld); 11224 DeleteDC(hdcMem); 11225 } 11226 11227 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11228 BITMAP bm; 11229 11230 HDC hdcMem = CreateCompatibleDC(hdc); 11231 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 11232 11233 GetObject(s.handle, bm.sizeof, &bm); 11234 11235 version(CRuntime_DigitalMars) goto noalpha; 11236 11237 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 11238 if(s.enableAlpha) { 11239 auto dw = w ? w : bm.bmWidth; 11240 auto dh = h ? h : bm.bmHeight; 11241 BLENDFUNCTION bf; 11242 bf.BlendOp = AC_SRC_OVER; 11243 bf.SourceConstantAlpha = 255; 11244 bf.AlphaFormat = AC_SRC_ALPHA; 11245 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 11246 } else { 11247 noalpha: 11248 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 11249 } 11250 11251 SelectObject(hdcMem, hbmOld); 11252 DeleteDC(hdcMem); 11253 } 11254 11255 Size textSize(scope const(char)[] text) { 11256 bool dummyX; 11257 if(text.length == 0) { 11258 text = " "; 11259 dummyX = true; 11260 } 11261 RECT rect; 11262 WCharzBuffer buffer = WCharzBuffer(text); 11263 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 11264 return Size(dummyX ? 0 : rect.right, rect.bottom); 11265 } 11266 11267 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 11268 if(text.length && text[$-1] == '\n') 11269 text = text[0 .. $-1]; // tailing newlines are weird on windows... 11270 if(text.length && text[$-1] == '\r') 11271 text = text[0 .. $-1]; 11272 11273 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 11274 if(x2 == 0 && y2 == 0) { 11275 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 11276 } else { 11277 RECT rect; 11278 rect.left = x; 11279 rect.top = y; 11280 rect.right = x2; 11281 rect.bottom = y2; 11282 11283 uint mode = DT_LEFT; 11284 if(alignment & TextAlignment.Right) 11285 mode = DT_RIGHT; 11286 else if(alignment & TextAlignment.Center) 11287 mode = DT_CENTER; 11288 11289 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 11290 if(alignment & TextAlignment.VerticalCenter) 11291 mode |= DT_VCENTER | DT_SINGLELINE; 11292 11293 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 11294 } 11295 11296 /* 11297 uint mode; 11298 11299 if(alignment & TextAlignment.Center) 11300 mode = TA_CENTER; 11301 11302 SetTextAlign(hdc, mode); 11303 */ 11304 } 11305 11306 int fontHeight() { 11307 TEXTMETRIC metric; 11308 if(GetTextMetricsW(hdc, &metric)) { 11309 return metric.tmHeight; 11310 } 11311 11312 return 16; // idk just guessing here, maybe we should throw 11313 } 11314 11315 void drawPixel(int x, int y) { 11316 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 11317 } 11318 11319 // The basic shapes, outlined 11320 11321 void drawLine(int x1, int y1, int x2, int y2) { 11322 MoveToEx(hdc, x1, y1, null); 11323 LineTo(hdc, x2, y2); 11324 } 11325 11326 void drawRectangle(int x, int y, int width, int height) { 11327 // FIXME: with a wider pen this might not draw quite right. im not sure. 11328 gdi.Rectangle(hdc, x, y, x + width, y + height); 11329 } 11330 11331 /// Arguments are the points of the bounding rectangle 11332 void drawEllipse(int x1, int y1, int x2, int y2) { 11333 Ellipse(hdc, x1, y1, x2, y2); 11334 } 11335 11336 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11337 if((start % (360*64)) == (finish % (360*64))) 11338 drawEllipse(x1, y1, x1 + width, y1 + height); 11339 else { 11340 import core.stdc.math; 11341 float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323; 11342 float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323; 11343 11344 auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2); 11345 auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2); 11346 auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2); 11347 auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2); 11348 11349 if(_activePen.color.a) 11350 Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11351 if(_fillColor.a) 11352 Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11353 } 11354 } 11355 11356 void drawPolygon(Point[] vertexes) { 11357 POINT[] points; 11358 points.length = vertexes.length; 11359 11360 foreach(i, p; vertexes) { 11361 points[i].x = p.x; 11362 points[i].y = p.y; 11363 } 11364 11365 Polygon(hdc, points.ptr, cast(int) points.length); 11366 } 11367 } 11368 11369 11370 // Mix this into the SimpleWindow class 11371 mixin template NativeSimpleWindowImplementation() { 11372 int curHidden = 0; // counter 11373 __gshared static bool[string] knownWinClasses; 11374 static bool altPressed = false; 11375 11376 HANDLE oldCursor; 11377 11378 void hideCursor () { 11379 if(curHidden == 0) 11380 oldCursor = SetCursor(null); 11381 ++curHidden; 11382 } 11383 11384 void showCursor () { 11385 --curHidden; 11386 if(curHidden == 0) { 11387 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 11388 } 11389 } 11390 11391 11392 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 11393 11394 void setMinSize (int minwidth, int minheight) { 11395 minWidth = minwidth; 11396 minHeight = minheight; 11397 } 11398 void setMaxSize (int maxwidth, int maxheight) { 11399 maxWidth = maxwidth; 11400 maxHeight = maxheight; 11401 } 11402 11403 // FIXME i'm not sure that Windows has this functionality 11404 // though it is nonessential anyway. 11405 void setResizeGranularity (int granx, int grany) {} 11406 11407 ScreenPainter getPainter(bool manualInvalidations) { 11408 return ScreenPainter(this, hwnd, manualInvalidations); 11409 } 11410 11411 HBITMAP buffer; 11412 11413 void setTitle(string title) { 11414 WCharzBuffer bfr = WCharzBuffer(title); 11415 SetWindowTextW(hwnd, bfr.ptr); 11416 } 11417 11418 string getTitle() { 11419 auto len = GetWindowTextLengthW(hwnd); 11420 if (!len) 11421 return null; 11422 wchar[256] tmpBuffer; 11423 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 11424 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 11425 auto str = buffer[0 .. len2]; 11426 return makeUtf8StringFromWindowsString(str); 11427 } 11428 11429 void move(int x, int y) { 11430 RECT rect; 11431 GetWindowRect(hwnd, &rect); 11432 // move it while maintaining the same size... 11433 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 11434 } 11435 11436 void resize(int w, int h) { 11437 RECT rect; 11438 GetWindowRect(hwnd, &rect); 11439 11440 RECT client; 11441 GetClientRect(hwnd, &client); 11442 11443 rect.right = rect.right - client.right + w; 11444 rect.bottom = rect.bottom - client.bottom + h; 11445 11446 // same position, new size for the client rectangle 11447 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 11448 11449 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 11450 } 11451 11452 void moveResize (int x, int y, int w, int h) { 11453 // what's given is the client rectangle, we need to adjust 11454 11455 RECT rect; 11456 rect.left = x; 11457 rect.top = y; 11458 rect.right = w + x; 11459 rect.bottom = h + y; 11460 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 11461 throw new Exception("AdjustWindowRect"); 11462 11463 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 11464 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 11465 if (windowResized !is null) windowResized(w, h); 11466 } 11467 11468 version(without_opengl) {} else { 11469 HGLRC ghRC; 11470 HDC ghDC; 11471 } 11472 11473 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 11474 string cnamec; 11475 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 11476 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 11477 cnamec = "DSimpleWindow"; 11478 } else { 11479 cnamec = sdpyWindowClass; 11480 } 11481 11482 WCharzBuffer cn = WCharzBuffer(cnamec); 11483 11484 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 11485 11486 if(cnamec !in knownWinClasses) { 11487 WNDCLASSEX wc; 11488 11489 // FIXME: I might be able to use cbWndExtra to hold the pointer back 11490 // to the object. Maybe. 11491 wc.cbSize = wc.sizeof; 11492 wc.cbClsExtra = 0; 11493 wc.cbWndExtra = 0; 11494 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 11495 wc.hCursor = LoadCursorW(null, IDC_ARROW); 11496 wc.hIcon = LoadIcon(hInstance, null); 11497 wc.hInstance = hInstance; 11498 wc.lpfnWndProc = &WndProc; 11499 wc.lpszClassName = cn.ptr; 11500 wc.hIconSm = null; 11501 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 11502 if(!RegisterClassExW(&wc)) 11503 throw new WindowsApiException("RegisterClassExW"); 11504 knownWinClasses[cnamec] = true; 11505 } 11506 11507 int style; 11508 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 11509 11510 // FIXME: windowType and customizationFlags 11511 final switch(windowType) { 11512 case WindowTypes.normal: 11513 style = WS_OVERLAPPEDWINDOW; 11514 break; 11515 case WindowTypes.undecorated: 11516 style = WS_POPUP | WS_SYSMENU; 11517 break; 11518 case WindowTypes.eventOnly: 11519 _hidden = true; 11520 break; 11521 case WindowTypes.dropdownMenu: 11522 case WindowTypes.popupMenu: 11523 case WindowTypes.notification: 11524 style = WS_POPUP; 11525 flags |= WS_EX_NOACTIVATE; 11526 break; 11527 case WindowTypes.nestedChild: 11528 style = WS_CHILD; 11529 break; 11530 } 11531 11532 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11533 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 11534 11535 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 11536 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 11537 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 11538 11539 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11540 setOpacity(255); 11541 11542 SimpleWindow.nativeMapping[hwnd] = this; 11543 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 11544 11545 if(windowType == WindowTypes.eventOnly) 11546 return; 11547 11548 HDC hdc = GetDC(hwnd); 11549 11550 11551 version(without_opengl) {} 11552 else { 11553 if(opengl == OpenGlOptions.yes) { 11554 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 11555 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 11556 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 11557 ghDC = hdc; 11558 PIXELFORMATDESCRIPTOR pfd; 11559 11560 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 11561 pfd.nVersion = 1; 11562 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 11563 pfd.dwLayerMask = PFD_MAIN_PLANE; 11564 pfd.iPixelType = PFD_TYPE_RGBA; 11565 pfd.cColorBits = 24; 11566 pfd.cDepthBits = 24; 11567 pfd.cAccumBits = 0; 11568 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 11569 11570 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 11571 11572 if (pixelformat == 0) 11573 throw new WindowsApiException("ChoosePixelFormat"); 11574 11575 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 11576 throw new WindowsApiException("SetPixelFormat"); 11577 11578 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 11579 // windoze is idiotic: we have to have OpenGL context to get function addresses 11580 // so we will create fake context to get that stupid address 11581 auto tmpcc = wglCreateContext(ghDC); 11582 if (tmpcc !is null) { 11583 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 11584 wglMakeCurrent(ghDC, tmpcc); 11585 wglInitOtherFunctions(); 11586 } 11587 } 11588 11589 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 11590 int[9] contextAttribs = [ 11591 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 11592 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 11593 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 11594 // for modern context, set "forward compatibility" flag too 11595 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 11596 0/*None*/, 11597 ]; 11598 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 11599 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 11600 // activate fallback mode 11601 // 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; 11602 ghRC = wglCreateContext(ghDC); 11603 } 11604 if (ghRC is null) 11605 throw new WindowsApiException("wglCreateContextAttribsARB"); 11606 } else { 11607 // try to do at least something 11608 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 11609 sdpyOpenGLContextVersion = 0; 11610 ghRC = wglCreateContext(ghDC); 11611 } 11612 if (ghRC is null) 11613 throw new WindowsApiException("wglCreateContext"); 11614 } 11615 } 11616 } 11617 11618 if(opengl == OpenGlOptions.no) { 11619 buffer = CreateCompatibleBitmap(hdc, width, height); 11620 11621 auto hdcBmp = CreateCompatibleDC(hdc); 11622 // make sure it's filled with a blank slate 11623 auto oldBmp = SelectObject(hdcBmp, buffer); 11624 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 11625 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 11626 gdi.Rectangle(hdcBmp, 0, 0, width, height); 11627 SelectObject(hdcBmp, oldBmp); 11628 SelectObject(hdcBmp, oldBrush); 11629 SelectObject(hdcBmp, oldPen); 11630 DeleteDC(hdcBmp); 11631 11632 bmpWidth = width; 11633 bmpHeight = height; 11634 11635 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 11636 } 11637 11638 // We want the window's client area to match the image size 11639 RECT rcClient, rcWindow; 11640 POINT ptDiff; 11641 GetClientRect(hwnd, &rcClient); 11642 GetWindowRect(hwnd, &rcWindow); 11643 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 11644 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 11645 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 11646 11647 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 11648 ShowWindow(hwnd, SW_SHOWNORMAL); 11649 } else { 11650 _hidden = true; 11651 } 11652 this._visibleForTheFirstTimeCalled = false; // hack! 11653 } 11654 11655 11656 void dispose() { 11657 if(buffer) 11658 DeleteObject(buffer); 11659 } 11660 11661 void closeWindow() { 11662 DestroyWindow(hwnd); 11663 } 11664 11665 bool setOpacity(ubyte alpha) { 11666 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 11667 } 11668 11669 HANDLE currentCursor; 11670 11671 // returns zero if it recognized the event 11672 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 11673 MouseEvent mouse; 11674 11675 void mouseEvent(bool isScreen, ulong mods) { 11676 auto x = LOWORD(lParam); 11677 auto y = HIWORD(lParam); 11678 if(isScreen) { 11679 POINT p; 11680 p.x = x; 11681 p.y = y; 11682 ScreenToClient(hwnd, &p); 11683 x = cast(ushort) p.x; 11684 y = cast(ushort) p.y; 11685 } 11686 mouse.x = x + offsetX; 11687 mouse.y = y + offsetY; 11688 11689 wind.mdx(mouse); 11690 mouse.modifierState = cast(int) mods; 11691 mouse.window = wind; 11692 11693 if(wind.handleMouseEvent) 11694 wind.handleMouseEvent(mouse); 11695 } 11696 11697 switch(msg) { 11698 case WM_GETMINMAXINFO: 11699 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 11700 11701 if(wind.minWidth > 0) { 11702 RECT rect; 11703 rect.left = 100; 11704 rect.top = 100; 11705 rect.right = wind.minWidth + 100; 11706 rect.bottom = wind.minHeight + 100; 11707 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11708 throw new WindowsApiException("AdjustWindowRect"); 11709 11710 mmi.ptMinTrackSize.x = rect.right - rect.left; 11711 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 11712 } 11713 11714 if(wind.maxWidth < int.max) { 11715 RECT rect; 11716 rect.left = 100; 11717 rect.top = 100; 11718 rect.right = wind.maxWidth + 100; 11719 rect.bottom = wind.maxHeight + 100; 11720 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11721 throw new WindowsApiException("AdjustWindowRect"); 11722 11723 mmi.ptMaxTrackSize.x = rect.right - rect.left; 11724 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 11725 } 11726 break; 11727 case WM_CHAR: 11728 wchar c = cast(wchar) wParam; 11729 if(wind.handleCharEvent) 11730 wind.handleCharEvent(cast(dchar) c); 11731 break; 11732 case WM_SETFOCUS: 11733 case WM_KILLFOCUS: 11734 wind._focused = (msg == WM_SETFOCUS); 11735 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 11736 if(wind.onFocusChange) 11737 wind.onFocusChange(msg == WM_SETFOCUS); 11738 break; 11739 11740 case WM_SYSKEYDOWN: 11741 goto case; 11742 case WM_SYSKEYUP: 11743 if(lParam & (1 << 29)) { 11744 goto case; 11745 } else { 11746 // no window has keyboard focus 11747 goto default; 11748 } 11749 case WM_KEYDOWN: 11750 case WM_KEYUP: 11751 KeyEvent ev; 11752 ev.key = cast(Key) wParam; 11753 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 11754 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 11755 11756 ev.hardwareCode = (lParam & 0xff0000) >> 16; 11757 11758 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 11759 ev.modifierState |= ModifierState.shift; 11760 //k8: this doesn't work; thanks for nothing, windows 11761 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 11762 ev.modifierState |= ModifierState.alt;*/ 11763 // this never seems to actually be set 11764 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11765 11766 if (wParam == 0x12) { 11767 altPressed = (msg == WM_SYSKEYDOWN); 11768 } 11769 11770 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 11771 altPressed = false; 11772 } 11773 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 11774 11775 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11776 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 11777 ev.modifierState |= ModifierState.ctrl; 11778 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 11779 ev.modifierState |= ModifierState.windows; 11780 if(GetKeyState(Key.NumLock)) 11781 ev.modifierState |= ModifierState.numLock; 11782 if(GetKeyState(Key.CapsLock)) 11783 ev.modifierState |= ModifierState.capsLock; 11784 11785 /+ 11786 // we always want to send the character too, so let's convert it 11787 ubyte[256] state; 11788 wchar[16] buffer; 11789 GetKeyboardState(state.ptr); 11790 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 11791 11792 foreach(dchar d; buffer) { 11793 ev.character = d; 11794 break; 11795 } 11796 +/ 11797 11798 ev.window = wind; 11799 if(wind.handleKeyEvent) 11800 wind.handleKeyEvent(ev); 11801 break; 11802 case 0x020a /*WM_MOUSEWHEEL*/: 11803 // send click 11804 mouse.type = cast(MouseEventType) 1; 11805 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 11806 mouseEvent(true, LOWORD(wParam)); 11807 11808 // also send release 11809 mouse.type = cast(MouseEventType) 2; 11810 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 11811 mouseEvent(true, LOWORD(wParam)); 11812 break; 11813 case WM_MOUSEMOVE: 11814 mouse.type = cast(MouseEventType) 0; 11815 mouseEvent(false, wParam); 11816 break; 11817 case WM_LBUTTONDOWN: 11818 case WM_LBUTTONDBLCLK: 11819 mouse.type = cast(MouseEventType) 1; 11820 mouse.button = MouseButton.left; 11821 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 11822 mouseEvent(false, wParam); 11823 break; 11824 case WM_LBUTTONUP: 11825 mouse.type = cast(MouseEventType) 2; 11826 mouse.button = MouseButton.left; 11827 mouseEvent(false, wParam); 11828 break; 11829 case WM_RBUTTONDOWN: 11830 case WM_RBUTTONDBLCLK: 11831 mouse.type = cast(MouseEventType) 1; 11832 mouse.button = MouseButton.right; 11833 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 11834 mouseEvent(false, wParam); 11835 break; 11836 case WM_RBUTTONUP: 11837 mouse.type = cast(MouseEventType) 2; 11838 mouse.button = MouseButton.right; 11839 mouseEvent(false, wParam); 11840 break; 11841 case WM_MBUTTONDOWN: 11842 case WM_MBUTTONDBLCLK: 11843 mouse.type = cast(MouseEventType) 1; 11844 mouse.button = MouseButton.middle; 11845 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 11846 mouseEvent(false, wParam); 11847 break; 11848 case WM_MBUTTONUP: 11849 mouse.type = cast(MouseEventType) 2; 11850 mouse.button = MouseButton.middle; 11851 mouseEvent(false, wParam); 11852 break; 11853 case WM_XBUTTONDOWN: 11854 case WM_XBUTTONDBLCLK: 11855 mouse.type = cast(MouseEventType) 1; 11856 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 11857 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 11858 mouseEvent(false, wParam); 11859 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 11860 case WM_XBUTTONUP: 11861 mouse.type = cast(MouseEventType) 2; 11862 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 11863 mouseEvent(false, wParam); 11864 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 11865 11866 default: return 1; 11867 } 11868 return 0; 11869 } 11870 11871 HWND hwnd; 11872 private int oldWidth; 11873 private int oldHeight; 11874 private bool inSizeMove; 11875 11876 /++ 11877 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. 11878 11879 History: 11880 Added November 23, 2021 11881 11882 Not fully stable, may be moved out of the impl struct. 11883 11884 Default value changed to `true` on February 15, 2021 11885 +/ 11886 bool doLiveResizing = true; 11887 11888 package int bmpWidth; 11889 package int bmpHeight; 11890 11891 // the extern(Windows) wndproc should just forward to this 11892 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 11893 try { 11894 assert(hwnd is this.hwnd); 11895 11896 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 11897 switch(msg) { 11898 case WM_MENUCHAR: // menu active but key not associated with a thing. 11899 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 11900 // The main things we can do are select, execute, close, or ignore 11901 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 11902 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 11903 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 11904 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 11905 11906 // returns the value in the *high order word* of the return value 11907 // hence the << 16 11908 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 11909 case WM_SETCURSOR: 11910 if(cast(HWND) wParam !is hwnd) 11911 return 0; // further processing elsewhere 11912 11913 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 11914 SetCursor(this.curHidden > 0 ? null : currentCursor); 11915 return 1; 11916 } else { 11917 return DefWindowProc(hwnd, msg, wParam, lParam); 11918 } 11919 //break; 11920 11921 case WM_CLOSE: 11922 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 11923 break; 11924 case WM_DESTROY: 11925 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 11926 SimpleWindow.nativeMapping.remove(hwnd); 11927 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 11928 11929 bool anyImportant = false; 11930 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 11931 if(w.beingOpenKeepsAppOpen) { 11932 anyImportant = true; 11933 break; 11934 } 11935 if(!anyImportant) { 11936 PostQuitMessage(0); 11937 } 11938 break; 11939 case 0x02E0 /*WM_DPICHANGED*/: 11940 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 11941 11942 RECT* prcNewWindow = cast(RECT*)lParam; 11943 // docs say this is the recommended position and we should honor it 11944 SetWindowPos(hwnd, 11945 null, 11946 prcNewWindow.left, 11947 prcNewWindow.top, 11948 prcNewWindow.right - prcNewWindow.left, 11949 prcNewWindow.bottom - prcNewWindow.top, 11950 SWP_NOZORDER | SWP_NOACTIVATE); 11951 11952 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 11953 // im not sure it is completely correct 11954 // but without it the tabs and such do look weird as things change. 11955 if(SystemParametersInfoForDpi) { 11956 LOGFONT lfText; 11957 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 11958 HFONT hFontNew = CreateFontIndirect(&lfText); 11959 if (hFontNew) 11960 { 11961 //DeleteObject(hFontOld); 11962 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 11963 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 11964 return TRUE; 11965 } 11966 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 11967 } 11968 } 11969 11970 if(this.onDpiChanged) 11971 this.onDpiChanged(); 11972 break; 11973 case WM_ENTERIDLE: 11974 // when a menu is up, it stops normal event processing (modal message loop) 11975 // but this at least gives us a chance to SOMETIMES catch up 11976 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 11977 SimpleWindow.processAllCustomEvents; 11978 SimpleWindow.processAllCustomEvents; 11979 SleepEx(0, true); 11980 break; 11981 case WM_SIZE: 11982 if(wParam == 1 /* SIZE_MINIMIZED */) 11983 break; 11984 _width = LOWORD(lParam); 11985 _height = HIWORD(lParam); 11986 11987 // I want to avoid tearing in the windows (my code is inefficient 11988 // so this is a hack around that) so while sizing, we don't trigger, 11989 // but we do want to trigger on events like mazimize. 11990 if(!inSizeMove || doLiveResizing) 11991 goto size_changed; 11992 break; 11993 /+ 11994 case WM_SIZING: 11995 import std.stdio; writeln("size"); 11996 break; 11997 +/ 11998 // I don't like the tearing I get when redrawing on WM_SIZE 11999 // (I know there's other ways to fix that but I don't like that behavior anyway) 12000 // so instead it is going to redraw only at the end of a size. 12001 case 0x0231: /* WM_ENTERSIZEMOVE */ 12002 inSizeMove = true; 12003 break; 12004 case 0x0232: /* WM_EXITSIZEMOVE */ 12005 inSizeMove = false; 12006 12007 size_changed: 12008 12009 // nothing relevant changed, don't bother redrawing 12010 if(oldWidth == width && oldHeight == height) { 12011 break; 12012 } 12013 12014 // note: OpenGL windows don't use a backing bmp, so no need to change them 12015 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12016 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12017 // gotta get the double buffer bmp to match the window 12018 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12019 if(width > bmpWidth || height > bmpHeight) { 12020 auto hdc = GetDC(hwnd); 12021 auto oldBuffer = buffer; 12022 buffer = CreateCompatibleBitmap(hdc, width, height); 12023 12024 auto hdcBmp = CreateCompatibleDC(hdc); 12025 auto oldBmp = SelectObject(hdcBmp, buffer); 12026 12027 auto hdcOldBmp = CreateCompatibleDC(hdc); 12028 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12029 12030 /+ 12031 RECT r; 12032 r.left = 0; 12033 r.top = 0; 12034 r.right = width; 12035 r.bottom = height; 12036 auto c = Color.green; 12037 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12038 FillRect(hdcBmp, &r, brush); 12039 DeleteObject(brush); 12040 +/ 12041 12042 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12043 12044 bmpWidth = width; 12045 bmpHeight = height; 12046 12047 SelectObject(hdcOldBmp, oldOldBmp); 12048 DeleteDC(hdcOldBmp); 12049 12050 SelectObject(hdcBmp, oldBmp); 12051 DeleteDC(hdcBmp); 12052 12053 ReleaseDC(hwnd, hdc); 12054 12055 DeleteObject(oldBuffer); 12056 } 12057 } 12058 12059 version(without_opengl) {} else 12060 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 12061 glViewport(0, 0, width, height); 12062 } 12063 12064 if(windowResized !is null) 12065 windowResized(width, height); 12066 12067 if(inSizeMove) { 12068 SimpleWindow.processAllCustomEvents(); 12069 SimpleWindow.processAllCustomEvents(); 12070 } else { 12071 // when it is all done, make sure everything is freshly drawn or there might be 12072 // weird bugs left. 12073 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12074 } 12075 12076 oldWidth = this.width; 12077 oldHeight = this.height; 12078 break; 12079 case WM_ERASEBKGND: 12080 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12081 if (!this._visibleForTheFirstTimeCalled) { 12082 this._visibleForTheFirstTimeCalled = true; 12083 if (this.visibleForTheFirstTime !is null) { 12084 version(without_opengl) {} else { 12085 if(openglMode == OpenGlOptions.yes) { 12086 this.setAsCurrentOpenGlContextNT(); 12087 glViewport(0, 0, width, height); 12088 } 12089 } 12090 this.visibleForTheFirstTime(); 12091 } 12092 } 12093 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12094 version(without_opengl) {} else { 12095 if (openglMode == OpenGlOptions.yes) return 1; 12096 } 12097 // call windows default handler, so it can paint standard controls 12098 goto default; 12099 case WM_CTLCOLORBTN: 12100 case WM_CTLCOLORSTATIC: 12101 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12102 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12103 GetSysColorBrush(COLOR_3DFACE); 12104 //break; 12105 case WM_SHOWWINDOW: 12106 this._visible = (wParam != 0); 12107 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12108 this._visibleForTheFirstTimeCalled = true; 12109 if (this.visibleForTheFirstTime !is null) { 12110 version(without_opengl) {} else { 12111 if(openglMode == OpenGlOptions.yes) { 12112 this.setAsCurrentOpenGlContextNT(); 12113 glViewport(0, 0, width, height); 12114 } 12115 } 12116 this.visibleForTheFirstTime(); 12117 } 12118 } 12119 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12120 break; 12121 case WM_PAINT: { 12122 if (!this._visibleForTheFirstTimeCalled) { 12123 this._visibleForTheFirstTimeCalled = true; 12124 if (this.visibleForTheFirstTime !is null) { 12125 version(without_opengl) {} else { 12126 if(openglMode == OpenGlOptions.yes) { 12127 this.setAsCurrentOpenGlContextNT(); 12128 glViewport(0, 0, width, height); 12129 } 12130 } 12131 this.visibleForTheFirstTime(); 12132 } 12133 } 12134 12135 BITMAP bm; 12136 PAINTSTRUCT ps; 12137 12138 HDC hdc = BeginPaint(hwnd, &ps); 12139 12140 if(openglMode == OpenGlOptions.no) { 12141 12142 HDC hdcMem = CreateCompatibleDC(hdc); 12143 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12144 12145 GetObject(buffer, bm.sizeof, &bm); 12146 12147 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12148 if(resizability == Resizability.automaticallyScaleIfPossible) 12149 StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12150 else 12151 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12152 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12153 12154 SelectObject(hdcMem, hbmOld); 12155 DeleteDC(hdcMem); 12156 EndPaint(hwnd, &ps); 12157 } else { 12158 EndPaint(hwnd, &ps); 12159 version(without_opengl) {} else 12160 redrawOpenGlSceneNow(); 12161 } 12162 } break; 12163 default: 12164 return DefWindowProc(hwnd, msg, wParam, lParam); 12165 } 12166 return 0; 12167 12168 } 12169 catch(Throwable t) { 12170 sdpyPrintDebugString(t.toString); 12171 return 0; 12172 } 12173 } 12174 } 12175 12176 mixin template NativeImageImplementation() { 12177 HBITMAP handle; 12178 ubyte* rawData; 12179 12180 final: 12181 12182 Color getPixel(int x, int y) { 12183 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12184 // remember, bmps are upside down 12185 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12186 12187 Color c; 12188 if(enableAlpha) 12189 c.a = rawData[offset + 3]; 12190 else 12191 c.a = 255; 12192 c.b = rawData[offset + 0]; 12193 c.g = rawData[offset + 1]; 12194 c.r = rawData[offset + 2]; 12195 c.unPremultiply(); 12196 return c; 12197 } 12198 12199 void setPixel(int x, int y, Color c) { 12200 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12201 // remember, bmps are upside down 12202 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12203 12204 if(enableAlpha) 12205 c.premultiply(); 12206 12207 rawData[offset + 0] = c.b; 12208 rawData[offset + 1] = c.g; 12209 rawData[offset + 2] = c.r; 12210 if(enableAlpha) 12211 rawData[offset + 3] = c.a; 12212 } 12213 12214 void convertToRgbaBytes(ubyte[] where) { 12215 assert(where.length == this.width * this.height * 4); 12216 12217 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12218 int idx = 0; 12219 int offset = itemsPerLine * (height - 1); 12220 // remember, bmps are upside down 12221 for(int y = height - 1; y >= 0; y--) { 12222 auto offsetStart = offset; 12223 for(int x = 0; x < width; x++) { 12224 where[idx + 0] = rawData[offset + 2]; // r 12225 where[idx + 1] = rawData[offset + 1]; // g 12226 where[idx + 2] = rawData[offset + 0]; // b 12227 if(enableAlpha) { 12228 where[idx + 3] = rawData[offset + 3]; // a 12229 unPremultiplyRgba(where[idx .. idx + 4]); 12230 offset++; 12231 } else 12232 where[idx + 3] = 255; // a 12233 idx += 4; 12234 offset += 3; 12235 } 12236 12237 offset = offsetStart - itemsPerLine; 12238 } 12239 } 12240 12241 void setFromRgbaBytes(in ubyte[] what) { 12242 assert(what.length == this.width * this.height * 4); 12243 12244 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 12245 int idx = 0; 12246 int offset = itemsPerLine * (height - 1); 12247 // remember, bmps are upside down 12248 for(int y = height - 1; y >= 0; y--) { 12249 auto offsetStart = offset; 12250 for(int x = 0; x < width; x++) { 12251 if(enableAlpha) { 12252 auto a = what[idx + 3]; 12253 12254 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 12255 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 12256 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 12257 rawData[offset + 3] = a; // a 12258 //premultiplyBgra(rawData[offset .. offset + 4]); 12259 offset++; 12260 } else { 12261 rawData[offset + 2] = what[idx + 0]; // r 12262 rawData[offset + 1] = what[idx + 1]; // g 12263 rawData[offset + 0] = what[idx + 2]; // b 12264 } 12265 idx += 4; 12266 offset += 3; 12267 } 12268 12269 offset = offsetStart - itemsPerLine; 12270 } 12271 } 12272 12273 12274 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 12275 BITMAPINFO infoheader; 12276 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 12277 infoheader.bmiHeader.biWidth = width; 12278 infoheader.bmiHeader.biHeight = height; 12279 infoheader.bmiHeader.biPlanes = 1; 12280 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 12281 infoheader.bmiHeader.biCompression = BI_RGB; 12282 12283 handle = CreateDIBSection( 12284 null, 12285 &infoheader, 12286 DIB_RGB_COLORS, 12287 cast(void**) &rawData, 12288 null, 12289 0); 12290 if(handle is null) 12291 throw new WindowsApiException("create image failed"); 12292 12293 } 12294 12295 void dispose() { 12296 DeleteObject(handle); 12297 } 12298 } 12299 12300 enum KEY_ESCAPE = 27; 12301 } 12302 version(X11) { 12303 /// This is the default font used. You might change this before doing anything else with 12304 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 12305 /// for cross-platform compatibility. 12306 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12307 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12308 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 12309 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 12310 12311 alias int delegate(XEvent) NativeEventHandler; 12312 alias Window NativeWindowHandle; 12313 12314 enum KEY_ESCAPE = 9; 12315 12316 mixin template NativeScreenPainterImplementation() { 12317 Display* display; 12318 Drawable d; 12319 Drawable destiny; 12320 12321 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 12322 GC gc; 12323 12324 __gshared bool fontAttempted; 12325 12326 __gshared XFontStruct* defaultfont; 12327 __gshared XFontSet defaultfontset; 12328 12329 XFontStruct* font; 12330 XFontSet fontset; 12331 12332 void create(NativeWindowHandle window) { 12333 this.display = XDisplayConnection.get(); 12334 12335 Drawable buffer = None; 12336 if(auto sw = cast(SimpleWindow) this.window) { 12337 buffer = sw.impl.buffer; 12338 this.destiny = cast(Drawable) window; 12339 } else { 12340 buffer = cast(Drawable) window; 12341 this.destiny = None; 12342 } 12343 12344 this.d = cast(Drawable) buffer; 12345 12346 auto dgc = DefaultGC(display, DefaultScreen(display)); 12347 12348 this.gc = XCreateGC(display, d, 0, null); 12349 12350 XCopyGC(display, dgc, 0xffffffff, this.gc); 12351 12352 ensureDefaultFontLoaded(); 12353 12354 font = defaultfont; 12355 fontset = defaultfontset; 12356 12357 if(font) { 12358 XSetFont(display, gc, font.fid); 12359 } 12360 } 12361 12362 static void ensureDefaultFontLoaded() { 12363 if(!fontAttempted) { 12364 auto display = XDisplayConnection.get; 12365 auto font = XLoadQueryFont(display, xfontstr.ptr); 12366 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 12367 if(font is null) { 12368 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 12369 font = XLoadQueryFont(display, xfontstr.ptr); 12370 } 12371 12372 char** lol; 12373 int lol2; 12374 char* lol3; 12375 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 12376 12377 fontAttempted = true; 12378 12379 defaultfont = font; 12380 defaultfontset = fontset; 12381 } 12382 } 12383 12384 arsd.color.Rectangle _clipRectangle; 12385 void setClipRectangle(int x, int y, int width, int height) { 12386 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12387 if(width == 0 || height == 0) { 12388 XSetClipMask(display, gc, None); 12389 12390 if(xrenderPicturePainter) { 12391 12392 XRectangle[1] rects; 12393 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 12394 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12395 } 12396 12397 version(with_xft) { 12398 if(xftFont is null || xftDraw is null) 12399 return; 12400 XftDrawSetClip(xftDraw, null); 12401 } 12402 } else { 12403 XRectangle[1] rects; 12404 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 12405 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 12406 12407 if(xrenderPicturePainter) 12408 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12409 12410 version(with_xft) { 12411 if(xftFont is null || xftDraw is null) 12412 return; 12413 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 12414 } 12415 } 12416 } 12417 12418 version(with_xft) { 12419 XftFont* xftFont; 12420 XftDraw* xftDraw; 12421 12422 XftColor xftColor; 12423 12424 void updateXftColor() { 12425 if(xftFont is null) 12426 return; 12427 12428 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 12429 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 12430 12431 XftColorAllocValue( 12432 display, 12433 DefaultVisual(display, DefaultScreen(display)), 12434 DefaultColormap(display, 0), 12435 &colorIn, 12436 &xftColor 12437 ); 12438 } 12439 } 12440 12441 void setFont(OperatingSystemFont font) { 12442 version(with_xft) { 12443 if(font && font.isXft && font.xftFont) 12444 this.xftFont = font.xftFont; 12445 else 12446 this.xftFont = null; 12447 12448 if(this.xftFont) { 12449 if(xftDraw is null) { 12450 xftDraw = XftDrawCreate( 12451 display, 12452 d, 12453 DefaultVisual(display, DefaultScreen(display)), 12454 DefaultColormap(display, 0) 12455 ); 12456 12457 updateXftColor(); 12458 } 12459 12460 return; 12461 } 12462 } 12463 12464 if(font && font.font) { 12465 this.font = font.font; 12466 this.fontset = font.fontset; 12467 XSetFont(display, gc, font.font.fid); 12468 } else { 12469 this.font = defaultfont; 12470 this.fontset = defaultfontset; 12471 } 12472 12473 } 12474 12475 private Picture xrenderPicturePainter; 12476 12477 bool manualInvalidations; 12478 void invalidateRect(Rectangle invalidRect) { 12479 // FIXME if manualInvalidations 12480 } 12481 12482 void dispose() { 12483 this.rasterOp = RasterOp.normal; 12484 12485 if(xrenderPicturePainter) { 12486 XRenderFreePicture(display, xrenderPicturePainter); 12487 xrenderPicturePainter = None; 12488 } 12489 12490 // FIXME: this.window.width/height is probably wrong 12491 12492 // src x,y then dest x, y 12493 if(destiny != None) { 12494 // FIXME: if manual invalidations we can actually only copy some of the area. 12495 // if(manualInvalidations) 12496 XSetClipMask(display, gc, None); 12497 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 12498 } 12499 12500 XFreeGC(display, gc); 12501 12502 version(with_xft) 12503 if(xftDraw) { 12504 XftDrawDestroy(xftDraw); 12505 xftDraw = null; 12506 } 12507 12508 /+ 12509 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 12510 if(font && font !is defaultfont) { 12511 XFreeFont(display, font); 12512 font = null; 12513 } 12514 if(fontset && fontset !is defaultfontset) { 12515 XFreeFontSet(display, fontset); 12516 fontset = null; 12517 } 12518 +/ 12519 XFlush(display); 12520 12521 if(window.paintingFinishedDg !is null) 12522 window.paintingFinishedDg()(); 12523 } 12524 12525 bool backgroundIsNotTransparent = true; 12526 bool foregroundIsNotTransparent = true; 12527 12528 bool _penInitialized = false; 12529 Pen _activePen; 12530 12531 Color _outlineColor; 12532 Color _fillColor; 12533 12534 @property void pen(Pen p) { 12535 if(_penInitialized && p == _activePen) { 12536 return; 12537 } 12538 _penInitialized = true; 12539 _activePen = p; 12540 _outlineColor = p.color; 12541 12542 int style; 12543 12544 byte dashLength; 12545 12546 final switch(p.style) { 12547 case Pen.Style.Solid: 12548 style = 0 /*LineSolid*/; 12549 break; 12550 case Pen.Style.Dashed: 12551 style = 1 /*LineOnOffDash*/; 12552 dashLength = 4; 12553 break; 12554 case Pen.Style.Dotted: 12555 style = 1 /*LineOnOffDash*/; 12556 dashLength = 1; 12557 break; 12558 } 12559 12560 XSetLineAttributes(display, gc, p.width, style, 0, 0); 12561 if(dashLength) 12562 XSetDashes(display, gc, 0, &dashLength, 1); 12563 12564 if(p.color.a == 0) { 12565 foregroundIsNotTransparent = false; 12566 return; 12567 } 12568 12569 foregroundIsNotTransparent = true; 12570 12571 XSetForeground(display, gc, colorToX(p.color, display)); 12572 12573 version(with_xft) 12574 updateXftColor(); 12575 } 12576 12577 RasterOp _currentRasterOp; 12578 bool _currentRasterOpInitialized = false; 12579 @property void rasterOp(RasterOp op) { 12580 if(_currentRasterOpInitialized && _currentRasterOp == op) 12581 return; 12582 _currentRasterOp = op; 12583 _currentRasterOpInitialized = true; 12584 int mode; 12585 final switch(op) { 12586 case RasterOp.normal: 12587 mode = GXcopy; 12588 break; 12589 case RasterOp.xor: 12590 mode = GXxor; 12591 break; 12592 } 12593 XSetFunction(display, gc, mode); 12594 } 12595 12596 12597 bool _fillColorInitialized = false; 12598 12599 @property void fillColor(Color c) { 12600 if(_fillColorInitialized && _fillColor == c) 12601 return; // already good, no need to waste time calling it 12602 _fillColor = c; 12603 _fillColorInitialized = true; 12604 if(c.a == 0) { 12605 backgroundIsNotTransparent = false; 12606 return; 12607 } 12608 12609 backgroundIsNotTransparent = true; 12610 12611 XSetBackground(display, gc, colorToX(c, display)); 12612 12613 } 12614 12615 void swapColors() { 12616 auto tmp = _fillColor; 12617 fillColor = _outlineColor; 12618 auto newPen = _activePen; 12619 newPen.color = tmp; 12620 pen(newPen); 12621 } 12622 12623 uint colorToX(Color c, Display* display) { 12624 auto visual = DefaultVisual(display, DefaultScreen(display)); 12625 import core.bitop; 12626 uint color = 0; 12627 { 12628 auto startBit = bsf(visual.red_mask); 12629 auto lastBit = bsr(visual.red_mask); 12630 auto r = cast(uint) c.r; 12631 r >>= 7 - (lastBit - startBit); 12632 r <<= startBit; 12633 color |= r; 12634 } 12635 { 12636 auto startBit = bsf(visual.green_mask); 12637 auto lastBit = bsr(visual.green_mask); 12638 auto g = cast(uint) c.g; 12639 g >>= 7 - (lastBit - startBit); 12640 g <<= startBit; 12641 color |= g; 12642 } 12643 { 12644 auto startBit = bsf(visual.blue_mask); 12645 auto lastBit = bsr(visual.blue_mask); 12646 auto b = cast(uint) c.b; 12647 b >>= 7 - (lastBit - startBit); 12648 b <<= startBit; 12649 color |= b; 12650 } 12651 12652 12653 12654 return color; 12655 } 12656 12657 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12658 // source x, source y 12659 if(ix >= i.width) return; 12660 if(iy >= i.height) return; 12661 if(ix + w > i.width) w = i.width - ix; 12662 if(iy + h > i.height) h = i.height - iy; 12663 if(i.usingXshm) 12664 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 12665 else 12666 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 12667 } 12668 12669 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12670 if(s.enableAlpha) { 12671 // the Sprite must be created first, meaning if we're here, XRender is already loaded 12672 if(this.xrenderPicturePainter == None) { 12673 XRenderPictureAttributes attrs; 12674 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 12675 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 12676 12677 // need to initialize the clip 12678 XRectangle[1] rects; 12679 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 12680 12681 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12682 } 12683 12684 XRenderComposite( 12685 display, 12686 3, // PicOpOver 12687 s.xrenderPicture, 12688 None, 12689 this.xrenderPicturePainter, 12690 ix, 12691 iy, 12692 0, 12693 0, 12694 x, 12695 y, 12696 w ? w : s.width, 12697 h ? h : s.height 12698 ); 12699 } else { 12700 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 12701 } 12702 } 12703 12704 int fontHeight() { 12705 version(with_xft) 12706 if(xftFont !is null) 12707 return xftFont.height; 12708 if(font) 12709 return font.max_bounds.ascent + font.max_bounds.descent; 12710 return 12; // pretty common default... 12711 } 12712 12713 int textWidth(in char[] line) { 12714 version(with_xft) 12715 if(xftFont) { 12716 if(line.length == 0) 12717 return 0; 12718 XGlyphInfo extents; 12719 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 12720 return extents.width; 12721 } 12722 12723 if(fontset) { 12724 if(line.length == 0) 12725 return 0; 12726 XRectangle rect; 12727 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 12728 12729 return rect.width; 12730 } 12731 12732 if(font) 12733 // FIXME: unicode 12734 return XTextWidth( font, line.ptr, cast(int) line.length); 12735 else 12736 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 12737 } 12738 12739 Size textSize(in char[] text) { 12740 auto maxWidth = 0; 12741 auto lineHeight = fontHeight; 12742 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 12743 foreach(line; text.split('\n')) { 12744 int textWidth = this.textWidth(line); 12745 if(textWidth > maxWidth) 12746 maxWidth = textWidth; 12747 h += lineHeight + 4; 12748 } 12749 return Size(maxWidth, h); 12750 } 12751 12752 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 12753 const(char)[] text; 12754 version(with_xft) 12755 if(xftFont) { 12756 text = originalText; 12757 goto loaded; 12758 } 12759 12760 if(fontset) 12761 text = originalText; 12762 else { 12763 text.reserve(originalText.length); 12764 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 12765 // then strip the rest so there isn't garbage 12766 foreach(dchar ch; originalText) 12767 if(ch < 256) 12768 text ~= cast(ubyte) ch; 12769 else 12770 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 12771 } 12772 loaded: 12773 if(text.length == 0) 12774 return; 12775 12776 // FIXME: should we clip it to the bounding box? 12777 int textHeight = fontHeight; 12778 12779 auto lines = text.split('\n'); 12780 12781 const lineHeight = textHeight; 12782 textHeight *= lines.length; 12783 12784 int cy = y; 12785 12786 if(alignment & TextAlignment.VerticalBottom) { 12787 if(y2 <= 0) 12788 return; 12789 auto h = y2 - y; 12790 if(h > textHeight) { 12791 cy += h - textHeight; 12792 cy -= lineHeight / 2; 12793 } 12794 } else if(alignment & TextAlignment.VerticalCenter) { 12795 if(y2 <= 0) 12796 return; 12797 auto h = y2 - y; 12798 if(textHeight < h) { 12799 cy += (h - textHeight) / 2; 12800 //cy -= lineHeight / 4; 12801 } 12802 } 12803 12804 foreach(line; text.split('\n')) { 12805 int textWidth = this.textWidth(line); 12806 12807 int px = x, py = cy; 12808 12809 if(alignment & TextAlignment.Center) { 12810 if(x2 <= 0) 12811 return; 12812 auto w = x2 - x; 12813 if(w > textWidth) 12814 px += (w - textWidth) / 2; 12815 } else if(alignment & TextAlignment.Right) { 12816 if(x2 <= 0) 12817 return; 12818 auto pos = x2 - textWidth; 12819 if(pos > x) 12820 px = pos; 12821 } 12822 12823 version(with_xft) 12824 if(xftFont) { 12825 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 12826 12827 goto carry_on; 12828 } 12829 12830 if(fontset) 12831 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 12832 else 12833 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 12834 carry_on: 12835 cy += lineHeight + 4; 12836 } 12837 } 12838 12839 void drawPixel(int x, int y) { 12840 XDrawPoint(display, d, gc, x, y); 12841 } 12842 12843 // The basic shapes, outlined 12844 12845 void drawLine(int x1, int y1, int x2, int y2) { 12846 if(foregroundIsNotTransparent) 12847 XDrawLine(display, d, gc, x1, y1, x2, y2); 12848 } 12849 12850 void drawRectangle(int x, int y, int width, int height) { 12851 if(backgroundIsNotTransparent) { 12852 swapColors(); 12853 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 12854 swapColors(); 12855 } 12856 // 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 12857 if(foregroundIsNotTransparent) 12858 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 12859 } 12860 12861 /// Arguments are the points of the bounding rectangle 12862 void drawEllipse(int x1, int y1, int x2, int y2) { 12863 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 12864 } 12865 12866 // NOTE: start and finish are in units of degrees * 64 12867 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 12868 if(backgroundIsNotTransparent) { 12869 swapColors(); 12870 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 12871 swapColors(); 12872 } 12873 if(foregroundIsNotTransparent) { 12874 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 12875 // Windows draws the straight lines on the edges too so FIXME sort of 12876 } 12877 } 12878 12879 void drawPolygon(Point[] vertexes) { 12880 XPoint[16] pointsBuffer; 12881 XPoint[] points; 12882 if(vertexes.length <= pointsBuffer.length) 12883 points = pointsBuffer[0 .. vertexes.length]; 12884 else 12885 points.length = vertexes.length; 12886 12887 foreach(i, p; vertexes) { 12888 points[i].x = cast(short) p.x; 12889 points[i].y = cast(short) p.y; 12890 } 12891 12892 if(backgroundIsNotTransparent) { 12893 swapColors(); 12894 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 12895 swapColors(); 12896 } 12897 if(foregroundIsNotTransparent) { 12898 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 12899 } 12900 } 12901 } 12902 12903 /* XRender { */ 12904 12905 struct XRenderColor { 12906 ushort red; 12907 ushort green; 12908 ushort blue; 12909 ushort alpha; 12910 } 12911 12912 alias Picture = XID; 12913 alias PictFormat = XID; 12914 12915 struct XGlyphInfo { 12916 ushort width; 12917 ushort height; 12918 short x; 12919 short y; 12920 short xOff; 12921 short yOff; 12922 } 12923 12924 struct XRenderDirectFormat { 12925 short red; 12926 short redMask; 12927 short green; 12928 short greenMask; 12929 short blue; 12930 short blueMask; 12931 short alpha; 12932 short alphaMask; 12933 } 12934 12935 struct XRenderPictFormat { 12936 PictFormat id; 12937 int type; 12938 int depth; 12939 XRenderDirectFormat direct; 12940 Colormap colormap; 12941 } 12942 12943 enum PictFormatID = (1 << 0); 12944 enum PictFormatType = (1 << 1); 12945 enum PictFormatDepth = (1 << 2); 12946 enum PictFormatRed = (1 << 3); 12947 enum PictFormatRedMask =(1 << 4); 12948 enum PictFormatGreen = (1 << 5); 12949 enum PictFormatGreenMask=(1 << 6); 12950 enum PictFormatBlue = (1 << 7); 12951 enum PictFormatBlueMask =(1 << 8); 12952 enum PictFormatAlpha = (1 << 9); 12953 enum PictFormatAlphaMask=(1 << 10); 12954 enum PictFormatColormap =(1 << 11); 12955 12956 struct XRenderPictureAttributes { 12957 int repeat; 12958 Picture alpha_map; 12959 int alpha_x_origin; 12960 int alpha_y_origin; 12961 int clip_x_origin; 12962 int clip_y_origin; 12963 Pixmap clip_mask; 12964 Bool graphics_exposures; 12965 int subwindow_mode; 12966 int poly_edge; 12967 int poly_mode; 12968 Atom dither; 12969 Bool component_alpha; 12970 } 12971 12972 alias int XFixed; 12973 12974 struct XPointFixed { 12975 XFixed x, y; 12976 } 12977 12978 struct XCircle { 12979 XFixed x; 12980 XFixed y; 12981 XFixed radius; 12982 } 12983 12984 struct XTransform { 12985 XFixed[3][3] matrix; 12986 } 12987 12988 struct XFilters { 12989 int nfilter; 12990 char **filter; 12991 int nalias; 12992 short *alias_; 12993 } 12994 12995 struct XIndexValue { 12996 c_ulong pixel; 12997 ushort red, green, blue, alpha; 12998 } 12999 13000 struct XAnimCursor { 13001 Cursor cursor; 13002 c_ulong delay; 13003 } 13004 13005 struct XLinearGradient { 13006 XPointFixed p1; 13007 XPointFixed p2; 13008 } 13009 13010 struct XRadialGradient { 13011 XCircle inner; 13012 XCircle outer; 13013 } 13014 13015 struct XConicalGradient { 13016 XPointFixed center; 13017 XFixed angle; /* in degrees */ 13018 } 13019 13020 enum PictStandardARGB32 = 0; 13021 enum PictStandardRGB24 = 1; 13022 enum PictStandardA8 = 2; 13023 enum PictStandardA4 = 3; 13024 enum PictStandardA1 = 4; 13025 enum PictStandardNUM = 5; 13026 13027 interface XRender { 13028 extern(C) @nogc: 13029 13030 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13031 13032 Status XRenderQueryVersion (Display *dpy, 13033 int *major_versionp, 13034 int *minor_versionp); 13035 13036 Status XRenderQueryFormats (Display *dpy); 13037 13038 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13039 13040 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13041 13042 XRenderPictFormat * 13043 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13044 13045 XRenderPictFormat * 13046 XRenderFindFormat (Display *dpy, 13047 c_ulong mask, 13048 const XRenderPictFormat *templ, 13049 int count); 13050 XRenderPictFormat * 13051 XRenderFindStandardFormat (Display *dpy, 13052 int format); 13053 13054 XIndexValue * 13055 XRenderQueryPictIndexValues(Display *dpy, 13056 const XRenderPictFormat *format, 13057 int *num); 13058 13059 Picture XRenderCreatePicture( 13060 Display *dpy, 13061 Drawable drawable, 13062 const XRenderPictFormat *format, 13063 c_ulong valuemask, 13064 const XRenderPictureAttributes *attributes); 13065 13066 void XRenderChangePicture (Display *dpy, 13067 Picture picture, 13068 c_ulong valuemask, 13069 const XRenderPictureAttributes *attributes); 13070 13071 void 13072 XRenderSetPictureClipRectangles (Display *dpy, 13073 Picture picture, 13074 int xOrigin, 13075 int yOrigin, 13076 const XRectangle *rects, 13077 int n); 13078 13079 void 13080 XRenderSetPictureClipRegion (Display *dpy, 13081 Picture picture, 13082 Region r); 13083 13084 void 13085 XRenderSetPictureTransform (Display *dpy, 13086 Picture picture, 13087 XTransform *transform); 13088 13089 void 13090 XRenderFreePicture (Display *dpy, 13091 Picture picture); 13092 13093 void 13094 XRenderComposite (Display *dpy, 13095 int op, 13096 Picture src, 13097 Picture mask, 13098 Picture dst, 13099 int src_x, 13100 int src_y, 13101 int mask_x, 13102 int mask_y, 13103 int dst_x, 13104 int dst_y, 13105 uint width, 13106 uint height); 13107 13108 13109 Picture XRenderCreateSolidFill (Display *dpy, 13110 const XRenderColor *color); 13111 13112 Picture XRenderCreateLinearGradient (Display *dpy, 13113 const XLinearGradient *gradient, 13114 const XFixed *stops, 13115 const XRenderColor *colors, 13116 int nstops); 13117 13118 Picture XRenderCreateRadialGradient (Display *dpy, 13119 const XRadialGradient *gradient, 13120 const XFixed *stops, 13121 const XRenderColor *colors, 13122 int nstops); 13123 13124 Picture XRenderCreateConicalGradient (Display *dpy, 13125 const XConicalGradient *gradient, 13126 const XFixed *stops, 13127 const XRenderColor *colors, 13128 int nstops); 13129 13130 13131 13132 Cursor 13133 XRenderCreateCursor (Display *dpy, 13134 Picture source, 13135 uint x, 13136 uint y); 13137 13138 XFilters * 13139 XRenderQueryFilters (Display *dpy, Drawable drawable); 13140 13141 void 13142 XRenderSetPictureFilter (Display *dpy, 13143 Picture picture, 13144 const char *filter, 13145 XFixed *params, 13146 int nparams); 13147 13148 Cursor 13149 XRenderCreateAnimCursor (Display *dpy, 13150 int ncursor, 13151 XAnimCursor *cursors); 13152 } 13153 13154 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 13155 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 13156 13157 /* XRender } */ 13158 13159 /* Xrandr { */ 13160 13161 struct XRRMonitorInfo { 13162 Atom name; 13163 Bool primary; 13164 Bool automatic; 13165 int noutput; 13166 int x; 13167 int y; 13168 int width; 13169 int height; 13170 int mwidth; 13171 int mheight; 13172 /*RROutput*/ void *outputs; 13173 } 13174 13175 struct XRRScreenChangeNotifyEvent { 13176 int type; /* event base */ 13177 c_ulong serial; /* # of last request processed by server */ 13178 Bool send_event; /* true if this came from a SendEvent request */ 13179 Display *display; /* Display the event was read from */ 13180 Window window; /* window which selected for this event */ 13181 Window root; /* Root window for changed screen */ 13182 Time timestamp; /* when the screen change occurred */ 13183 Time config_timestamp; /* when the last configuration change */ 13184 ushort/*SizeID*/ size_index; 13185 ushort/*SubpixelOrder*/ subpixel_order; 13186 ushort/*Rotation*/ rotation; 13187 int width; 13188 int height; 13189 int mwidth; 13190 int mheight; 13191 } 13192 13193 enum RRScreenChangeNotify = 0; 13194 13195 enum RRScreenChangeNotifyMask = 1; 13196 13197 __gshared int xrrEventBase = -1; 13198 13199 13200 interface XRandr { 13201 extern(C) @nogc: 13202 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 13203 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 13204 13205 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 13206 void XRRFreeMonitors(XRRMonitorInfo *monitors); 13207 13208 void XRRSelectInput(Display *dpy, Window window, int mask); 13209 } 13210 13211 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 13212 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 13213 /* Xrandr } */ 13214 13215 /* Xft { */ 13216 13217 // actually freetype 13218 alias void FT_Face; 13219 13220 // actually fontconfig 13221 private alias FcBool = int; 13222 alias void FcCharSet; 13223 alias void FcPattern; 13224 alias void FcResult; 13225 enum FcEndian { FcEndianBig, FcEndianLittle } 13226 struct FcFontSet { 13227 int nfont; 13228 int sfont; 13229 FcPattern** fonts; 13230 } 13231 13232 // actually XRegion 13233 struct BOX { 13234 short x1, x2, y1, y2; 13235 } 13236 struct _XRegion { 13237 c_long size; 13238 c_long numRects; 13239 BOX* rects; 13240 BOX extents; 13241 } 13242 13243 alias Region = _XRegion*; 13244 13245 // ok actually Xft 13246 13247 struct XftFontInfo; 13248 13249 struct XftFont { 13250 int ascent; 13251 int descent; 13252 int height; 13253 int max_advance_width; 13254 FcCharSet* charset; 13255 FcPattern* pattern; 13256 } 13257 13258 struct XftDraw; 13259 13260 struct XftColor { 13261 c_ulong pixel; 13262 XRenderColor color; 13263 } 13264 13265 struct XftCharSpec { 13266 dchar ucs4; 13267 short x; 13268 short y; 13269 } 13270 13271 struct XftCharFontSpec { 13272 XftFont *font; 13273 dchar ucs4; 13274 short x; 13275 short y; 13276 } 13277 13278 struct XftGlyphSpec { 13279 uint glyph; 13280 short x; 13281 short y; 13282 } 13283 13284 struct XftGlyphFontSpec { 13285 XftFont *font; 13286 uint glyph; 13287 short x; 13288 short y; 13289 } 13290 13291 interface Xft { 13292 extern(C) @nogc pure: 13293 13294 Bool XftColorAllocName (Display *dpy, 13295 const Visual *visual, 13296 Colormap cmap, 13297 const char *name, 13298 XftColor *result); 13299 13300 Bool XftColorAllocValue (Display *dpy, 13301 Visual *visual, 13302 Colormap cmap, 13303 const XRenderColor *color, 13304 XftColor *result); 13305 13306 void XftColorFree (Display *dpy, 13307 Visual *visual, 13308 Colormap cmap, 13309 XftColor *color); 13310 13311 Bool XftDefaultHasRender (Display *dpy); 13312 13313 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 13314 13315 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 13316 13317 XftDraw * XftDrawCreate (Display *dpy, 13318 Drawable drawable, 13319 Visual *visual, 13320 Colormap colormap); 13321 13322 XftDraw * XftDrawCreateBitmap (Display *dpy, 13323 Pixmap bitmap); 13324 13325 XftDraw * XftDrawCreateAlpha (Display *dpy, 13326 Pixmap pixmap, 13327 int depth); 13328 13329 void XftDrawChange (XftDraw *draw, 13330 Drawable drawable); 13331 13332 Display * XftDrawDisplay (XftDraw *draw); 13333 13334 Drawable XftDrawDrawable (XftDraw *draw); 13335 13336 Colormap XftDrawColormap (XftDraw *draw); 13337 13338 Visual * XftDrawVisual (XftDraw *draw); 13339 13340 void XftDrawDestroy (XftDraw *draw); 13341 13342 Picture XftDrawPicture (XftDraw *draw); 13343 13344 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 13345 13346 void XftDrawGlyphs (XftDraw *draw, 13347 const XftColor *color, 13348 XftFont *pub, 13349 int x, 13350 int y, 13351 const uint *glyphs, 13352 int nglyphs); 13353 13354 void XftDrawString8 (XftDraw *draw, 13355 const XftColor *color, 13356 XftFont *pub, 13357 int x, 13358 int y, 13359 const char *string, 13360 int len); 13361 13362 void XftDrawString16 (XftDraw *draw, 13363 const XftColor *color, 13364 XftFont *pub, 13365 int x, 13366 int y, 13367 const wchar *string, 13368 int len); 13369 13370 void XftDrawString32 (XftDraw *draw, 13371 const XftColor *color, 13372 XftFont *pub, 13373 int x, 13374 int y, 13375 const dchar *string, 13376 int len); 13377 13378 void XftDrawStringUtf8 (XftDraw *draw, 13379 const XftColor *color, 13380 XftFont *pub, 13381 int x, 13382 int y, 13383 const char *string, 13384 int len); 13385 void XftDrawStringUtf16 (XftDraw *draw, 13386 const XftColor *color, 13387 XftFont *pub, 13388 int x, 13389 int y, 13390 const char *string, 13391 FcEndian endian, 13392 int len); 13393 13394 void XftDrawCharSpec (XftDraw *draw, 13395 const XftColor *color, 13396 XftFont *pub, 13397 const XftCharSpec *chars, 13398 int len); 13399 13400 void XftDrawCharFontSpec (XftDraw *draw, 13401 const XftColor *color, 13402 const XftCharFontSpec *chars, 13403 int len); 13404 13405 void XftDrawGlyphSpec (XftDraw *draw, 13406 const XftColor *color, 13407 XftFont *pub, 13408 const XftGlyphSpec *glyphs, 13409 int len); 13410 13411 void XftDrawGlyphFontSpec (XftDraw *draw, 13412 const XftColor *color, 13413 const XftGlyphFontSpec *glyphs, 13414 int len); 13415 13416 void XftDrawRect (XftDraw *draw, 13417 const XftColor *color, 13418 int x, 13419 int y, 13420 uint width, 13421 uint height); 13422 13423 Bool XftDrawSetClip (XftDraw *draw, 13424 Region r); 13425 13426 13427 Bool XftDrawSetClipRectangles (XftDraw *draw, 13428 int xOrigin, 13429 int yOrigin, 13430 const XRectangle *rects, 13431 int n); 13432 13433 void XftDrawSetSubwindowMode (XftDraw *draw, 13434 int mode); 13435 13436 void XftGlyphExtents (Display *dpy, 13437 XftFont *pub, 13438 const uint *glyphs, 13439 int nglyphs, 13440 XGlyphInfo *extents); 13441 13442 void XftTextExtents8 (Display *dpy, 13443 XftFont *pub, 13444 const char *string, 13445 int len, 13446 XGlyphInfo *extents); 13447 13448 void XftTextExtents16 (Display *dpy, 13449 XftFont *pub, 13450 const wchar *string, 13451 int len, 13452 XGlyphInfo *extents); 13453 13454 void XftTextExtents32 (Display *dpy, 13455 XftFont *pub, 13456 const dchar *string, 13457 int len, 13458 XGlyphInfo *extents); 13459 13460 void XftTextExtentsUtf8 (Display *dpy, 13461 XftFont *pub, 13462 const char *string, 13463 int len, 13464 XGlyphInfo *extents); 13465 13466 void XftTextExtentsUtf16 (Display *dpy, 13467 XftFont *pub, 13468 const char *string, 13469 FcEndian endian, 13470 int len, 13471 XGlyphInfo *extents); 13472 13473 FcPattern * XftFontMatch (Display *dpy, 13474 int screen, 13475 const FcPattern *pattern, 13476 FcResult *result); 13477 13478 XftFont * XftFontOpen (Display *dpy, int screen, ...); 13479 13480 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 13481 13482 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 13483 13484 FT_Face XftLockFace (XftFont *pub); 13485 13486 void XftUnlockFace (XftFont *pub); 13487 13488 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 13489 13490 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 13491 13492 dchar XftFontInfoHash (const XftFontInfo *fi); 13493 13494 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 13495 13496 XftFont * XftFontOpenInfo (Display *dpy, 13497 FcPattern *pattern, 13498 XftFontInfo *fi); 13499 13500 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 13501 13502 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 13503 13504 void XftFontClose (Display *dpy, XftFont *pub); 13505 13506 FcBool XftInitFtLibrary(); 13507 void XftFontLoadGlyphs (Display *dpy, 13508 XftFont *pub, 13509 FcBool need_bitmaps, 13510 const uint *glyphs, 13511 int nglyph); 13512 13513 void XftFontUnloadGlyphs (Display *dpy, 13514 XftFont *pub, 13515 const uint *glyphs, 13516 int nglyph); 13517 13518 FcBool XftFontCheckGlyph (Display *dpy, 13519 XftFont *pub, 13520 FcBool need_bitmaps, 13521 uint glyph, 13522 uint *missing, 13523 int *nmissing); 13524 13525 FcBool XftCharExists (Display *dpy, 13526 XftFont *pub, 13527 dchar ucs4); 13528 13529 uint XftCharIndex (Display *dpy, 13530 XftFont *pub, 13531 dchar ucs4); 13532 FcBool XftInit (const char *config); 13533 13534 int XftGetVersion (); 13535 13536 FcFontSet * XftListFonts (Display *dpy, 13537 int screen, 13538 ...); 13539 13540 FcPattern *XftNameParse (const char *name); 13541 13542 void XftGlyphRender (Display *dpy, 13543 int op, 13544 Picture src, 13545 XftFont *pub, 13546 Picture dst, 13547 int srcx, 13548 int srcy, 13549 int x, 13550 int y, 13551 const uint *glyphs, 13552 int nglyphs); 13553 13554 void XftGlyphSpecRender (Display *dpy, 13555 int op, 13556 Picture src, 13557 XftFont *pub, 13558 Picture dst, 13559 int srcx, 13560 int srcy, 13561 const XftGlyphSpec *glyphs, 13562 int nglyphs); 13563 13564 void XftCharSpecRender (Display *dpy, 13565 int op, 13566 Picture src, 13567 XftFont *pub, 13568 Picture dst, 13569 int srcx, 13570 int srcy, 13571 const XftCharSpec *chars, 13572 int len); 13573 void XftGlyphFontSpecRender (Display *dpy, 13574 int op, 13575 Picture src, 13576 Picture dst, 13577 int srcx, 13578 int srcy, 13579 const XftGlyphFontSpec *glyphs, 13580 int nglyphs); 13581 13582 void XftCharFontSpecRender (Display *dpy, 13583 int op, 13584 Picture src, 13585 Picture dst, 13586 int srcx, 13587 int srcy, 13588 const XftCharFontSpec *chars, 13589 int len); 13590 13591 void XftTextRender8 (Display *dpy, 13592 int op, 13593 Picture src, 13594 XftFont *pub, 13595 Picture dst, 13596 int srcx, 13597 int srcy, 13598 int x, 13599 int y, 13600 const char *string, 13601 int len); 13602 void XftTextRender16 (Display *dpy, 13603 int op, 13604 Picture src, 13605 XftFont *pub, 13606 Picture dst, 13607 int srcx, 13608 int srcy, 13609 int x, 13610 int y, 13611 const wchar *string, 13612 int len); 13613 13614 void XftTextRender16BE (Display *dpy, 13615 int op, 13616 Picture src, 13617 XftFont *pub, 13618 Picture dst, 13619 int srcx, 13620 int srcy, 13621 int x, 13622 int y, 13623 const char *string, 13624 int len); 13625 13626 void XftTextRender16LE (Display *dpy, 13627 int op, 13628 Picture src, 13629 XftFont *pub, 13630 Picture dst, 13631 int srcx, 13632 int srcy, 13633 int x, 13634 int y, 13635 const char *string, 13636 int len); 13637 13638 void XftTextRender32 (Display *dpy, 13639 int op, 13640 Picture src, 13641 XftFont *pub, 13642 Picture dst, 13643 int srcx, 13644 int srcy, 13645 int x, 13646 int y, 13647 const dchar *string, 13648 int len); 13649 13650 void XftTextRender32BE (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 char *string, 13660 int len); 13661 13662 void XftTextRender32LE (Display *dpy, 13663 int op, 13664 Picture src, 13665 XftFont *pub, 13666 Picture dst, 13667 int srcx, 13668 int srcy, 13669 int x, 13670 int y, 13671 const char *string, 13672 int len); 13673 13674 void XftTextRenderUtf8 (Display *dpy, 13675 int op, 13676 Picture src, 13677 XftFont *pub, 13678 Picture dst, 13679 int srcx, 13680 int srcy, 13681 int x, 13682 int y, 13683 const char *string, 13684 int len); 13685 13686 void XftTextRenderUtf16 (Display *dpy, 13687 int op, 13688 Picture src, 13689 XftFont *pub, 13690 Picture dst, 13691 int srcx, 13692 int srcy, 13693 int x, 13694 int y, 13695 const char *string, 13696 FcEndian endian, 13697 int len); 13698 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 13699 13700 } 13701 13702 interface FontConfig { 13703 extern(C) @nogc pure: 13704 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 13705 void FcFontSetDestroy(FcFontSet*); 13706 char* FcNameUnparse(const FcPattern *); 13707 } 13708 13709 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 13710 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 13711 13712 13713 /* Xft } */ 13714 13715 class XDisconnectException : Exception { 13716 bool userRequested; 13717 this(bool userRequested = true) { 13718 this.userRequested = userRequested; 13719 super("X disconnected"); 13720 } 13721 } 13722 13723 /++ 13724 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 13725 13726 Please note that it returns 13727 +/ 13728 XErrorEvent[] trapXErrors(scope void delegate() dg) { 13729 13730 static XErrorEvent[] errorBuffer; 13731 13732 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 13733 errorBuffer ~= *evt; 13734 return 0; 13735 } 13736 13737 auto savedErrorHandler = XSetErrorHandler(&handler); 13738 13739 try { 13740 dg(); 13741 } finally { 13742 XSync(XDisplayConnection.get, 0/*False*/); 13743 XSetErrorHandler(savedErrorHandler); 13744 } 13745 13746 auto bfr = errorBuffer; 13747 errorBuffer = null; 13748 13749 return bfr; 13750 } 13751 13752 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 13753 class XDisplayConnection { 13754 private __gshared Display* display; 13755 private __gshared XIM xim; 13756 private __gshared char* displayName; 13757 13758 private __gshared int connectionSequence_; 13759 private __gshared bool isLocal_; 13760 13761 /// use this for lazy caching when reconnection 13762 static int connectionSequenceNumber() { return connectionSequence_; } 13763 13764 /++ 13765 Guesses if the connection appears to be local. 13766 13767 History: 13768 Added June 3, 2021 13769 +/ 13770 static @property bool isLocal() nothrow @trusted @nogc { 13771 return isLocal_; 13772 } 13773 13774 /// Attempts recreation of state, may require application assistance 13775 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 13776 /// then call this, and if successful, reenter the loop. 13777 static void discardAndRecreate(string newDisplayString = null) { 13778 if(insideXEventLoop) 13779 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 13780 13781 // 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 13782 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 13783 13784 foreach(handle; chnenhm) { 13785 handle.discardConnectionState(); 13786 } 13787 13788 discardState(); 13789 13790 if(newDisplayString !is null) 13791 setDisplayName(newDisplayString); 13792 13793 auto display = get(); 13794 13795 foreach(handle; chnenhm) { 13796 handle.recreateAfterDisconnect(); 13797 } 13798 } 13799 13800 private __gshared EventMask rootEventMask; 13801 13802 /++ 13803 Requests the specified input from the root window on the connection, in addition to any other request. 13804 13805 13806 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. 13807 13808 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 13809 +/ 13810 static void addRootInput(EventMask mask) { 13811 auto old = rootEventMask; 13812 rootEventMask |= mask; 13813 get(); // to ensure display connected 13814 if(display !is null && rootEventMask != old) 13815 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 13816 } 13817 13818 static void discardState() { 13819 freeImages(); 13820 13821 foreach(atomPtr; interredAtoms) 13822 *atomPtr = 0; 13823 interredAtoms = null; 13824 interredAtoms.assumeSafeAppend(); 13825 13826 ScreenPainterImplementation.fontAttempted = false; 13827 ScreenPainterImplementation.defaultfont = null; 13828 ScreenPainterImplementation.defaultfontset = null; 13829 13830 Image.impl.xshmQueryCompleted = false; 13831 Image.impl._xshmAvailable = false; 13832 13833 SimpleWindow.nativeMapping = null; 13834 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 13835 // GlobalHotkeyManager 13836 13837 display = null; 13838 xim = null; 13839 } 13840 13841 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 13842 private static void createXIM () { 13843 import core.stdc.locale : setlocale, LC_ALL; 13844 import core.stdc.stdio : stderr, fprintf; 13845 import core.stdc.stdlib : free; 13846 import core.stdc.string : strdup; 13847 13848 static immutable string[3] mtry = [ null, "@im=local", "@im=" ]; 13849 13850 auto olocale = strdup(setlocale(LC_ALL, null)); 13851 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 13852 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 13853 13854 //fprintf(stderr, "opening IM...\n"); 13855 foreach (string s; mtry) { 13856 if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 13857 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 13858 } 13859 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 13860 } 13861 13862 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 13863 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 13864 static struct ImgList { 13865 size_t img; // class; hide it from GC 13866 ImgList* next; 13867 } 13868 13869 static __gshared ImgList* imglist = null; 13870 static __gshared bool imglistLocked = false; // true: don't register and unregister images 13871 13872 static void registerImage (Image img) { 13873 if (!imglistLocked && img !is null) { 13874 import core.stdc.stdlib : malloc; 13875 auto it = cast(ImgList*)malloc(ImgList.sizeof); 13876 assert(it !is null); // do proper checks 13877 it.img = cast(size_t)cast(void*)img; 13878 it.next = imglist; 13879 imglist = it; 13880 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 13881 } 13882 } 13883 13884 static void unregisterImage (Image img) { 13885 if (!imglistLocked && img !is null) { 13886 import core.stdc.stdlib : free; 13887 ImgList* prev = null; 13888 ImgList* cur = imglist; 13889 while (cur !is null) { 13890 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 13891 prev = cur; 13892 cur = cur.next; 13893 } 13894 if (cur !is null) { 13895 if (prev is null) imglist = cur.next; else prev.next = cur.next; 13896 free(cur); 13897 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 13898 } else { 13899 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 13900 } 13901 } 13902 } 13903 13904 static void freeImages () { // needed for discardAndRecreate 13905 imglistLocked = true; 13906 scope(exit) imglistLocked = false; 13907 ImgList* cur = imglist; 13908 ImgList* next = null; 13909 while (cur !is null) { 13910 import core.stdc.stdlib : free; 13911 next = cur.next; 13912 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 13913 (cast(Image)cast(void*)cur.img).dispose(); 13914 free(cur); 13915 cur = next; 13916 } 13917 imglist = null; 13918 } 13919 13920 /// can be used to override normal handling of display name 13921 /// from environment and/or command line 13922 static setDisplayName(string newDisplayName) { 13923 displayName = cast(char*) (newDisplayName ~ '\0'); 13924 } 13925 13926 /// resets to the default display string 13927 static resetDisplayName() { 13928 displayName = null; 13929 } 13930 13931 /// 13932 static Display* get() { 13933 if(display is null) { 13934 if(!librariesSuccessfullyLoaded) 13935 throw new Exception("Unable to load X11 client libraries"); 13936 display = XOpenDisplay(displayName); 13937 13938 isLocal_ = false; 13939 13940 connectionSequence_++; 13941 if(display is null) 13942 throw new Exception("Unable to open X display"); 13943 13944 auto str = display.display_name; 13945 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 13946 // and otherwise it probably isn't 13947 if(str is null || (str[0] != ':' && str[0] != '/')) 13948 isLocal_ = false; 13949 else 13950 isLocal_ = true; 13951 13952 //XSetErrorHandler(&adrlogger); 13953 //XSynchronize(display, true); 13954 13955 13956 XSetIOErrorHandler(&x11ioerrCB); 13957 Bool sup; 13958 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 13959 createXIM(); 13960 version(with_eventloop) { 13961 import arsd.eventloop; 13962 addFileEventListeners(display.fd, &eventListener, null, null); 13963 } 13964 } 13965 13966 return display; 13967 } 13968 13969 extern(C) 13970 static int x11ioerrCB(Display* dpy) { 13971 throw new XDisconnectException(false); 13972 } 13973 13974 version(with_eventloop) { 13975 import arsd.eventloop; 13976 static void eventListener(OsFileHandle fd) { 13977 //this.mtLock(); 13978 //scope(exit) this.mtUnlock(); 13979 while(XPending(display)) 13980 doXNextEvent(display); 13981 } 13982 } 13983 13984 // close connection on program exit -- we need this to properly free all images 13985 static ~this () { 13986 // the gui thread must clean up after itself or else Xlib might deadlock 13987 // using this flag on any thread destruction is the easiest way i know of 13988 // (shared static this is run by the LAST thread to exit, which may not be 13989 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 13990 if(thisIsGuiThread) 13991 close(); 13992 } 13993 13994 /// 13995 static void close() { 13996 if(display is null) 13997 return; 13998 13999 version(with_eventloop) { 14000 import arsd.eventloop; 14001 removeFileEventListeners(display.fd); 14002 } 14003 14004 // now remove all registered images to prevent shared memory leaks 14005 freeImages(); 14006 14007 // tbh I don't know why it is doing this but like if this happens to run 14008 // from the other thread there's frequent hanging inside here. 14009 if(thisIsGuiThread) 14010 XCloseDisplay(display); 14011 display = null; 14012 } 14013 } 14014 14015 mixin template NativeImageImplementation() { 14016 XImage* handle; 14017 ubyte* rawData; 14018 14019 XShmSegmentInfo shminfo; 14020 14021 __gshared bool xshmQueryCompleted; 14022 __gshared bool _xshmAvailable; 14023 public static @property bool xshmAvailable() { 14024 if(!xshmQueryCompleted) { 14025 int i1, i2, i3; 14026 xshmQueryCompleted = true; 14027 14028 if(!XDisplayConnection.isLocal) 14029 _xshmAvailable = false; 14030 else 14031 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14032 } 14033 return _xshmAvailable; 14034 } 14035 14036 bool usingXshm; 14037 final: 14038 14039 private __gshared bool xshmfailed; 14040 14041 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14042 auto display = XDisplayConnection.get(); 14043 assert(display !is null); 14044 auto screen = DefaultScreen(display); 14045 14046 // it will only use shared memory for somewhat largish images, 14047 // since otherwise we risk wasting shared memory handles on a lot of little ones 14048 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14049 14050 14051 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14052 // the actual use still fails. For example, if the program is in a container and permission denied 14053 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14054 // 14055 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14056 14057 14058 // synchronize so preexisting buffers are clear 14059 XSync(display, false); 14060 xshmfailed = false; 14061 14062 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14063 14064 14065 usingXshm = true; 14066 handle = XShmCreateImage( 14067 display, 14068 DefaultVisual(display, screen), 14069 enableAlpha ? 32: 24, 14070 ImageFormat.ZPixmap, 14071 null, 14072 &shminfo, 14073 width, height); 14074 if(handle is null) 14075 goto abortXshm1; 14076 14077 if(handle.bytes_per_line != 4 * width) 14078 goto abortXshm2; 14079 14080 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14081 if(shminfo.shmid < 0) 14082 goto abortXshm3; 14083 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14084 if(rawData == cast(ubyte*) -1) 14085 goto abortXshm4; 14086 shminfo.readOnly = 0; 14087 XShmAttach(display, &shminfo); 14088 14089 // and now to the final error check to ensure it actually worked. 14090 XSync(display, false); 14091 if(xshmfailed) 14092 goto abortXshm5; 14093 14094 XSetErrorHandler(oldErrorHandler); 14095 14096 XDisplayConnection.registerImage(this); 14097 // if I don't flush here there's a chance the dtor will run before the 14098 // ctor and lead to a bad value X error. While this hurts the efficiency 14099 // it is local anyway so prolly better to keep it simple 14100 XFlush(display); 14101 14102 return; 14103 14104 abortXshm5: 14105 shmdt(shminfo.shmaddr); 14106 rawData = null; 14107 14108 abortXshm4: 14109 shmctl(shminfo.shmid, IPC_RMID, null); 14110 14111 abortXshm3: 14112 // nothing needed, the shmget failed so there's nothing to free 14113 14114 abortXshm2: 14115 XDestroyImage(handle); 14116 handle = null; 14117 14118 abortXshm1: 14119 XSetErrorHandler(oldErrorHandler); 14120 usingXshm = false; 14121 handle = null; 14122 14123 shminfo = typeof(shminfo).init; 14124 14125 _xshmAvailable = false; // don't try again in the future 14126 14127 //import std.stdio; writeln("fallingback"); 14128 14129 goto fallback; 14130 14131 } else { 14132 fallback: 14133 14134 if (forcexshm) throw new Exception("can't create XShm Image"); 14135 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 14136 import core.stdc.stdlib : malloc; 14137 rawData = cast(ubyte*) malloc(width * height * 4); 14138 14139 handle = XCreateImage( 14140 display, 14141 DefaultVisual(display, screen), 14142 enableAlpha ? 32 : 24, // bpp 14143 ImageFormat.ZPixmap, 14144 0, // offset 14145 rawData, 14146 width, height, 14147 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 14148 } 14149 } 14150 14151 void dispose() { 14152 // note: this calls free(rawData) for us 14153 if(handle) { 14154 if (usingXshm) { 14155 XDisplayConnection.unregisterImage(this); 14156 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 14157 } 14158 XDestroyImage(handle); 14159 if(usingXshm) { 14160 shmdt(shminfo.shmaddr); 14161 shmctl(shminfo.shmid, IPC_RMID, null); 14162 } 14163 handle = null; 14164 } 14165 } 14166 14167 Color getPixel(int x, int y) { 14168 auto offset = (y * width + x) * 4; 14169 Color c; 14170 c.a = enableAlpha ? rawData[offset + 3] : 255; 14171 c.b = rawData[offset + 0]; 14172 c.g = rawData[offset + 1]; 14173 c.r = rawData[offset + 2]; 14174 if(enableAlpha) 14175 c.unPremultiply; 14176 return c; 14177 } 14178 14179 void setPixel(int x, int y, Color c) { 14180 if(enableAlpha) 14181 c.premultiply(); 14182 auto offset = (y * width + x) * 4; 14183 rawData[offset + 0] = c.b; 14184 rawData[offset + 1] = c.g; 14185 rawData[offset + 2] = c.r; 14186 if(enableAlpha) 14187 rawData[offset + 3] = c.a; 14188 } 14189 14190 void convertToRgbaBytes(ubyte[] where) { 14191 assert(where.length == this.width * this.height * 4); 14192 14193 // if rawData had a length.... 14194 //assert(rawData.length == where.length); 14195 for(int idx = 0; idx < where.length; idx += 4) { 14196 where[idx + 0] = rawData[idx + 2]; // r 14197 where[idx + 1] = rawData[idx + 1]; // g 14198 where[idx + 2] = rawData[idx + 0]; // b 14199 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 14200 14201 if(enableAlpha) 14202 unPremultiplyRgba(where[idx .. idx + 4]); 14203 } 14204 } 14205 14206 void setFromRgbaBytes(in ubyte[] where) { 14207 assert(where.length == this.width * this.height * 4); 14208 14209 // if rawData had a length.... 14210 //assert(rawData.length == where.length); 14211 for(int idx = 0; idx < where.length; idx += 4) { 14212 rawData[idx + 2] = where[idx + 0]; // r 14213 rawData[idx + 1] = where[idx + 1]; // g 14214 rawData[idx + 0] = where[idx + 2]; // b 14215 if(enableAlpha) { 14216 rawData[idx + 3] = where[idx + 3]; // a 14217 premultiplyBgra(rawData[idx .. idx + 4]); 14218 } 14219 } 14220 } 14221 14222 } 14223 14224 mixin template NativeSimpleWindowImplementation() { 14225 GC gc; 14226 Window window; 14227 Display* display; 14228 14229 Pixmap buffer; 14230 int bufferw, bufferh; // size of the buffer; can be bigger than window 14231 XIC xic; // input context 14232 int curHidden = 0; // counter 14233 Cursor blankCurPtr = 0; 14234 int cursorSequenceNumber = 0; 14235 int warpEventCount = 0; // number of mouse movement events to eat 14236 14237 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 14238 X11GetSelectionHandler[Atom] getSelectionHandlers; 14239 14240 version(without_opengl) {} else 14241 GLXContext glc; 14242 14243 private void fixFixedSize(bool forced=false) (int width, int height) { 14244 if (forced || this.resizability == Resizability.fixedSize) { 14245 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 14246 XSizeHints sh; 14247 static if (!forced) { 14248 c_long spr; 14249 XGetWMNormalHints(display, window, &sh, &spr); 14250 sh.flags |= PMaxSize | PMinSize; 14251 } else { 14252 sh.flags = PMaxSize | PMinSize; 14253 } 14254 sh.min_width = width; 14255 sh.min_height = height; 14256 sh.max_width = width; 14257 sh.max_height = height; 14258 XSetWMNormalHints(display, window, &sh); 14259 //XFlush(display); 14260 } 14261 } 14262 14263 ScreenPainter getPainter(bool manualInvalidations) { 14264 return ScreenPainter(this, window, manualInvalidations); 14265 } 14266 14267 void move(int x, int y) { 14268 XMoveWindow(display, window, x, y); 14269 } 14270 14271 void resize(int w, int h) { 14272 if (w < 1) w = 1; 14273 if (h < 1) h = 1; 14274 XResizeWindow(display, window, w, h); 14275 14276 // calling this now to avoid waiting for the server to 14277 // acknowledge the resize; draws without returning to the 14278 // event loop will thus actually work. the server's event 14279 // btw might overrule this and resize it again 14280 recordX11Resize(display, this, w, h); 14281 14282 // FIXME: do we need to set this as the opengl context to do the glViewport change? 14283 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 14284 } 14285 14286 void moveResize (int x, int y, int w, int h) { 14287 if (w < 1) w = 1; 14288 if (h < 1) h = 1; 14289 XMoveResizeWindow(display, window, x, y, w, h); 14290 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 14291 } 14292 14293 void hideCursor () { 14294 if (curHidden++ == 0) { 14295 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 14296 static const(char)[1] cmbmp = 0; 14297 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 14298 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 14299 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 14300 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 14301 XFreePixmap(display, pm); 14302 } 14303 XDefineCursor(display, window, blankCurPtr); 14304 } 14305 } 14306 14307 void showCursor () { 14308 if (--curHidden == 0) XUndefineCursor(display, window); 14309 } 14310 14311 void warpMouse (int x, int y) { 14312 // here i will send dummy "ignore next mouse motion" event, 14313 // 'cause `XWarpPointer()` sends synthesised mouse motion, 14314 // and we don't need to report it to the user (as warping is 14315 // used when the user needs movement deltas). 14316 //XClientMessageEvent xclient; 14317 XEvent e; 14318 e.xclient.type = EventType.ClientMessage; 14319 e.xclient.window = window; 14320 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14321 e.xclient.format = 32; 14322 e.xclient.data.l[0] = 0; 14323 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 14324 //{ 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]); } 14325 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14326 // now warp pointer... 14327 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 14328 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 14329 // ...and flush 14330 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 14331 XFlush(display); 14332 } 14333 14334 void sendDummyEvent () { 14335 // here i will send dummy event to ping event queue 14336 XEvent e; 14337 e.xclient.type = EventType.ClientMessage; 14338 e.xclient.window = window; 14339 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14340 e.xclient.format = 32; 14341 e.xclient.data.l[0] = 0; 14342 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14343 XFlush(display); 14344 } 14345 14346 void setTitle(string title) { 14347 if (title.ptr is null) title = ""; 14348 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14349 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14350 XTextProperty windowName; 14351 windowName.value = title.ptr; 14352 windowName.encoding = XA_UTF8; //XA_STRING; 14353 windowName.format = 8; 14354 windowName.nitems = cast(uint)title.length; 14355 XSetWMName(display, window, &windowName); 14356 char[1024] namebuf = 0; 14357 auto maxlen = namebuf.length-1; 14358 if (maxlen > title.length) maxlen = title.length; 14359 namebuf[0..maxlen] = title[0..maxlen]; 14360 XStoreName(display, window, namebuf.ptr); 14361 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 14362 flushGui(); // without this OpenGL windows has a LONG delay before changing title 14363 } 14364 14365 string[] getTitles() { 14366 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14367 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14368 XTextProperty textProp; 14369 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 14370 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 14371 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 14372 } else 14373 return []; 14374 } else 14375 return null; 14376 } 14377 14378 string getTitle() { 14379 auto titles = getTitles(); 14380 return titles.length ? titles[0] : null; 14381 } 14382 14383 void setMinSize (int minwidth, int minheight) { 14384 import core.stdc.config : c_long; 14385 if (minwidth < 1) minwidth = 1; 14386 if (minheight < 1) minheight = 1; 14387 XSizeHints sh; 14388 c_long spr; 14389 XGetWMNormalHints(display, window, &sh, &spr); 14390 sh.min_width = minwidth; 14391 sh.min_height = minheight; 14392 sh.flags |= PMinSize; 14393 XSetWMNormalHints(display, window, &sh); 14394 flushGui(); 14395 } 14396 14397 void setMaxSize (int maxwidth, int maxheight) { 14398 import core.stdc.config : c_long; 14399 if (maxwidth < 1) maxwidth = 1; 14400 if (maxheight < 1) maxheight = 1; 14401 XSizeHints sh; 14402 c_long spr; 14403 XGetWMNormalHints(display, window, &sh, &spr); 14404 sh.max_width = maxwidth; 14405 sh.max_height = maxheight; 14406 sh.flags |= PMaxSize; 14407 XSetWMNormalHints(display, window, &sh); 14408 flushGui(); 14409 } 14410 14411 void setResizeGranularity (int granx, int grany) { 14412 import core.stdc.config : c_long; 14413 if (granx < 1) granx = 1; 14414 if (grany < 1) grany = 1; 14415 XSizeHints sh; 14416 c_long spr; 14417 XGetWMNormalHints(display, window, &sh, &spr); 14418 sh.width_inc = granx; 14419 sh.height_inc = grany; 14420 sh.flags |= PResizeInc; 14421 XSetWMNormalHints(display, window, &sh); 14422 flushGui(); 14423 } 14424 14425 void setOpacity (uint opacity) { 14426 arch_ulong o = opacity; 14427 if (opacity == uint.max) 14428 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 14429 else 14430 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 14431 XA_CARDINAL, 32, PropModeReplace, &o, 1); 14432 } 14433 14434 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 14435 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 14436 display = XDisplayConnection.get(); 14437 auto screen = DefaultScreen(display); 14438 14439 bool overrideRedirect = false; 14440 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 14441 overrideRedirect = true; 14442 14443 version(without_opengl) {} 14444 else { 14445 if(opengl == OpenGlOptions.yes) { 14446 GLXFBConfig fbconf = null; 14447 XVisualInfo* vi = null; 14448 bool useLegacy = false; 14449 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 14450 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 14451 int[23] visualAttribs = [ 14452 GLX_X_RENDERABLE , 1/*True*/, 14453 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 14454 GLX_RENDER_TYPE , GLX_RGBA_BIT, 14455 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 14456 GLX_RED_SIZE , 8, 14457 GLX_GREEN_SIZE , 8, 14458 GLX_BLUE_SIZE , 8, 14459 GLX_ALPHA_SIZE , 8, 14460 GLX_DEPTH_SIZE , 24, 14461 GLX_STENCIL_SIZE , 8, 14462 GLX_DOUBLEBUFFER , 1/*True*/, 14463 0/*None*/, 14464 ]; 14465 int fbcount; 14466 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 14467 if (fbcount == 0) { 14468 useLegacy = true; // try to do at least something 14469 } else { 14470 // pick the FB config/visual with the most samples per pixel 14471 int bestidx = -1, bestns = -1; 14472 foreach (int fbi; 0..fbcount) { 14473 int sb, samples; 14474 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 14475 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 14476 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 14477 } 14478 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 14479 fbconf = fbc[bestidx]; 14480 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 14481 XFree(fbc); 14482 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 14483 } 14484 } 14485 if (vi is null || useLegacy) { 14486 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 14487 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 14488 useLegacy = true; 14489 } 14490 if (vi is null) throw new Exception("no open gl visual found"); 14491 14492 XSetWindowAttributes swa; 14493 auto root = RootWindow(display, screen); 14494 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 14495 14496 swa.override_redirect = overrideRedirect; 14497 14498 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14499 0, 0, width, height, 14500 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 14501 14502 // now try to use `glXCreateContextAttribsARB()` if it's here 14503 if (!useLegacy) { 14504 // request fairly advanced context, even with stencil buffer! 14505 int[9] contextAttribs = [ 14506 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 14507 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 14508 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 14509 // for modern context, set "forward compatibility" flag too 14510 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 14511 0/*None*/, 14512 ]; 14513 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 14514 if (glc is null && sdpyOpenGLContextAllowFallback) { 14515 sdpyOpenGLContextVersion = 0; 14516 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14517 } 14518 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 14519 } else { 14520 // fallback to old GLX call 14521 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 14522 sdpyOpenGLContextVersion = 0; 14523 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14524 } 14525 } 14526 // sync to ensure any errors generated are processed 14527 XSync(display, 0/*False*/); 14528 //{ import core.stdc.stdio; printf("ogl is here\n"); } 14529 if(glc is null) 14530 throw new Exception("glc"); 14531 } 14532 } 14533 14534 if(opengl == OpenGlOptions.no) { 14535 14536 XSetWindowAttributes swa; 14537 swa.background_pixel = WhitePixel(display, screen); 14538 swa.border_pixel = BlackPixel(display, screen); 14539 swa.override_redirect = overrideRedirect; 14540 auto root = RootWindow(display, screen); 14541 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 14542 14543 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14544 0, 0, width, height, 14545 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 14546 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 14547 14548 14549 14550 /* 14551 window = XCreateSimpleWindow( 14552 display, 14553 parent is null ? RootWindow(display, screen) : parent.impl.window, 14554 0, 0, // x, y 14555 width, height, 14556 1, // border width 14557 BlackPixel(display, screen), // border 14558 WhitePixel(display, screen)); // background 14559 */ 14560 14561 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 14562 bufferw = width; 14563 bufferh = height; 14564 14565 gc = DefaultGC(display, screen); 14566 14567 // clear out the buffer to get us started... 14568 XSetForeground(display, gc, WhitePixel(display, screen)); 14569 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 14570 XSetForeground(display, gc, BlackPixel(display, screen)); 14571 } 14572 14573 // input context 14574 //TODO: create this only for top-level windows, and reuse that? 14575 if (XDisplayConnection.xim !is null) { 14576 xic = XCreateIC(XDisplayConnection.xim, 14577 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 14578 /*XNClientWindow*/"clientWindow".ptr, window, 14579 /*XNFocusWindow*/"focusWindow".ptr, window, 14580 null); 14581 if (xic is null) { 14582 import core.stdc.stdio : stderr, fprintf; 14583 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 14584 } 14585 } 14586 14587 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 14588 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 14589 // window class 14590 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 14591 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 14592 XClassHint klass; 14593 XWMHints wh; 14594 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14595 wh.input = true; 14596 wh.flags |= InputHint; 14597 } 14598 XSizeHints size; 14599 klass.res_name = sdpyWindowClassStr; 14600 klass.res_class = sdpyWindowClassStr; 14601 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 14602 } 14603 14604 setTitle(title); 14605 SimpleWindow.nativeMapping[window] = this; 14606 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 14607 14608 // This gives our window a close button 14609 if (windowType != WindowTypes.eventOnly) { 14610 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 14611 int useAtoms; 14612 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14613 useAtoms = 2; 14614 } else { 14615 useAtoms = 1; 14616 } 14617 assert(useAtoms <= atoms.length); 14618 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 14619 } 14620 14621 // FIXME: windowType and customizationFlags 14622 Atom[8] wsatoms; // here, due to goto 14623 int wmsacount = 0; // here, due to goto 14624 14625 try 14626 final switch(windowType) { 14627 case WindowTypes.normal: 14628 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14629 break; 14630 case WindowTypes.undecorated: 14631 motifHideDecorations(); 14632 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14633 break; 14634 case WindowTypes.eventOnly: 14635 _hidden = true; 14636 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 14637 goto hiddenWindow; 14638 //break; 14639 case WindowTypes.nestedChild: 14640 // handled in XCreateWindow calls 14641 break; 14642 14643 case WindowTypes.dropdownMenu: 14644 motifHideDecorations(); 14645 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 14646 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14647 break; 14648 case WindowTypes.popupMenu: 14649 motifHideDecorations(); 14650 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 14651 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14652 break; 14653 case WindowTypes.notification: 14654 motifHideDecorations(); 14655 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 14656 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14657 break; 14658 /+ 14659 case WindowTypes.menu: 14660 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14661 motifHideDecorations(); 14662 break; 14663 case WindowTypes.desktop: 14664 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 14665 break; 14666 case WindowTypes.dock: 14667 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 14668 break; 14669 case WindowTypes.toolbar: 14670 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 14671 break; 14672 case WindowTypes.menu: 14673 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14674 break; 14675 case WindowTypes.utility: 14676 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 14677 break; 14678 case WindowTypes.splash: 14679 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 14680 break; 14681 case WindowTypes.dialog: 14682 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 14683 break; 14684 case WindowTypes.tooltip: 14685 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 14686 break; 14687 case WindowTypes.notification: 14688 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 14689 break; 14690 case WindowTypes.combo: 14691 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 14692 break; 14693 case WindowTypes.dnd: 14694 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 14695 break; 14696 +/ 14697 } 14698 catch(Exception e) { 14699 // XInternAtom failed, prolly a WM 14700 // that doesn't support these things 14701 } 14702 14703 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 14704 // the two following flags may be ignored by WM 14705 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 14706 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 14707 14708 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 14709 14710 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 14711 14712 // What would be ideal here is if they only were 14713 // selected if there was actually an event handler 14714 // for them... 14715 14716 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 14717 14718 hiddenWindow: 14719 14720 // set the pid property for lookup later by window managers 14721 // a standard convenience 14722 import core.sys.posix.unistd; 14723 arch_ulong pid = getpid(); 14724 14725 XChangeProperty( 14726 display, 14727 impl.window, 14728 GetAtom!("_NET_WM_PID", true)(display), 14729 XA_CARDINAL, 14730 32 /* bits */, 14731 0 /*PropModeReplace*/, 14732 &pid, 14733 1); 14734 14735 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 14736 if(parent is null) assert(0); 14737 XChangeProperty( 14738 display, 14739 impl.window, 14740 GetAtom!("WM_TRANSIENT_FOR", true)(display), 14741 XA_WINDOW, 14742 32 /* bits */, 14743 0 /*PropModeReplace*/, 14744 &parent.impl.window, 14745 1); 14746 14747 } 14748 14749 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 14750 XMapWindow(display, window); 14751 } else { 14752 _hidden = true; 14753 } 14754 } 14755 14756 void selectDefaultInput(bool forceIncludeMouseMotion) { 14757 auto mask = EventMask.ExposureMask | 14758 EventMask.KeyPressMask | 14759 EventMask.KeyReleaseMask | 14760 EventMask.PropertyChangeMask | 14761 EventMask.FocusChangeMask | 14762 EventMask.StructureNotifyMask | 14763 EventMask.VisibilityChangeMask 14764 | EventMask.ButtonPressMask 14765 | EventMask.ButtonReleaseMask 14766 ; 14767 14768 // xshm is our shortcut for local connections 14769 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 14770 mask |= EventMask.PointerMotionMask; 14771 else 14772 mask |= EventMask.ButtonMotionMask; 14773 14774 XSelectInput(display, window, mask); 14775 } 14776 14777 14778 void setNetWMWindowType(Atom type) { 14779 Atom[2] atoms; 14780 14781 atoms[0] = type; 14782 // generic fallback 14783 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 14784 14785 XChangeProperty( 14786 display, 14787 impl.window, 14788 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 14789 XA_ATOM, 14790 32 /* bits */, 14791 0 /*PropModeReplace*/, 14792 atoms.ptr, 14793 cast(int) atoms.length); 14794 } 14795 14796 void motifHideDecorations(bool hide = true) { 14797 MwmHints hints; 14798 hints.flags = MWM_HINTS_DECORATIONS; 14799 hints.decorations = hide ? 0 : 1; 14800 14801 XChangeProperty( 14802 display, 14803 impl.window, 14804 GetAtom!"_MOTIF_WM_HINTS"(display), 14805 GetAtom!"_MOTIF_WM_HINTS"(display), 14806 32 /* bits */, 14807 0 /*PropModeReplace*/, 14808 &hints, 14809 hints.sizeof / 4); 14810 } 14811 14812 /*k8: unused 14813 void createOpenGlContext() { 14814 14815 } 14816 */ 14817 14818 void closeWindow() { 14819 // I can't close this or a child window closing will 14820 // break events for everyone. So I'm just leaking it right 14821 // now and that is probably perfectly fine... 14822 version(none) 14823 if (customEventFDRead != -1) { 14824 import core.sys.posix.unistd : close; 14825 auto same = customEventFDRead == customEventFDWrite; 14826 14827 close(customEventFDRead); 14828 if(!same) 14829 close(customEventFDWrite); 14830 customEventFDRead = -1; 14831 customEventFDWrite = -1; 14832 } 14833 if(buffer) 14834 XFreePixmap(display, buffer); 14835 bufferw = bufferh = 0; 14836 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 14837 XDestroyWindow(display, window); 14838 XFlush(display); 14839 } 14840 14841 void dispose() { 14842 } 14843 14844 bool destroyed = false; 14845 } 14846 14847 bool insideXEventLoop; 14848 } 14849 14850 version(X11) { 14851 14852 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 14853 14854 private class ResizeEvent { 14855 int width, height; 14856 } 14857 14858 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 14859 if(win.pendingResizeEvent is null) { 14860 win.pendingResizeEvent = new ResizeEvent(); 14861 win.addEventListener((ResizeEvent re) { 14862 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 14863 }); 14864 } 14865 win.pendingResizeEvent.width = width; 14866 win.pendingResizeEvent.height = height; 14867 if(!win.eventQueued!ResizeEvent) { 14868 win.postEvent(win.pendingResizeEvent); 14869 } 14870 } 14871 14872 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 14873 if(width != win.width || height != win.height) { 14874 win._width = width; 14875 win._height = height; 14876 14877 if(win.openglMode == OpenGlOptions.no) { 14878 // FIXME: could this be more efficient? 14879 14880 if (win.bufferw < width || win.bufferh < height) { 14881 //{ 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); } 14882 // grow the internal buffer to match the window... 14883 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 14884 { 14885 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 14886 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 14887 scope(exit) XFreeGC(win.display, xgc); 14888 XSetClipMask(win.display, xgc, None); 14889 XSetForeground(win.display, xgc, 0); 14890 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 14891 } 14892 XCopyArea(display, 14893 cast(Drawable) win.buffer, 14894 cast(Drawable) newPixmap, 14895 win.gc, 0, 0, 14896 win.bufferw < width ? win.bufferw : win.width, 14897 win.bufferh < height ? win.bufferh : win.height, 14898 0, 0); 14899 14900 XFreePixmap(display, win.buffer); 14901 win.buffer = newPixmap; 14902 win.bufferw = width; 14903 win.bufferh = height; 14904 } 14905 14906 // clear unused parts of the buffer 14907 if (win.bufferw > width || win.bufferh > height) { 14908 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 14909 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 14910 scope(exit) XFreeGC(win.display, xgc); 14911 XSetClipMask(win.display, xgc, None); 14912 XSetForeground(win.display, xgc, 0); 14913 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 14914 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 14915 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 14916 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 14917 } 14918 14919 } 14920 14921 version(without_opengl) {} else 14922 if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) { 14923 glViewport(0, 0, width, height); 14924 } 14925 14926 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 14927 14928 if(win.windowResized !is null) { 14929 XUnlockDisplay(display); 14930 scope(exit) XLockDisplay(display); 14931 win.windowResized(width, height); 14932 } 14933 } 14934 } 14935 14936 14937 /// Platform-specific, you might use it when doing a custom event loop. 14938 bool doXNextEvent(Display* display) { 14939 bool done; 14940 XEvent e; 14941 XNextEvent(display, &e); 14942 version(sddddd) { 14943 import std.stdio, std.conv : to; 14944 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 14945 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 14946 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 14947 } 14948 } 14949 14950 // filter out compose events 14951 if (XFilterEvent(&e, None)) { 14952 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 14953 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 14954 return false; 14955 } 14956 // process keyboard mapping changes 14957 if (e.type == EventType.KeymapNotify) { 14958 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 14959 XRefreshKeyboardMapping(&e.xmapping); 14960 return false; 14961 } 14962 14963 version(with_eventloop) 14964 import arsd.eventloop; 14965 14966 if(SimpleWindow.handleNativeGlobalEvent !is null) { 14967 // see windows impl's comments 14968 XUnlockDisplay(display); 14969 scope(exit) XLockDisplay(display); 14970 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 14971 if(ret == 0) 14972 return done; 14973 } 14974 14975 14976 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 14977 if(win.getNativeEventHandler !is null) { 14978 XUnlockDisplay(display); 14979 scope(exit) XLockDisplay(display); 14980 auto ret = win.getNativeEventHandler()(e); 14981 if(ret == 0) 14982 return done; 14983 } 14984 } 14985 14986 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 14987 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 14988 // we get this because of the RRScreenChangeNotifyMask 14989 14990 // this isn't actually an ideal way to do it since it wastes time 14991 // but meh it is simple and it works. 14992 win.actualDpiLoadAttempted = false; 14993 SimpleWindow.xRandrInfoLoadAttemped = false; 14994 win.updateActualDpi(); // trigger a reload 14995 } 14996 } 14997 14998 switch(e.type) { 14999 case EventType.SelectionClear: 15000 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15001 // FIXME so it is supposed to finish any in progress transfers... but idk... 15002 //import std.stdio; writeln("SelectionClear"); 15003 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15004 } 15005 break; 15006 case EventType.SelectionRequest: 15007 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15008 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15009 // import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15010 XUnlockDisplay(display); 15011 scope(exit) XLockDisplay(display); 15012 (*ssh).handleRequest(e); 15013 } 15014 break; 15015 case EventType.PropertyNotify: 15016 // import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15017 15018 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15019 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15020 ssh.sendMoreIncr(&e.xproperty); 15021 } 15022 15023 15024 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15025 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15026 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15027 Atom target; 15028 int format; 15029 arch_ulong bytesafter, length; 15030 void* value; 15031 15032 ubyte[] s; 15033 Atom targetToKeep; 15034 15035 XGetWindowProperty( 15036 e.xproperty.display, 15037 e.xproperty.window, 15038 e.xproperty.atom, 15039 0, 15040 100000 /* length */, 15041 true, /* erase it to signal we got it and want more */ 15042 0 /*AnyPropertyType*/, 15043 &target, &format, &length, &bytesafter, &value); 15044 15045 if(!targetToKeep) 15046 targetToKeep = target; 15047 15048 auto id = (cast(ubyte*) value)[0 .. length]; 15049 15050 handler.handleIncrData(targetToKeep, id); 15051 15052 XFree(value); 15053 } 15054 } 15055 break; 15056 case EventType.SelectionNotify: 15057 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15058 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15059 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15060 XUnlockDisplay(display); 15061 scope(exit) XLockDisplay(display); 15062 handler.handleData(None, null); 15063 } else { 15064 Atom target; 15065 int format; 15066 arch_ulong bytesafter, length; 15067 void* value; 15068 XGetWindowProperty( 15069 e.xselection.display, 15070 e.xselection.requestor, 15071 e.xselection.property, 15072 0, 15073 100000 /* length */, 15074 //false, /* don't erase it */ 15075 true, /* do erase it lol */ 15076 0 /*AnyPropertyType*/, 15077 &target, &format, &length, &bytesafter, &value); 15078 15079 // FIXME: I don't have to copy it now since it is in char[] instead of string 15080 15081 { 15082 XUnlockDisplay(display); 15083 scope(exit) XLockDisplay(display); 15084 15085 if(target == XA_ATOM) { 15086 // initial request, see what they are able to work with and request the best one 15087 // we can handle, if available 15088 15089 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15090 Atom best = handler.findBestFormat(answer); 15091 15092 /+ 15093 writeln("got ", answer); 15094 foreach(a; answer) 15095 printf("%s\n", XGetAtomName(display, a)); 15096 writeln("best ", best); 15097 +/ 15098 15099 if(best != None) { 15100 // actually request the best format 15101 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15102 } 15103 } else if(target == GetAtom!"INCR"(display)) { 15104 // incremental 15105 15106 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 15107 15108 // signal the sending program that we see 15109 // the incr and are ready to receive more. 15110 XDeleteProperty( 15111 e.xselection.display, 15112 e.xselection.requestor, 15113 e.xselection.property); 15114 } else { 15115 // unsupported type... maybe, forward 15116 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 15117 } 15118 } 15119 XFree(value); 15120 /* 15121 XDeleteProperty( 15122 e.xselection.display, 15123 e.xselection.requestor, 15124 e.xselection.property); 15125 */ 15126 } 15127 } 15128 break; 15129 case EventType.ConfigureNotify: 15130 auto event = e.xconfigure; 15131 if(auto win = event.window in SimpleWindow.nativeMapping) { 15132 //version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); } 15133 15134 /+ 15135 The ICCCM says window managers must send a synthetic event when the window 15136 is moved but NOT when it is resized. In the resize case, an event is sent 15137 with position (0, 0) which can be wrong and break the dpi calculations. 15138 15139 So we only consider the synthetic events from the WM and otherwise 15140 need to wait for some other event to get the position which... sucks. 15141 15142 I'd rather not have windows changing their layout on mouse motion after 15143 switching monitors... might be forced to but for now just ignoring it. 15144 15145 Easiest way to switch monitors without sending a size position is by 15146 maximize or fullscreen in a setup like mine, but on most setups those 15147 work on the monitor it is already living on, so it should be ok most the 15148 time. 15149 +/ 15150 if(event.send_event) { 15151 win.screenPositionKnown = true; 15152 win.screenPositionX = event.x; 15153 win.screenPositionY = event.y; 15154 win.updateActualDpi(); 15155 } 15156 15157 recordX11ResizeAsync(display, *win, event.width, event.height); 15158 } 15159 break; 15160 case EventType.Expose: 15161 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 15162 // if it is closing from a popup menu, it can get 15163 // an Expose event right by the end and trigger a 15164 // BadDrawable error ... we'll just check 15165 // closed to handle that. 15166 if((*win).closed) break; 15167 if((*win).openglMode == OpenGlOptions.no) { 15168 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 15169 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 15170 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); 15171 } else { 15172 // need to redraw the scene somehow 15173 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 15174 XUnlockDisplay(display); 15175 scope(exit) XLockDisplay(display); 15176 version(without_opengl) {} else 15177 win.redrawOpenGlSceneSoon(); 15178 } 15179 } 15180 } 15181 break; 15182 case EventType.FocusIn: 15183 case EventType.FocusOut: 15184 15185 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15186 /+ 15187 15188 void info(string detail) { 15189 string s; 15190 import std.conv; 15191 import std.datetime; 15192 s ~= to!string(Clock.currTime); 15193 s ~= " "; 15194 s ~= e.type == EventType.FocusIn ? "in " : "out"; 15195 s ~= " "; 15196 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 15197 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 15198 s ~= detail; 15199 s ~= " "; 15200 15201 sdpyPrintDebugString(s); 15202 15203 } 15204 15205 switch(e.xfocus.detail) { 15206 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 15207 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 15208 case NotifyDetail.NotifyInferior: info("Inferior"); break; 15209 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 15210 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 15211 case NotifyDetail.NotifyPointer: info("pointer"); break; 15212 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 15213 case NotifyDetail.NotifyDetailNone: info("none"); break; 15214 default: 15215 15216 } 15217 +/ 15218 15219 15220 if (win.xic !is null) { 15221 //{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); } 15222 if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic); 15223 } 15224 15225 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 15226 break; // just ignore these they seem irrelevant 15227 15228 auto old = win._focused; 15229 win._focused = e.type == EventType.FocusIn; 15230 15231 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 15232 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 15233 win._focused = true; 15234 15235 if(win.demandingAttention) 15236 demandAttention(*win, false); 15237 15238 if(old != win._focused && win.onFocusChange) { 15239 XUnlockDisplay(display); 15240 scope(exit) XLockDisplay(display); 15241 win.onFocusChange(win._focused); 15242 } 15243 } 15244 break; 15245 case EventType.VisibilityNotify: 15246 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15247 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 15248 if (win.visibilityChanged !is null) { 15249 XUnlockDisplay(display); 15250 scope(exit) XLockDisplay(display); 15251 win.visibilityChanged(false); 15252 } 15253 } else { 15254 if (win.visibilityChanged !is null) { 15255 XUnlockDisplay(display); 15256 scope(exit) XLockDisplay(display); 15257 win.visibilityChanged(true); 15258 } 15259 } 15260 } 15261 break; 15262 case EventType.ClientMessage: 15263 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 15264 // "ignore next mouse motion" event, increment ignore counter for teh window 15265 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15266 ++(*win).warpEventCount; 15267 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 15268 } else { 15269 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 15270 } 15271 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 15272 // user clicked the close button on the window manager 15273 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15274 XUnlockDisplay(display); 15275 scope(exit) XLockDisplay(display); 15276 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 15277 } 15278 15279 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 15280 //import std.stdio; writeln("HAPPENED"); 15281 // user clicked the close button on the window manager 15282 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15283 XUnlockDisplay(display); 15284 scope(exit) XLockDisplay(display); 15285 15286 auto setTo = *win; 15287 15288 if(win.setRequestedInputFocus !is null) { 15289 auto s = win.setRequestedInputFocus(); 15290 if(s !is null) 15291 setTo = s; 15292 } 15293 15294 assert(setTo !is null); 15295 15296 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 15297 15298 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 15299 } 15300 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 15301 foreach(nai; NotificationAreaIcon.activeIcons) 15302 nai.newManager(); 15303 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15304 15305 bool xDragWindow = true; 15306 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 15307 //XDefineCursor(display, xDragWindow.impl.window, 15308 //import std.stdio; writeln("XdndStatus ", e.xclient.data.l); 15309 } 15310 if(auto dh = win.dropHandler) { 15311 15312 static Atom[3] xFormatsBuffer; 15313 static Atom[] xFormats; 15314 15315 void resetXFormats() { 15316 xFormatsBuffer[] = 0; 15317 xFormats = xFormatsBuffer[]; 15318 } 15319 15320 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 15321 // on Windows it is supposed to return the effect you actually do FIXME 15322 15323 auto sourceWindow = e.xclient.data.l[0]; 15324 15325 xFormatsBuffer[0] = e.xclient.data.l[2]; 15326 xFormatsBuffer[1] = e.xclient.data.l[3]; 15327 xFormatsBuffer[2] = e.xclient.data.l[4]; 15328 15329 if(e.xclient.data.l[1] & 1) { 15330 // can just grab it all but like we don't necessarily need them... 15331 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 15332 } else { 15333 int len; 15334 foreach(fmt; xFormatsBuffer) 15335 if(fmt) len++; 15336 xFormats = xFormatsBuffer[0 .. len]; 15337 } 15338 15339 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 15340 15341 dh.dragEnter(&pkg); 15342 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 15343 15344 auto pack = e.xclient.data.l[2]; 15345 15346 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 15347 15348 15349 XClientMessageEvent xclient; 15350 15351 xclient.type = EventType.ClientMessage; 15352 xclient.window = e.xclient.data.l[0]; 15353 xclient.message_type = GetAtom!"XdndStatus"(display); 15354 xclient.format = 32; 15355 xclient.data.l[0] = win.impl.window; 15356 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 15357 auto r = result.consistentWithin; 15358 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 15359 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 15360 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 15361 15362 XSendEvent( 15363 display, 15364 e.xclient.data.l[0], 15365 false, 15366 EventMask.NoEventMask, 15367 cast(XEvent*) &xclient 15368 ); 15369 15370 15371 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 15372 //import std.stdio; writeln("XdndLeave"); 15373 // drop cancelled. 15374 // data.l[0] is the source window 15375 dh.dragLeave(); 15376 15377 resetXFormats(); 15378 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 15379 // drop happening, should fetch data, then send finished 15380 //import std.stdio; writeln("XdndDrop"); 15381 15382 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 15383 15384 dh.drop(&pkg); 15385 15386 resetXFormats(); 15387 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 15388 // import std.stdio; writeln("XdndFinished"); 15389 15390 dh.finish(); 15391 } 15392 15393 } 15394 } 15395 break; 15396 case EventType.MapNotify: 15397 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 15398 (*win)._visible = true; 15399 if (!(*win)._visibleForTheFirstTimeCalled) { 15400 (*win)._visibleForTheFirstTimeCalled = true; 15401 if ((*win).visibleForTheFirstTime !is null) { 15402 XUnlockDisplay(display); 15403 scope(exit) XLockDisplay(display); 15404 version(without_opengl) {} else { 15405 if((*win).openglMode == OpenGlOptions.yes) { 15406 (*win).setAsCurrentOpenGlContextNT(); 15407 glViewport(0, 0, (*win).width, (*win).height); 15408 } 15409 } 15410 (*win).visibleForTheFirstTime(); 15411 } 15412 } 15413 if ((*win).visibilityChanged !is null) { 15414 XUnlockDisplay(display); 15415 scope(exit) XLockDisplay(display); 15416 (*win).visibilityChanged(true); 15417 } 15418 } 15419 break; 15420 case EventType.UnmapNotify: 15421 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 15422 win._visible = false; 15423 if (win.visibilityChanged !is null) { 15424 XUnlockDisplay(display); 15425 scope(exit) XLockDisplay(display); 15426 win.visibilityChanged(false); 15427 } 15428 } 15429 break; 15430 case EventType.DestroyNotify: 15431 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 15432 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 15433 win._closed = true; // just in case 15434 win.destroyed = true; 15435 if (win.xic !is null) { 15436 XDestroyIC(win.xic); 15437 win.xic = null; // just in calse 15438 } 15439 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 15440 bool anyImportant = false; 15441 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 15442 if(w.beingOpenKeepsAppOpen) { 15443 anyImportant = true; 15444 break; 15445 } 15446 if(!anyImportant) { 15447 EventLoop.quitApplication(); 15448 done = true; 15449 } 15450 } 15451 auto window = e.xdestroywindow.window; 15452 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 15453 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 15454 15455 version(with_eventloop) { 15456 if(done) exit(); 15457 } 15458 break; 15459 15460 case EventType.MotionNotify: 15461 MouseEvent mouse; 15462 auto event = e.xmotion; 15463 15464 mouse.type = MouseEventType.motion; 15465 mouse.x = event.x; 15466 mouse.y = event.y; 15467 mouse.modifierState = event.state; 15468 15469 mouse.timestamp = event.time; 15470 15471 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 15472 mouse.window = *win; 15473 if (win.warpEventCount > 0) { 15474 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 15475 --(*win).warpEventCount; 15476 (*win).mdx(mouse); // so deltas will be correctly updated 15477 } else { 15478 win.warpEventCount = 0; // just in case 15479 (*win).mdx(mouse); 15480 if((*win).handleMouseEvent) { 15481 XUnlockDisplay(display); 15482 scope(exit) XLockDisplay(display); 15483 (*win).handleMouseEvent(mouse); 15484 } 15485 } 15486 } 15487 15488 version(with_eventloop) 15489 send(mouse); 15490 break; 15491 case EventType.ButtonPress: 15492 case EventType.ButtonRelease: 15493 MouseEvent mouse; 15494 auto event = e.xbutton; 15495 15496 mouse.timestamp = event.time; 15497 15498 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 15499 mouse.x = event.x; 15500 mouse.y = event.y; 15501 15502 static Time lastMouseDownTime = 0; 15503 15504 mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 15505 if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time; 15506 15507 switch(event.button) { 15508 case 1: mouse.button = MouseButton.left; break; // left 15509 case 2: mouse.button = MouseButton.middle; break; // middle 15510 case 3: mouse.button = MouseButton.right; break; // right 15511 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 15512 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 15513 case 6: break; // idk 15514 case 7: break; // idk 15515 case 8: mouse.button = MouseButton.backButton; break; 15516 case 9: mouse.button = MouseButton.forwardButton; break; 15517 default: 15518 } 15519 15520 // FIXME: double check this 15521 mouse.modifierState = event.state; 15522 15523 //mouse.modifierState = event.detail; 15524 15525 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 15526 mouse.window = *win; 15527 (*win).mdx(mouse); 15528 if((*win).handleMouseEvent) { 15529 XUnlockDisplay(display); 15530 scope(exit) XLockDisplay(display); 15531 (*win).handleMouseEvent(mouse); 15532 } 15533 } 15534 version(with_eventloop) 15535 send(mouse); 15536 break; 15537 15538 case EventType.KeyPress: 15539 case EventType.KeyRelease: 15540 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 15541 KeyEvent ke; 15542 ke.pressed = e.type == EventType.KeyPress; 15543 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 15544 15545 auto sym = XKeycodeToKeysym( 15546 XDisplayConnection.get(), 15547 e.xkey.keycode, 15548 0); 15549 15550 ke.key = cast(Key) sym;//e.xkey.keycode; 15551 15552 ke.modifierState = e.xkey.state; 15553 15554 // import std.stdio; writefln("%x", sym); 15555 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 15556 int charbuflen = 0; // return value of XwcLookupString 15557 if (ke.pressed) { 15558 auto win = e.xkey.window in SimpleWindow.nativeMapping; 15559 if (win !is null && win.xic !is null) { 15560 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 15561 Status status; 15562 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 15563 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 15564 } else { 15565 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 15566 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 15567 char[16] buffer; 15568 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 15569 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 15570 } 15571 } 15572 15573 // if there's no char, subst one 15574 if (charbuflen == 0) { 15575 switch (sym) { 15576 case 0xff09: charbuf[charbuflen++] = '\t'; break; 15577 case 0xff8d: // keypad enter 15578 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 15579 default : // ignore 15580 } 15581 } 15582 15583 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 15584 ke.window = *win; 15585 15586 15587 if(win.inputProxy) 15588 win = &win.inputProxy; 15589 15590 // char events are separate since they are on Windows too 15591 // also, xcompose can generate long char sequences 15592 // don't send char events if Meta and/or Hyper is pressed 15593 // TODO: ctrl+char should only send control chars; not yet 15594 if ((e.xkey.state&ModifierState.ctrl) != 0) { 15595 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 15596 } 15597 15598 dchar[32] charsComingBuffer; 15599 int charsComingPosition; 15600 dchar[] charsComing = charsComingBuffer[]; 15601 15602 if (ke.pressed && charbuflen > 0) { 15603 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 15604 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 15605 if(charsComingPosition >= charsComing.length) 15606 charsComing.length = charsComingPosition + 8; 15607 15608 charsComing[charsComingPosition++] = ch; 15609 } 15610 15611 charsComing = charsComing[0 .. charsComingPosition]; 15612 } else { 15613 charsComing = null; 15614 } 15615 15616 ke.charsPossible = charsComing; 15617 15618 if (win.handleKeyEvent) { 15619 XUnlockDisplay(display); 15620 scope(exit) XLockDisplay(display); 15621 win.handleKeyEvent(ke); 15622 } 15623 15624 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 15625 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 15626 XUnlockDisplay(display); 15627 scope(exit) XLockDisplay(display); 15628 foreach(ch; charsComing) 15629 win.handleCharEvent(ch); 15630 } 15631 } 15632 15633 version(with_eventloop) 15634 send(ke); 15635 break; 15636 default: 15637 } 15638 15639 return done; 15640 } 15641 } 15642 15643 /* *************************************** */ 15644 /* Done with simpledisplay stuff */ 15645 /* *************************************** */ 15646 15647 // Necessary C library bindings follow 15648 version(Windows) {} else 15649 version(X11) { 15650 15651 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 15652 15653 // X11 bindings needed here 15654 /* 15655 A little of this is from the bindings project on 15656 D Source and some of it is copy/paste from the C 15657 header. 15658 15659 The DSource listing consistently used D's long 15660 where C used long. That's wrong - C long is 32 bit, so 15661 it should be int in D. I changed that here. 15662 15663 Note: 15664 This isn't complete, just took what I needed for myself. 15665 */ 15666 15667 import core.stdc.stddef : wchar_t; 15668 15669 interface XLib { 15670 extern(C) nothrow @nogc { 15671 char* XResourceManagerString(Display*); 15672 void XrmInitialize(); 15673 XrmDatabase XrmGetStringDatabase(char* data); 15674 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 15675 15676 Cursor XCreateFontCursor(Display*, uint shape); 15677 int XDefineCursor(Display* display, Window w, Cursor cursor); 15678 int XUndefineCursor(Display* display, Window w); 15679 15680 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 15681 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 15682 int XFreeCursor(Display* display, Cursor cursor); 15683 15684 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 15685 15686 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 15687 15688 char *XKeysymToString(KeySym keysym); 15689 KeySym XKeycodeToKeysym( 15690 Display* /* display */, 15691 KeyCode /* keycode */, 15692 int /* index */ 15693 ); 15694 15695 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 15696 15697 int XFree(void*); 15698 int XDeleteProperty(Display *display, Window w, Atom property); 15699 15700 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements); 15701 15702 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 15703 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 15704 *actual_type_return, int *actual_format_return, arch_ulong 15705 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 15706 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 15707 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 15708 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 15709 15710 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 15711 15712 Window XGetSelectionOwner(Display *display, Atom selection); 15713 15714 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 15715 15716 char** XListFonts(Display*, const char*, int, int*); 15717 void XFreeFontNames(char**); 15718 15719 Display* XOpenDisplay(const char*); 15720 int XCloseDisplay(Display*); 15721 15722 int XSynchronize(Display*, bool); 15723 15724 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 15725 15726 Bool XSupportsLocale(); 15727 char* XSetLocaleModifiers(const(char)* modifier_list); 15728 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 15729 Status XCloseOM(XOM om); 15730 15731 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 15732 Status XCloseIM(XIM im); 15733 15734 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 15735 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 15736 Display* XDisplayOfIM(XIM im); 15737 char* XLocaleOfIM(XIM im); 15738 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 15739 void XDestroyIC(XIC ic); 15740 void XSetICFocus(XIC ic); 15741 void XUnsetICFocus(XIC ic); 15742 //wchar_t* XwcResetIC(XIC ic); 15743 char* XmbResetIC(XIC ic); 15744 char* Xutf8ResetIC(XIC ic); 15745 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 15746 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 15747 XIM XIMOfIC(XIC ic); 15748 15749 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 15750 15751 15752 XFontStruct *XLoadQueryFont(Display *display, in char *name); 15753 int XFreeFont(Display *display, XFontStruct *font_struct); 15754 int XSetFont(Display* display, GC gc, Font font); 15755 int XTextWidth(XFontStruct*, in char*, int); 15756 15757 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 15758 int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n); 15759 15760 Window XCreateSimpleWindow( 15761 Display* /* display */, 15762 Window /* parent */, 15763 int /* x */, 15764 int /* y */, 15765 uint /* width */, 15766 uint /* height */, 15767 uint /* border_width */, 15768 uint /* border */, 15769 uint /* background */ 15770 ); 15771 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); 15772 15773 int XReparentWindow(Display*, Window, Window, int, int); 15774 int XClearWindow(Display*, Window); 15775 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 15776 int XMoveWindow(Display*, Window, int, int); 15777 int XResizeWindow(Display *display, Window w, uint width, uint height); 15778 15779 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 15780 15781 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 15782 15783 XImage *XCreateImage( 15784 Display* /* display */, 15785 Visual* /* visual */, 15786 uint /* depth */, 15787 int /* format */, 15788 int /* offset */, 15789 ubyte* /* data */, 15790 uint /* width */, 15791 uint /* height */, 15792 int /* bitmap_pad */, 15793 int /* bytes_per_line */ 15794 ); 15795 15796 Status XInitImage (XImage* image); 15797 15798 Atom XInternAtom( 15799 Display* /* display */, 15800 const char* /* atom_name */, 15801 Bool /* only_if_exists */ 15802 ); 15803 15804 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 15805 char* XGetAtomName(Display*, Atom); 15806 Status XGetAtomNames(Display*, Atom*, int count, char**); 15807 15808 int XPutImage( 15809 Display* /* display */, 15810 Drawable /* d */, 15811 GC /* gc */, 15812 XImage* /* image */, 15813 int /* src_x */, 15814 int /* src_y */, 15815 int /* dest_x */, 15816 int /* dest_y */, 15817 uint /* width */, 15818 uint /* height */ 15819 ); 15820 15821 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 15822 15823 15824 int XDestroyWindow( 15825 Display* /* display */, 15826 Window /* w */ 15827 ); 15828 15829 int XDestroyImage(XImage*); 15830 15831 int XSelectInput( 15832 Display* /* display */, 15833 Window /* w */, 15834 EventMask /* event_mask */ 15835 ); 15836 15837 int XMapWindow( 15838 Display* /* display */, 15839 Window /* w */ 15840 ); 15841 15842 Status XIconifyWindow(Display*, Window, int); 15843 int XMapRaised(Display*, Window); 15844 int XMapSubwindows(Display*, Window); 15845 15846 int XNextEvent( 15847 Display* /* display */, 15848 XEvent* /* event_return */ 15849 ); 15850 15851 int XMaskEvent(Display*, arch_long, XEvent*); 15852 15853 Bool XFilterEvent(XEvent *event, Window window); 15854 int XRefreshKeyboardMapping(XMappingEvent *event_map); 15855 15856 Status XSetWMProtocols( 15857 Display* /* display */, 15858 Window /* w */, 15859 Atom* /* protocols */, 15860 int /* count */ 15861 ); 15862 15863 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 15864 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 15865 15866 15867 Status XInitThreads(); 15868 void XLockDisplay (Display* display); 15869 void XUnlockDisplay (Display* display); 15870 15871 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 15872 15873 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 15874 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 15875 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 15876 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 15877 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 15878 15879 15880 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 15881 int XDrawString(Display*, Drawable, GC, int, int, in char*, int); 15882 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 15883 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 15884 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 15885 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 15886 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 15887 int XDrawPoint(Display*, Drawable, GC, int, int); 15888 int XSetForeground(Display*, GC, uint); 15889 int XSetBackground(Display*, GC, uint); 15890 15891 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 15892 void XFreeFontSet(Display*, XFontSet); 15893 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int); 15894 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 15895 15896 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 15897 15898 15899 //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); 15900 15901 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 15902 int XSetFunction(Display*, GC, int); 15903 15904 GC XCreateGC(Display*, Drawable, uint, void*); 15905 int XCopyGC(Display*, GC, uint, GC); 15906 int XFreeGC(Display*, GC); 15907 15908 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 15909 bool XCheckMaskEvent(Display*, int, XEvent*); 15910 15911 int XPending(Display*); 15912 int XEventsQueued(Display* display, int mode); 15913 15914 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 15915 int XFreePixmap(Display*, Pixmap); 15916 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 15917 int XFlush(Display*); 15918 int XBell(Display*, int); 15919 int XSync(Display*, bool); 15920 15921 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 15922 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 15923 15924 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 15925 int XUngrabKeyboard(Display*, Time); 15926 15927 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 15928 15929 KeySym XStringToKeysym(const char *string); 15930 15931 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 15932 15933 Window XDefaultRootWindow(Display*); 15934 15935 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); 15936 15937 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 15938 15939 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 15940 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 15941 15942 Status XAllocColor(Display*, Colormap, XColor*); 15943 15944 int XWithdrawWindow(Display*, Window, int); 15945 int XUnmapWindow(Display*, Window); 15946 int XLowerWindow(Display*, Window); 15947 int XRaiseWindow(Display*, Window); 15948 15949 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); 15950 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); 15951 15952 int XGetInputFocus(Display*, Window*, int*); 15953 int XSetInputFocus(Display*, Window, int, Time); 15954 15955 XErrorHandler XSetErrorHandler(XErrorHandler); 15956 15957 int XGetErrorText(Display*, int, char*, int); 15958 15959 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 15960 15961 15962 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); 15963 int XUngrabPointer(Display *display, Time time); 15964 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 15965 15966 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 15967 15968 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 15969 int XSetClipMask(Display*, GC, Pixmap); 15970 int XSetClipOrigin(Display*, GC, int, int); 15971 15972 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 15973 15974 void XSetWMName(Display*, Window, XTextProperty*); 15975 Status XGetWMName(Display*, Window, XTextProperty*); 15976 int XStoreName(Display* display, Window w, const(char)* window_name); 15977 15978 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 15979 15980 } 15981 } 15982 15983 interface Xext { 15984 extern(C) nothrow @nogc { 15985 Status XShmAttach(Display*, XShmSegmentInfo*); 15986 Status XShmDetach(Display*, XShmSegmentInfo*); 15987 Status XShmPutImage( 15988 Display* /* dpy */, 15989 Drawable /* d */, 15990 GC /* gc */, 15991 XImage* /* image */, 15992 int /* src_x */, 15993 int /* src_y */, 15994 int /* dst_x */, 15995 int /* dst_y */, 15996 uint /* src_width */, 15997 uint /* src_height */, 15998 Bool /* send_event */ 15999 ); 16000 16001 Status XShmQueryExtension(Display*); 16002 16003 XImage *XShmCreateImage( 16004 Display* /* dpy */, 16005 Visual* /* visual */, 16006 uint /* depth */, 16007 int /* format */, 16008 char* /* data */, 16009 XShmSegmentInfo* /* shminfo */, 16010 uint /* width */, 16011 uint /* height */ 16012 ); 16013 16014 Pixmap XShmCreatePixmap( 16015 Display* /* dpy */, 16016 Drawable /* d */, 16017 char* /* data */, 16018 XShmSegmentInfo* /* shminfo */, 16019 uint /* width */, 16020 uint /* height */, 16021 uint /* depth */ 16022 ); 16023 16024 } 16025 } 16026 16027 // this requires -lXpm 16028 //int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16029 16030 16031 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16032 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16033 shared static this() { 16034 xlib.loadDynamicLibrary(); 16035 xext.loadDynamicLibrary(); 16036 } 16037 16038 16039 extern(C) nothrow @nogc { 16040 16041 alias XrmDatabase = void*; 16042 struct XrmValue { 16043 uint size; 16044 void* addr; 16045 } 16046 16047 struct XVisualInfo { 16048 Visual* visual; 16049 VisualID visualid; 16050 int screen; 16051 uint depth; 16052 int c_class; 16053 c_ulong red_mask; 16054 c_ulong green_mask; 16055 c_ulong blue_mask; 16056 int colormap_size; 16057 int bits_per_rgb; 16058 } 16059 16060 enum VisualNoMask= 0x0; 16061 enum VisualIDMask= 0x1; 16062 enum VisualScreenMask=0x2; 16063 enum VisualDepthMask= 0x4; 16064 enum VisualClassMask= 0x8; 16065 enum VisualRedMaskMask=0x10; 16066 enum VisualGreenMaskMask=0x20; 16067 enum VisualBlueMaskMask=0x40; 16068 enum VisualColormapSizeMask=0x80; 16069 enum VisualBitsPerRGBMask=0x100; 16070 enum VisualAllMask= 0x1FF; 16071 16072 enum AnyKey = 0; 16073 enum AnyModifier = 1 << 15; 16074 16075 // XIM and other crap 16076 struct _XOM {} 16077 struct _XIM {} 16078 struct _XIC {} 16079 alias XOM = _XOM*; 16080 alias XIM = _XIM*; 16081 alias XIC = _XIC*; 16082 16083 alias XIMStyle = arch_ulong; 16084 enum : arch_ulong { 16085 XIMPreeditArea = 0x0001, 16086 XIMPreeditCallbacks = 0x0002, 16087 XIMPreeditPosition = 0x0004, 16088 XIMPreeditNothing = 0x0008, 16089 XIMPreeditNone = 0x0010, 16090 XIMStatusArea = 0x0100, 16091 XIMStatusCallbacks = 0x0200, 16092 XIMStatusNothing = 0x0400, 16093 XIMStatusNone = 0x0800, 16094 } 16095 16096 16097 /* X Shared Memory Extension functions */ 16098 //pragma(lib, "Xshm"); 16099 alias arch_ulong ShmSeg; 16100 struct XShmSegmentInfo { 16101 ShmSeg shmseg; 16102 int shmid; 16103 ubyte* shmaddr; 16104 Bool readOnly; 16105 } 16106 16107 // and the necessary OS functions 16108 int shmget(int, size_t, int); 16109 void* shmat(int, in void*, int); 16110 int shmdt(in void*); 16111 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 16112 16113 enum IPC_PRIVATE = 0; 16114 enum IPC_CREAT = 512; 16115 enum IPC_RMID = 0; 16116 16117 /* MIT-SHM end */ 16118 16119 16120 enum MappingType:int { 16121 MappingModifier =0, 16122 MappingKeyboard =1, 16123 MappingPointer =2 16124 } 16125 16126 /* ImageFormat -- PutImage, GetImage */ 16127 enum ImageFormat:int { 16128 XYBitmap =0, /* depth 1, XYFormat */ 16129 XYPixmap =1, /* depth == drawable depth */ 16130 ZPixmap =2 /* depth == drawable depth */ 16131 } 16132 16133 enum ModifierName:int { 16134 ShiftMapIndex =0, 16135 LockMapIndex =1, 16136 ControlMapIndex =2, 16137 Mod1MapIndex =3, 16138 Mod2MapIndex =4, 16139 Mod3MapIndex =5, 16140 Mod4MapIndex =6, 16141 Mod5MapIndex =7 16142 } 16143 16144 enum ButtonMask:int { 16145 Button1Mask =1<<8, 16146 Button2Mask =1<<9, 16147 Button3Mask =1<<10, 16148 Button4Mask =1<<11, 16149 Button5Mask =1<<12, 16150 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16151 } 16152 16153 enum KeyOrButtonMask:uint { 16154 ShiftMask =1<<0, 16155 LockMask =1<<1, 16156 ControlMask =1<<2, 16157 Mod1Mask =1<<3, 16158 Mod2Mask =1<<4, 16159 Mod3Mask =1<<5, 16160 Mod4Mask =1<<6, 16161 Mod5Mask =1<<7, 16162 Button1Mask =1<<8, 16163 Button2Mask =1<<9, 16164 Button3Mask =1<<10, 16165 Button4Mask =1<<11, 16166 Button5Mask =1<<12, 16167 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16168 } 16169 16170 enum ButtonName:int { 16171 Button1 =1, 16172 Button2 =2, 16173 Button3 =3, 16174 Button4 =4, 16175 Button5 =5 16176 } 16177 16178 /* Notify modes */ 16179 enum NotifyModes:int 16180 { 16181 NotifyNormal =0, 16182 NotifyGrab =1, 16183 NotifyUngrab =2, 16184 NotifyWhileGrabbed =3 16185 } 16186 enum NotifyHint = 1; /* for MotionNotify events */ 16187 16188 /* Notify detail */ 16189 enum NotifyDetail:int 16190 { 16191 NotifyAncestor =0, 16192 NotifyVirtual =1, 16193 NotifyInferior =2, 16194 NotifyNonlinear =3, 16195 NotifyNonlinearVirtual =4, 16196 NotifyPointer =5, 16197 NotifyPointerRoot =6, 16198 NotifyDetailNone =7 16199 } 16200 16201 /* Visibility notify */ 16202 16203 enum VisibilityNotify:int 16204 { 16205 VisibilityUnobscured =0, 16206 VisibilityPartiallyObscured =1, 16207 VisibilityFullyObscured =2 16208 } 16209 16210 16211 enum WindowStackingMethod:int 16212 { 16213 Above =0, 16214 Below =1, 16215 TopIf =2, 16216 BottomIf =3, 16217 Opposite =4 16218 } 16219 16220 /* Circulation request */ 16221 enum CirculationRequest:int 16222 { 16223 PlaceOnTop =0, 16224 PlaceOnBottom =1 16225 } 16226 16227 enum PropertyNotification:int 16228 { 16229 PropertyNewValue =0, 16230 PropertyDelete =1 16231 } 16232 16233 enum ColorMapNotification:int 16234 { 16235 ColormapUninstalled =0, 16236 ColormapInstalled =1 16237 } 16238 16239 16240 struct _XPrivate {} 16241 struct _XrmHashBucketRec {} 16242 16243 alias void* XPointer; 16244 alias void* XExtData; 16245 16246 version( X86_64 ) { 16247 alias ulong XID; 16248 alias ulong arch_ulong; 16249 alias long arch_long; 16250 } else version (AArch64) { 16251 alias ulong XID; 16252 alias ulong arch_ulong; 16253 alias long arch_long; 16254 } else { 16255 alias uint XID; 16256 alias uint arch_ulong; 16257 alias int arch_long; 16258 } 16259 16260 alias XID Window; 16261 alias XID Drawable; 16262 alias XID Pixmap; 16263 16264 alias arch_ulong Atom; 16265 alias int Bool; 16266 alias Display XDisplay; 16267 16268 alias int ByteOrder; 16269 alias arch_ulong Time; 16270 alias void ScreenFormat; 16271 16272 struct XImage { 16273 int width, height; /* size of image */ 16274 int xoffset; /* number of pixels offset in X direction */ 16275 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 16276 void *data; /* pointer to image data */ 16277 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 16278 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 16279 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 16280 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 16281 int depth; /* depth of image */ 16282 int bytes_per_line; /* accelarator to next line */ 16283 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 16284 arch_ulong red_mask; /* bits in z arrangment */ 16285 arch_ulong green_mask; 16286 arch_ulong blue_mask; 16287 XPointer obdata; /* hook for the object routines to hang on */ 16288 static struct F { /* image manipulation routines */ 16289 XImage* function( 16290 XDisplay* /* display */, 16291 Visual* /* visual */, 16292 uint /* depth */, 16293 int /* format */, 16294 int /* offset */, 16295 ubyte* /* data */, 16296 uint /* width */, 16297 uint /* height */, 16298 int /* bitmap_pad */, 16299 int /* bytes_per_line */) create_image; 16300 int function(XImage *) destroy_image; 16301 arch_ulong function(XImage *, int, int) get_pixel; 16302 int function(XImage *, int, int, arch_ulong) put_pixel; 16303 XImage* function(XImage *, int, int, uint, uint) sub_image; 16304 int function(XImage *, arch_long) add_pixel; 16305 } 16306 F f; 16307 } 16308 version(X86_64) static assert(XImage.sizeof == 136); 16309 else version(X86) static assert(XImage.sizeof == 88); 16310 16311 struct XCharStruct { 16312 short lbearing; /* origin to left edge of raster */ 16313 short rbearing; /* origin to right edge of raster */ 16314 short width; /* advance to next char's origin */ 16315 short ascent; /* baseline to top edge of raster */ 16316 short descent; /* baseline to bottom edge of raster */ 16317 ushort attributes; /* per char flags (not predefined) */ 16318 } 16319 16320 /* 16321 * To allow arbitrary information with fonts, there are additional properties 16322 * returned. 16323 */ 16324 struct XFontProp { 16325 Atom name; 16326 arch_ulong card32; 16327 } 16328 16329 alias Atom Font; 16330 16331 struct XFontStruct { 16332 XExtData *ext_data; /* Hook for extension to hang data */ 16333 Font fid; /* Font ID for this font */ 16334 uint direction; /* Direction the font is painted */ 16335 uint min_char_or_byte2; /* First character */ 16336 uint max_char_or_byte2; /* Last character */ 16337 uint min_byte1; /* First row that exists (for two-byte fonts) */ 16338 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 16339 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 16340 uint default_char; /* Char to print for undefined character */ 16341 int n_properties; /* How many properties there are */ 16342 XFontProp *properties; /* Pointer to array of additional properties*/ 16343 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 16344 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 16345 XCharStruct *per_char; /* first_char to last_char information */ 16346 int ascent; /* Max extent above baseline for spacing */ 16347 int descent; /* Max descent below baseline for spacing */ 16348 } 16349 16350 16351 /* 16352 * Definitions of specific events. 16353 */ 16354 struct XKeyEvent 16355 { 16356 int type; /* of event */ 16357 arch_ulong serial; /* # of last request processed by server */ 16358 Bool send_event; /* true if this came from a SendEvent request */ 16359 Display *display; /* Display the event was read from */ 16360 Window window; /* "event" window it is reported relative to */ 16361 Window root; /* root window that the event occurred on */ 16362 Window subwindow; /* child window */ 16363 Time time; /* milliseconds */ 16364 int x, y; /* pointer x, y coordinates in event window */ 16365 int x_root, y_root; /* coordinates relative to root */ 16366 KeyOrButtonMask state; /* key or button mask */ 16367 uint keycode; /* detail */ 16368 Bool same_screen; /* same screen flag */ 16369 } 16370 version(X86_64) static assert(XKeyEvent.sizeof == 96); 16371 alias XKeyEvent XKeyPressedEvent; 16372 alias XKeyEvent XKeyReleasedEvent; 16373 16374 struct XButtonEvent 16375 { 16376 int type; /* of event */ 16377 arch_ulong serial; /* # of last request processed by server */ 16378 Bool send_event; /* true if this came from a SendEvent request */ 16379 Display *display; /* Display the event was read from */ 16380 Window window; /* "event" window it is reported relative to */ 16381 Window root; /* root window that the event occurred on */ 16382 Window subwindow; /* child window */ 16383 Time time; /* milliseconds */ 16384 int x, y; /* pointer x, y coordinates in event window */ 16385 int x_root, y_root; /* coordinates relative to root */ 16386 KeyOrButtonMask state; /* key or button mask */ 16387 uint button; /* detail */ 16388 Bool same_screen; /* same screen flag */ 16389 } 16390 alias XButtonEvent XButtonPressedEvent; 16391 alias XButtonEvent XButtonReleasedEvent; 16392 16393 struct XMotionEvent{ 16394 int type; /* of event */ 16395 arch_ulong serial; /* # of last request processed by server */ 16396 Bool send_event; /* true if this came from a SendEvent request */ 16397 Display *display; /* Display the event was read from */ 16398 Window window; /* "event" window reported relative to */ 16399 Window root; /* root window that the event occurred on */ 16400 Window subwindow; /* child window */ 16401 Time time; /* milliseconds */ 16402 int x, y; /* pointer x, y coordinates in event window */ 16403 int x_root, y_root; /* coordinates relative to root */ 16404 KeyOrButtonMask state; /* key or button mask */ 16405 byte is_hint; /* detail */ 16406 Bool same_screen; /* same screen flag */ 16407 } 16408 alias XMotionEvent XPointerMovedEvent; 16409 16410 struct XCrossingEvent{ 16411 int type; /* of event */ 16412 arch_ulong serial; /* # of last request processed by server */ 16413 Bool send_event; /* true if this came from a SendEvent request */ 16414 Display *display; /* Display the event was read from */ 16415 Window window; /* "event" window reported relative to */ 16416 Window root; /* root window that the event occurred on */ 16417 Window subwindow; /* child window */ 16418 Time time; /* milliseconds */ 16419 int x, y; /* pointer x, y coordinates in event window */ 16420 int x_root, y_root; /* coordinates relative to root */ 16421 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 16422 NotifyDetail detail; 16423 /* 16424 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16425 * NotifyNonlinear,NotifyNonlinearVirtual 16426 */ 16427 Bool same_screen; /* same screen flag */ 16428 Bool focus; /* Boolean focus */ 16429 KeyOrButtonMask state; /* key or button mask */ 16430 } 16431 alias XCrossingEvent XEnterWindowEvent; 16432 alias XCrossingEvent XLeaveWindowEvent; 16433 16434 struct XFocusChangeEvent{ 16435 int type; /* FocusIn or FocusOut */ 16436 arch_ulong serial; /* # of last request processed by server */ 16437 Bool send_event; /* true if this came from a SendEvent request */ 16438 Display *display; /* Display the event was read from */ 16439 Window window; /* window of event */ 16440 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 16441 NotifyGrab, NotifyUngrab */ 16442 NotifyDetail detail; 16443 /* 16444 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16445 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 16446 * NotifyPointerRoot, NotifyDetailNone 16447 */ 16448 } 16449 alias XFocusChangeEvent XFocusInEvent; 16450 alias XFocusChangeEvent XFocusOutEvent; 16451 16452 enum CWBackPixmap = (1L<<0); 16453 enum CWBackPixel = (1L<<1); 16454 enum CWBorderPixmap = (1L<<2); 16455 enum CWBorderPixel = (1L<<3); 16456 enum CWBitGravity = (1L<<4); 16457 enum CWWinGravity = (1L<<5); 16458 enum CWBackingStore = (1L<<6); 16459 enum CWBackingPlanes = (1L<<7); 16460 enum CWBackingPixel = (1L<<8); 16461 enum CWOverrideRedirect = (1L<<9); 16462 enum CWSaveUnder = (1L<<10); 16463 enum CWEventMask = (1L<<11); 16464 enum CWDontPropagate = (1L<<12); 16465 enum CWColormap = (1L<<13); 16466 enum CWCursor = (1L<<14); 16467 16468 struct XWindowAttributes { 16469 int x, y; /* location of window */ 16470 int width, height; /* width and height of window */ 16471 int border_width; /* border width of window */ 16472 int depth; /* depth of window */ 16473 Visual *visual; /* the associated visual structure */ 16474 Window root; /* root of screen containing window */ 16475 int class_; /* InputOutput, InputOnly*/ 16476 int bit_gravity; /* one of the bit gravity values */ 16477 int win_gravity; /* one of the window gravity values */ 16478 int backing_store; /* NotUseful, WhenMapped, Always */ 16479 arch_ulong backing_planes; /* planes to be preserved if possible */ 16480 arch_ulong backing_pixel; /* value to be used when restoring planes */ 16481 Bool save_under; /* boolean, should bits under be saved? */ 16482 Colormap colormap; /* color map to be associated with window */ 16483 Bool map_installed; /* boolean, is color map currently installed*/ 16484 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 16485 arch_long all_event_masks; /* set of events all people have interest in*/ 16486 arch_long your_event_mask; /* my event mask */ 16487 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 16488 Bool override_redirect; /* boolean value for override-redirect */ 16489 Screen *screen; /* back pointer to correct screen */ 16490 } 16491 16492 enum IsUnmapped = 0; 16493 enum IsUnviewable = 1; 16494 enum IsViewable = 2; 16495 16496 struct XSetWindowAttributes { 16497 Pixmap background_pixmap;/* background, None, or ParentRelative */ 16498 arch_ulong background_pixel;/* background pixel */ 16499 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 16500 arch_ulong border_pixel;/* border pixel value */ 16501 int bit_gravity; /* one of bit gravity values */ 16502 int win_gravity; /* one of the window gravity values */ 16503 int backing_store; /* NotUseful, WhenMapped, Always */ 16504 arch_ulong backing_planes;/* planes to be preserved if possible */ 16505 arch_ulong backing_pixel;/* value to use in restoring planes */ 16506 Bool save_under; /* should bits under be saved? (popups) */ 16507 arch_long event_mask; /* set of events that should be saved */ 16508 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 16509 Bool override_redirect; /* boolean value for override_redirect */ 16510 Colormap colormap; /* color map to be associated with window */ 16511 Cursor cursor; /* cursor to be displayed (or None) */ 16512 } 16513 16514 16515 alias int Status; 16516 16517 16518 enum EventMask:int 16519 { 16520 NoEventMask =0, 16521 KeyPressMask =1<<0, 16522 KeyReleaseMask =1<<1, 16523 ButtonPressMask =1<<2, 16524 ButtonReleaseMask =1<<3, 16525 EnterWindowMask =1<<4, 16526 LeaveWindowMask =1<<5, 16527 PointerMotionMask =1<<6, 16528 PointerMotionHintMask =1<<7, 16529 Button1MotionMask =1<<8, 16530 Button2MotionMask =1<<9, 16531 Button3MotionMask =1<<10, 16532 Button4MotionMask =1<<11, 16533 Button5MotionMask =1<<12, 16534 ButtonMotionMask =1<<13, 16535 KeymapStateMask =1<<14, 16536 ExposureMask =1<<15, 16537 VisibilityChangeMask =1<<16, 16538 StructureNotifyMask =1<<17, 16539 ResizeRedirectMask =1<<18, 16540 SubstructureNotifyMask =1<<19, 16541 SubstructureRedirectMask=1<<20, 16542 FocusChangeMask =1<<21, 16543 PropertyChangeMask =1<<22, 16544 ColormapChangeMask =1<<23, 16545 OwnerGrabButtonMask =1<<24 16546 } 16547 16548 struct MwmHints { 16549 c_ulong flags; 16550 c_ulong functions; 16551 c_ulong decorations; 16552 c_long input_mode; 16553 c_ulong status; 16554 } 16555 16556 enum { 16557 MWM_HINTS_FUNCTIONS = (1L << 0), 16558 MWM_HINTS_DECORATIONS = (1L << 1), 16559 16560 MWM_FUNC_ALL = (1L << 0), 16561 MWM_FUNC_RESIZE = (1L << 1), 16562 MWM_FUNC_MOVE = (1L << 2), 16563 MWM_FUNC_MINIMIZE = (1L << 3), 16564 MWM_FUNC_MAXIMIZE = (1L << 4), 16565 MWM_FUNC_CLOSE = (1L << 5), 16566 16567 MWM_DECOR_ALL = (1L << 0), 16568 MWM_DECOR_BORDER = (1L << 1), 16569 MWM_DECOR_RESIZEH = (1L << 2), 16570 MWM_DECOR_TITLE = (1L << 3), 16571 MWM_DECOR_MENU = (1L << 4), 16572 MWM_DECOR_MINIMIZE = (1L << 5), 16573 MWM_DECOR_MAXIMIZE = (1L << 6), 16574 } 16575 16576 import core.stdc.config : c_long, c_ulong; 16577 16578 /* Size hints mask bits */ 16579 16580 enum USPosition = (1L << 0) /* user specified x, y */; 16581 enum USSize = (1L << 1) /* user specified width, height */; 16582 enum PPosition = (1L << 2) /* program specified position */; 16583 enum PSize = (1L << 3) /* program specified size */; 16584 enum PMinSize = (1L << 4) /* program specified minimum size */; 16585 enum PMaxSize = (1L << 5) /* program specified maximum size */; 16586 enum PResizeInc = (1L << 6) /* program specified resize increments */; 16587 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 16588 enum PBaseSize = (1L << 8); 16589 enum PWinGravity = (1L << 9); 16590 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 16591 struct XSizeHints { 16592 arch_long flags; /* marks which fields in this structure are defined */ 16593 int x, y; /* Obsolete */ 16594 int width, height; /* Obsolete */ 16595 int min_width, min_height; 16596 int max_width, max_height; 16597 int width_inc, height_inc; 16598 struct Aspect { 16599 int x; /* numerator */ 16600 int y; /* denominator */ 16601 } 16602 16603 Aspect min_aspect; 16604 Aspect max_aspect; 16605 int base_width, base_height; 16606 int win_gravity; 16607 /* this structure may be extended in the future */ 16608 } 16609 16610 16611 16612 enum EventType:int 16613 { 16614 KeyPress =2, 16615 KeyRelease =3, 16616 ButtonPress =4, 16617 ButtonRelease =5, 16618 MotionNotify =6, 16619 EnterNotify =7, 16620 LeaveNotify =8, 16621 FocusIn =9, 16622 FocusOut =10, 16623 KeymapNotify =11, 16624 Expose =12, 16625 GraphicsExpose =13, 16626 NoExpose =14, 16627 VisibilityNotify =15, 16628 CreateNotify =16, 16629 DestroyNotify =17, 16630 UnmapNotify =18, 16631 MapNotify =19, 16632 MapRequest =20, 16633 ReparentNotify =21, 16634 ConfigureNotify =22, 16635 ConfigureRequest =23, 16636 GravityNotify =24, 16637 ResizeRequest =25, 16638 CirculateNotify =26, 16639 CirculateRequest =27, 16640 PropertyNotify =28, 16641 SelectionClear =29, 16642 SelectionRequest =30, 16643 SelectionNotify =31, 16644 ColormapNotify =32, 16645 ClientMessage =33, 16646 MappingNotify =34, 16647 LASTEvent =35 /* must be bigger than any event # */ 16648 } 16649 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 16650 struct XKeymapEvent 16651 { 16652 int type; 16653 arch_ulong serial; /* # of last request processed by server */ 16654 Bool send_event; /* true if this came from a SendEvent request */ 16655 Display *display; /* Display the event was read from */ 16656 Window window; 16657 byte[32] key_vector; 16658 } 16659 16660 struct XExposeEvent 16661 { 16662 int type; 16663 arch_ulong serial; /* # of last request processed by server */ 16664 Bool send_event; /* true if this came from a SendEvent request */ 16665 Display *display; /* Display the event was read from */ 16666 Window window; 16667 int x, y; 16668 int width, height; 16669 int count; /* if non-zero, at least this many more */ 16670 } 16671 16672 struct XGraphicsExposeEvent{ 16673 int type; 16674 arch_ulong serial; /* # of last request processed by server */ 16675 Bool send_event; /* true if this came from a SendEvent request */ 16676 Display *display; /* Display the event was read from */ 16677 Drawable drawable; 16678 int x, y; 16679 int width, height; 16680 int count; /* if non-zero, at least this many more */ 16681 int major_code; /* core is CopyArea or CopyPlane */ 16682 int minor_code; /* not defined in the core */ 16683 } 16684 16685 struct XNoExposeEvent{ 16686 int type; 16687 arch_ulong serial; /* # of last request processed by server */ 16688 Bool send_event; /* true if this came from a SendEvent request */ 16689 Display *display; /* Display the event was read from */ 16690 Drawable drawable; 16691 int major_code; /* core is CopyArea or CopyPlane */ 16692 int minor_code; /* not defined in the core */ 16693 } 16694 16695 struct XVisibilityEvent{ 16696 int type; 16697 arch_ulong serial; /* # of last request processed by server */ 16698 Bool send_event; /* true if this came from a SendEvent request */ 16699 Display *display; /* Display the event was read from */ 16700 Window window; 16701 VisibilityNotify state; /* Visibility state */ 16702 } 16703 16704 struct XCreateWindowEvent{ 16705 int type; 16706 arch_ulong serial; /* # of last request processed by server */ 16707 Bool send_event; /* true if this came from a SendEvent request */ 16708 Display *display; /* Display the event was read from */ 16709 Window parent; /* parent of the window */ 16710 Window window; /* window id of window created */ 16711 int x, y; /* window location */ 16712 int width, height; /* size of window */ 16713 int border_width; /* border width */ 16714 Bool override_redirect; /* creation should be overridden */ 16715 } 16716 16717 struct XDestroyWindowEvent 16718 { 16719 int type; 16720 arch_ulong serial; /* # of last request processed by server */ 16721 Bool send_event; /* true if this came from a SendEvent request */ 16722 Display *display; /* Display the event was read from */ 16723 Window event; 16724 Window window; 16725 } 16726 16727 struct XUnmapEvent 16728 { 16729 int type; 16730 arch_ulong serial; /* # of last request processed by server */ 16731 Bool send_event; /* true if this came from a SendEvent request */ 16732 Display *display; /* Display the event was read from */ 16733 Window event; 16734 Window window; 16735 Bool from_configure; 16736 } 16737 16738 struct XMapEvent 16739 { 16740 int type; 16741 arch_ulong serial; /* # of last request processed by server */ 16742 Bool send_event; /* true if this came from a SendEvent request */ 16743 Display *display; /* Display the event was read from */ 16744 Window event; 16745 Window window; 16746 Bool override_redirect; /* Boolean, is override set... */ 16747 } 16748 16749 struct XMapRequestEvent 16750 { 16751 int type; 16752 arch_ulong serial; /* # of last request processed by server */ 16753 Bool send_event; /* true if this came from a SendEvent request */ 16754 Display *display; /* Display the event was read from */ 16755 Window parent; 16756 Window window; 16757 } 16758 16759 struct XReparentEvent 16760 { 16761 int type; 16762 arch_ulong serial; /* # of last request processed by server */ 16763 Bool send_event; /* true if this came from a SendEvent request */ 16764 Display *display; /* Display the event was read from */ 16765 Window event; 16766 Window window; 16767 Window parent; 16768 int x, y; 16769 Bool override_redirect; 16770 } 16771 16772 struct XConfigureEvent 16773 { 16774 int type; 16775 arch_ulong serial; /* # of last request processed by server */ 16776 Bool send_event; /* true if this came from a SendEvent request */ 16777 Display *display; /* Display the event was read from */ 16778 Window event; 16779 Window window; 16780 int x, y; 16781 int width, height; 16782 int border_width; 16783 Window above; 16784 Bool override_redirect; 16785 } 16786 16787 struct XGravityEvent 16788 { 16789 int type; 16790 arch_ulong serial; /* # of last request processed by server */ 16791 Bool send_event; /* true if this came from a SendEvent request */ 16792 Display *display; /* Display the event was read from */ 16793 Window event; 16794 Window window; 16795 int x, y; 16796 } 16797 16798 struct XResizeRequestEvent 16799 { 16800 int type; 16801 arch_ulong serial; /* # of last request processed by server */ 16802 Bool send_event; /* true if this came from a SendEvent request */ 16803 Display *display; /* Display the event was read from */ 16804 Window window; 16805 int width, height; 16806 } 16807 16808 struct XConfigureRequestEvent 16809 { 16810 int type; 16811 arch_ulong serial; /* # of last request processed by server */ 16812 Bool send_event; /* true if this came from a SendEvent request */ 16813 Display *display; /* Display the event was read from */ 16814 Window parent; 16815 Window window; 16816 int x, y; 16817 int width, height; 16818 int border_width; 16819 Window above; 16820 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 16821 arch_ulong value_mask; 16822 } 16823 16824 struct XCirculateEvent 16825 { 16826 int type; 16827 arch_ulong serial; /* # of last request processed by server */ 16828 Bool send_event; /* true if this came from a SendEvent request */ 16829 Display *display; /* Display the event was read from */ 16830 Window event; 16831 Window window; 16832 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 16833 } 16834 16835 struct XCirculateRequestEvent 16836 { 16837 int type; 16838 arch_ulong serial; /* # of last request processed by server */ 16839 Bool send_event; /* true if this came from a SendEvent request */ 16840 Display *display; /* Display the event was read from */ 16841 Window parent; 16842 Window window; 16843 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 16844 } 16845 16846 struct XPropertyEvent 16847 { 16848 int type; 16849 arch_ulong serial; /* # of last request processed by server */ 16850 Bool send_event; /* true if this came from a SendEvent request */ 16851 Display *display; /* Display the event was read from */ 16852 Window window; 16853 Atom atom; 16854 Time time; 16855 PropertyNotification state; /* NewValue, Deleted */ 16856 } 16857 16858 struct XSelectionClearEvent 16859 { 16860 int type; 16861 arch_ulong serial; /* # of last request processed by server */ 16862 Bool send_event; /* true if this came from a SendEvent request */ 16863 Display *display; /* Display the event was read from */ 16864 Window window; 16865 Atom selection; 16866 Time time; 16867 } 16868 16869 struct XSelectionRequestEvent 16870 { 16871 int type; 16872 arch_ulong serial; /* # of last request processed by server */ 16873 Bool send_event; /* true if this came from a SendEvent request */ 16874 Display *display; /* Display the event was read from */ 16875 Window owner; 16876 Window requestor; 16877 Atom selection; 16878 Atom target; 16879 Atom property; 16880 Time time; 16881 } 16882 16883 struct XSelectionEvent 16884 { 16885 int type; 16886 arch_ulong serial; /* # of last request processed by server */ 16887 Bool send_event; /* true if this came from a SendEvent request */ 16888 Display *display; /* Display the event was read from */ 16889 Window requestor; 16890 Atom selection; 16891 Atom target; 16892 Atom property; /* ATOM or None */ 16893 Time time; 16894 } 16895 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 16896 16897 struct XColormapEvent 16898 { 16899 int type; 16900 arch_ulong serial; /* # of last request processed by server */ 16901 Bool send_event; /* true if this came from a SendEvent request */ 16902 Display *display; /* Display the event was read from */ 16903 Window window; 16904 Colormap colormap; /* COLORMAP or None */ 16905 Bool new_; /* C++ */ 16906 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 16907 } 16908 version(X86_64) static assert(XColormapEvent.sizeof == 56); 16909 16910 struct XClientMessageEvent 16911 { 16912 int type; 16913 arch_ulong serial; /* # of last request processed by server */ 16914 Bool send_event; /* true if this came from a SendEvent request */ 16915 Display *display; /* Display the event was read from */ 16916 Window window; 16917 Atom message_type; 16918 int format; 16919 union Data{ 16920 byte[20] b; 16921 short[10] s; 16922 arch_ulong[5] l; 16923 } 16924 Data data; 16925 16926 } 16927 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 16928 16929 struct XMappingEvent 16930 { 16931 int type; 16932 arch_ulong serial; /* # of last request processed by server */ 16933 Bool send_event; /* true if this came from a SendEvent request */ 16934 Display *display; /* Display the event was read from */ 16935 Window window; /* unused */ 16936 MappingType request; /* one of MappingModifier, MappingKeyboard, 16937 MappingPointer */ 16938 int first_keycode; /* first keycode */ 16939 int count; /* defines range of change w. first_keycode*/ 16940 } 16941 16942 struct XErrorEvent 16943 { 16944 int type; 16945 Display *display; /* Display the event was read from */ 16946 XID resourceid; /* resource id */ 16947 arch_ulong serial; /* serial number of failed request */ 16948 ubyte error_code; /* error code of failed request */ 16949 ubyte request_code; /* Major op-code of failed request */ 16950 ubyte minor_code; /* Minor op-code of failed request */ 16951 } 16952 16953 struct XAnyEvent 16954 { 16955 int type; 16956 arch_ulong serial; /* # of last request processed by server */ 16957 Bool send_event; /* true if this came from a SendEvent request */ 16958 Display *display;/* Display the event was read from */ 16959 Window window; /* window on which event was requested in event mask */ 16960 } 16961 16962 union XEvent{ 16963 int type; /* must not be changed; first element */ 16964 XAnyEvent xany; 16965 XKeyEvent xkey; 16966 XButtonEvent xbutton; 16967 XMotionEvent xmotion; 16968 XCrossingEvent xcrossing; 16969 XFocusChangeEvent xfocus; 16970 XExposeEvent xexpose; 16971 XGraphicsExposeEvent xgraphicsexpose; 16972 XNoExposeEvent xnoexpose; 16973 XVisibilityEvent xvisibility; 16974 XCreateWindowEvent xcreatewindow; 16975 XDestroyWindowEvent xdestroywindow; 16976 XUnmapEvent xunmap; 16977 XMapEvent xmap; 16978 XMapRequestEvent xmaprequest; 16979 XReparentEvent xreparent; 16980 XConfigureEvent xconfigure; 16981 XGravityEvent xgravity; 16982 XResizeRequestEvent xresizerequest; 16983 XConfigureRequestEvent xconfigurerequest; 16984 XCirculateEvent xcirculate; 16985 XCirculateRequestEvent xcirculaterequest; 16986 XPropertyEvent xproperty; 16987 XSelectionClearEvent xselectionclear; 16988 XSelectionRequestEvent xselectionrequest; 16989 XSelectionEvent xselection; 16990 XColormapEvent xcolormap; 16991 XClientMessageEvent xclient; 16992 XMappingEvent xmapping; 16993 XErrorEvent xerror; 16994 XKeymapEvent xkeymap; 16995 arch_ulong[24] pad; 16996 } 16997 16998 16999 struct Display { 17000 XExtData *ext_data; /* hook for extension to hang data */ 17001 _XPrivate *private1; 17002 int fd; /* Network socket. */ 17003 int private2; 17004 int proto_major_version;/* major version of server's X protocol */ 17005 int proto_minor_version;/* minor version of servers X protocol */ 17006 char *vendor; /* vendor of the server hardware */ 17007 XID private3; 17008 XID private4; 17009 XID private5; 17010 int private6; 17011 XID function(Display*)resource_alloc;/* allocator function */ 17012 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17013 int bitmap_unit; /* padding and data requirements */ 17014 int bitmap_pad; /* padding requirements on bitmaps */ 17015 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17016 int nformats; /* number of pixmap formats in list */ 17017 ScreenFormat *pixmap_format; /* pixmap format list */ 17018 int private8; 17019 int release; /* release of the server */ 17020 _XPrivate *private9; 17021 _XPrivate *private10; 17022 int qlen; /* Length of input event queue */ 17023 arch_ulong last_request_read; /* seq number of last event read */ 17024 arch_ulong request; /* sequence number of last request. */ 17025 XPointer private11; 17026 XPointer private12; 17027 XPointer private13; 17028 XPointer private14; 17029 uint max_request_size; /* maximum number 32 bit words in request*/ 17030 _XrmHashBucketRec *db; 17031 int function (Display*)private15; 17032 char *display_name; /* "host:display" string used on this connect*/ 17033 int default_screen; /* default screen for operations */ 17034 int nscreens; /* number of screens on this server*/ 17035 Screen *screens; /* pointer to list of screens */ 17036 arch_ulong motion_buffer; /* size of motion buffer */ 17037 arch_ulong private16; 17038 int min_keycode; /* minimum defined keycode */ 17039 int max_keycode; /* maximum defined keycode */ 17040 XPointer private17; 17041 XPointer private18; 17042 int private19; 17043 byte *xdefaults; /* contents of defaults from server */ 17044 /* there is more to this structure, but it is private to Xlib */ 17045 } 17046 17047 // I got these numbers from a C program as a sanity test 17048 version(X86_64) { 17049 static assert(Display.sizeof == 296); 17050 static assert(XPointer.sizeof == 8); 17051 static assert(XErrorEvent.sizeof == 40); 17052 static assert(XAnyEvent.sizeof == 40); 17053 static assert(XMappingEvent.sizeof == 56); 17054 static assert(XEvent.sizeof == 192); 17055 } else version (AArch64) { 17056 // omit check for aarch64 17057 } else { 17058 static assert(Display.sizeof == 176); 17059 static assert(XPointer.sizeof == 4); 17060 static assert(XEvent.sizeof == 96); 17061 } 17062 17063 struct Depth 17064 { 17065 int depth; /* this depth (Z) of the depth */ 17066 int nvisuals; /* number of Visual types at this depth */ 17067 Visual *visuals; /* list of visuals possible at this depth */ 17068 } 17069 17070 alias void* GC; 17071 alias c_ulong VisualID; 17072 alias XID Colormap; 17073 alias XID Cursor; 17074 alias XID KeySym; 17075 alias uint KeyCode; 17076 enum None = 0; 17077 } 17078 17079 version(without_opengl) {} 17080 else { 17081 extern(C) nothrow @nogc { 17082 17083 17084 static if(!SdpyIsUsingIVGLBinds) { 17085 enum GLX_USE_GL= 1; /* support GLX rendering */ 17086 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17087 enum GLX_LEVEL= 3; /* level in plane stacking */ 17088 enum GLX_RGBA= 4; /* true if RGBA mode */ 17089 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17090 enum GLX_STEREO= 6; /* stereo buffering supported */ 17091 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 17092 enum GLX_RED_SIZE= 8; /* number of red component bits */ 17093 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 17094 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 17095 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 17096 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 17097 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 17098 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 17099 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 17100 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 17101 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 17102 17103 17104 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 17105 17106 17107 17108 enum GL_TRUE = 1; 17109 enum GL_FALSE = 0; 17110 alias int GLint; 17111 } 17112 17113 alias XID GLXContextID; 17114 alias XID GLXPixmap; 17115 alias XID GLXDrawable; 17116 alias XID GLXPbuffer; 17117 alias XID GLXWindow; 17118 alias XID GLXFBConfigID; 17119 alias void* GLXContext; 17120 17121 } 17122 } 17123 17124 enum AllocNone = 0; 17125 17126 extern(C) { 17127 /* WARNING, this type not in Xlib spec */ 17128 extern(C) alias XIOErrorHandler = int function (Display* display); 17129 } 17130 17131 extern(C) nothrow 17132 alias XErrorHandler = int function(Display*, XErrorEvent*); 17133 17134 extern(C) nothrow @nogc { 17135 struct Screen{ 17136 XExtData *ext_data; /* hook for extension to hang data */ 17137 Display *display; /* back pointer to display structure */ 17138 Window root; /* Root window id. */ 17139 int width, height; /* width and height of screen */ 17140 int mwidth, mheight; /* width and height of in millimeters */ 17141 int ndepths; /* number of depths possible */ 17142 Depth *depths; /* list of allowable depths on the screen */ 17143 int root_depth; /* bits per pixel */ 17144 Visual *root_visual; /* root visual */ 17145 GC default_gc; /* GC for the root root visual */ 17146 Colormap cmap; /* default color map */ 17147 uint white_pixel; 17148 uint black_pixel; /* White and Black pixel values */ 17149 int max_maps, min_maps; /* max and min color maps */ 17150 int backing_store; /* Never, WhenMapped, Always */ 17151 bool save_unders; 17152 int root_input_mask; /* initial root input mask */ 17153 } 17154 17155 struct Visual 17156 { 17157 XExtData *ext_data; /* hook for extension to hang data */ 17158 VisualID visualid; /* visual id of this visual */ 17159 int class_; /* class of screen (monochrome, etc.) */ 17160 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 17161 int bits_per_rgb; /* log base 2 of distinct color values */ 17162 int map_entries; /* color map entries */ 17163 } 17164 17165 alias Display* _XPrivDisplay; 17166 17167 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) { 17168 assert(dpy !is null); 17169 return &dpy.screens[scr]; 17170 } 17171 17172 extern(D) Window RootWindow(Display *dpy,int scr) { 17173 return ScreenOfDisplay(dpy,scr).root; 17174 } 17175 17176 struct XWMHints { 17177 arch_long flags; 17178 Bool input; 17179 int initial_state; 17180 Pixmap icon_pixmap; 17181 Window icon_window; 17182 int icon_x, icon_y; 17183 Pixmap icon_mask; 17184 XID window_group; 17185 } 17186 17187 struct XClassHint { 17188 char* res_name; 17189 char* res_class; 17190 } 17191 17192 extern(D) int DefaultScreen(Display *dpy) { 17193 return dpy.default_screen; 17194 } 17195 17196 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 17197 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 17198 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 17199 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 17200 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 17201 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 17202 17203 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 17204 17205 enum int AnyPropertyType = 0; 17206 enum int Success = 0; 17207 17208 enum int RevertToNone = None; 17209 enum int PointerRoot = 1; 17210 enum Time CurrentTime = 0; 17211 enum int RevertToPointerRoot = PointerRoot; 17212 enum int RevertToParent = 2; 17213 17214 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 17215 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 17216 } 17217 17218 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 17219 return ScreenOfDisplay(dpy,scr).root_visual; 17220 } 17221 17222 extern(D) GC DefaultGC(Display *dpy,int scr) { 17223 return ScreenOfDisplay(dpy,scr).default_gc; 17224 } 17225 17226 extern(D) uint BlackPixel(Display *dpy,int scr) { 17227 return ScreenOfDisplay(dpy,scr).black_pixel; 17228 } 17229 17230 extern(D) uint WhitePixel(Display *dpy,int scr) { 17231 return ScreenOfDisplay(dpy,scr).white_pixel; 17232 } 17233 17234 alias void* XFontSet; // i think 17235 struct XmbTextItem { 17236 char* chars; 17237 int nchars; 17238 int delta; 17239 XFontSet font_set; 17240 } 17241 17242 struct XTextItem { 17243 char* chars; 17244 int nchars; 17245 int delta; 17246 Font font; 17247 } 17248 17249 enum { 17250 GXclear = 0x0, /* 0 */ 17251 GXand = 0x1, /* src AND dst */ 17252 GXandReverse = 0x2, /* src AND NOT dst */ 17253 GXcopy = 0x3, /* src */ 17254 GXandInverted = 0x4, /* NOT src AND dst */ 17255 GXnoop = 0x5, /* dst */ 17256 GXxor = 0x6, /* src XOR dst */ 17257 GXor = 0x7, /* src OR dst */ 17258 GXnor = 0x8, /* NOT src AND NOT dst */ 17259 GXequiv = 0x9, /* NOT src XOR dst */ 17260 GXinvert = 0xa, /* NOT dst */ 17261 GXorReverse = 0xb, /* src OR NOT dst */ 17262 GXcopyInverted = 0xc, /* NOT src */ 17263 GXorInverted = 0xd, /* NOT src OR dst */ 17264 GXnand = 0xe, /* NOT src OR NOT dst */ 17265 GXset = 0xf, /* 1 */ 17266 } 17267 enum QueueMode : int { 17268 QueuedAlready, 17269 QueuedAfterReading, 17270 QueuedAfterFlush 17271 } 17272 17273 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 17274 17275 struct XPoint { 17276 short x; 17277 short y; 17278 } 17279 17280 enum CoordMode:int { 17281 CoordModeOrigin = 0, 17282 CoordModePrevious = 1 17283 } 17284 17285 enum PolygonShape:int { 17286 Complex = 0, 17287 Nonconvex = 1, 17288 Convex = 2 17289 } 17290 17291 struct XTextProperty { 17292 const(char)* value; /* same as Property routines */ 17293 Atom encoding; /* prop type */ 17294 int format; /* prop data format: 8, 16, or 32 */ 17295 arch_ulong nitems; /* number of data items in value */ 17296 } 17297 17298 version( X86_64 ) { 17299 static assert(XTextProperty.sizeof == 32); 17300 } 17301 17302 17303 struct XGCValues { 17304 int function_; /* logical operation */ 17305 arch_ulong plane_mask;/* plane mask */ 17306 arch_ulong foreground;/* foreground pixel */ 17307 arch_ulong background;/* background pixel */ 17308 int line_width; /* line width */ 17309 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 17310 int cap_style; /* CapNotLast, CapButt, 17311 CapRound, CapProjecting */ 17312 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 17313 int fill_style; /* FillSolid, FillTiled, 17314 FillStippled, FillOpaeueStippled */ 17315 int fill_rule; /* EvenOddRule, WindingRule */ 17316 int arc_mode; /* ArcChord, ArcPieSlice */ 17317 Pixmap tile; /* tile pixmap for tiling operations */ 17318 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 17319 int ts_x_origin; /* offset for tile or stipple operations */ 17320 int ts_y_origin; 17321 Font font; /* default text font for text operations */ 17322 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 17323 Bool graphics_exposures;/* boolean, should exposures be generated */ 17324 int clip_x_origin; /* origin for clipping */ 17325 int clip_y_origin; 17326 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 17327 int dash_offset; /* patterned/dashed line information */ 17328 char dashes; 17329 } 17330 17331 struct XColor { 17332 arch_ulong pixel; 17333 ushort red, green, blue; 17334 byte flags; 17335 byte pad; 17336 } 17337 17338 struct XRectangle { 17339 short x; 17340 short y; 17341 ushort width; 17342 ushort height; 17343 } 17344 17345 enum ClipByChildren = 0; 17346 enum IncludeInferiors = 1; 17347 17348 enum Atom XA_PRIMARY = 1; 17349 enum Atom XA_SECONDARY = 2; 17350 enum Atom XA_STRING = 31; 17351 enum Atom XA_CARDINAL = 6; 17352 enum Atom XA_WM_NAME = 39; 17353 enum Atom XA_ATOM = 4; 17354 enum Atom XA_WINDOW = 33; 17355 enum Atom XA_WM_HINTS = 35; 17356 enum int PropModeAppend = 2; 17357 enum int PropModeReplace = 0; 17358 enum int PropModePrepend = 1; 17359 17360 enum int CopyFromParent = 0; 17361 enum int InputOutput = 1; 17362 17363 // XWMHints 17364 enum InputHint = 1 << 0; 17365 enum StateHint = 1 << 1; 17366 enum IconPixmapHint = (1L << 2); 17367 enum IconWindowHint = (1L << 3); 17368 enum IconPositionHint = (1L << 4); 17369 enum IconMaskHint = (1L << 5); 17370 enum WindowGroupHint = (1L << 6); 17371 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 17372 enum XUrgencyHint = (1L << 8); 17373 17374 // GC Components 17375 enum GCFunction = (1L<<0); 17376 enum GCPlaneMask = (1L<<1); 17377 enum GCForeground = (1L<<2); 17378 enum GCBackground = (1L<<3); 17379 enum GCLineWidth = (1L<<4); 17380 enum GCLineStyle = (1L<<5); 17381 enum GCCapStyle = (1L<<6); 17382 enum GCJoinStyle = (1L<<7); 17383 enum GCFillStyle = (1L<<8); 17384 enum GCFillRule = (1L<<9); 17385 enum GCTile = (1L<<10); 17386 enum GCStipple = (1L<<11); 17387 enum GCTileStipXOrigin = (1L<<12); 17388 enum GCTileStipYOrigin = (1L<<13); 17389 enum GCFont = (1L<<14); 17390 enum GCSubwindowMode = (1L<<15); 17391 enum GCGraphicsExposures= (1L<<16); 17392 enum GCClipXOrigin = (1L<<17); 17393 enum GCClipYOrigin = (1L<<18); 17394 enum GCClipMask = (1L<<19); 17395 enum GCDashOffset = (1L<<20); 17396 enum GCDashList = (1L<<21); 17397 enum GCArcMode = (1L<<22); 17398 enum GCLastBit = 22; 17399 17400 17401 enum int WithdrawnState = 0; 17402 enum int NormalState = 1; 17403 enum int IconicState = 3; 17404 17405 } 17406 } else version (OSXCocoa) { 17407 private: 17408 alias void* id; 17409 alias void* Class; 17410 alias void* SEL; 17411 alias void* IMP; 17412 alias void* Ivar; 17413 alias byte BOOL; 17414 alias const(void)* CFStringRef; 17415 alias const(void)* CFAllocatorRef; 17416 alias const(void)* CFTypeRef; 17417 alias const(void)* CGContextRef; 17418 alias const(void)* CGColorSpaceRef; 17419 alias const(void)* CGImageRef; 17420 alias ulong CGBitmapInfo; 17421 17422 struct objc_super { 17423 id self; 17424 Class superclass; 17425 } 17426 17427 struct CFRange { 17428 long location, length; 17429 } 17430 17431 struct NSPoint { 17432 double x, y; 17433 17434 static fromTuple(T)(T tupl) { 17435 return NSPoint(tupl.tupleof); 17436 } 17437 } 17438 struct NSSize { 17439 double width, height; 17440 } 17441 struct NSRect { 17442 NSPoint origin; 17443 NSSize size; 17444 } 17445 alias NSPoint CGPoint; 17446 alias NSSize CGSize; 17447 alias NSRect CGRect; 17448 17449 struct CGAffineTransform { 17450 double a, b, c, d, tx, ty; 17451 } 17452 17453 enum NSApplicationActivationPolicyRegular = 0; 17454 enum NSBackingStoreBuffered = 2; 17455 enum kCFStringEncodingUTF8 = 0x08000100; 17456 17457 enum : size_t { 17458 NSBorderlessWindowMask = 0, 17459 NSTitledWindowMask = 1 << 0, 17460 NSClosableWindowMask = 1 << 1, 17461 NSMiniaturizableWindowMask = 1 << 2, 17462 NSResizableWindowMask = 1 << 3, 17463 NSTexturedBackgroundWindowMask = 1 << 8 17464 } 17465 17466 enum : ulong { 17467 kCGImageAlphaNone, 17468 kCGImageAlphaPremultipliedLast, 17469 kCGImageAlphaPremultipliedFirst, 17470 kCGImageAlphaLast, 17471 kCGImageAlphaFirst, 17472 kCGImageAlphaNoneSkipLast, 17473 kCGImageAlphaNoneSkipFirst 17474 } 17475 enum : ulong { 17476 kCGBitmapAlphaInfoMask = 0x1F, 17477 kCGBitmapFloatComponents = (1 << 8), 17478 kCGBitmapByteOrderMask = 0x7000, 17479 kCGBitmapByteOrderDefault = (0 << 12), 17480 kCGBitmapByteOrder16Little = (1 << 12), 17481 kCGBitmapByteOrder32Little = (2 << 12), 17482 kCGBitmapByteOrder16Big = (3 << 12), 17483 kCGBitmapByteOrder32Big = (4 << 12) 17484 } 17485 enum CGPathDrawingMode { 17486 kCGPathFill, 17487 kCGPathEOFill, 17488 kCGPathStroke, 17489 kCGPathFillStroke, 17490 kCGPathEOFillStroke 17491 } 17492 enum objc_AssociationPolicy : size_t { 17493 OBJC_ASSOCIATION_ASSIGN = 0, 17494 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 17495 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 17496 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 17497 OBJC_ASSOCIATION_COPY = 0x303 //01403 17498 } 17499 17500 extern(C) { 17501 id objc_msgSend(id receiver, SEL selector, ...); 17502 id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...); 17503 id objc_getClass(const(char)* name); 17504 SEL sel_registerName(const(char)* str); 17505 Class objc_allocateClassPair(Class superclass, const(char)* name, 17506 size_t extra_bytes); 17507 void objc_registerClassPair(Class cls); 17508 BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types); 17509 id objc_getAssociatedObject(id object, void* key); 17510 void objc_setAssociatedObject(id object, void* key, id value, 17511 objc_AssociationPolicy policy); 17512 Ivar class_getInstanceVariable(Class cls, const(char)* name); 17513 id object_getIvar(id object, Ivar ivar); 17514 void object_setIvar(id object, Ivar ivar, id value); 17515 BOOL class_addIvar(Class cls, const(char)* name, 17516 size_t size, ubyte alignment, const(char)* types); 17517 17518 extern __gshared id NSApp; 17519 17520 void CFRelease(CFTypeRef obj); 17521 17522 CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator, 17523 const(char)* bytes, long numBytes, 17524 long encoding, 17525 BOOL isExternalRepresentation); 17526 long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding, 17527 char lossByte, bool isExternalRepresentation, 17528 char* buffer, long maxBufLen, long* usedBufLen); 17529 long CFStringGetLength(CFStringRef theString); 17530 17531 CGContextRef CGBitmapContextCreate(void* data, 17532 size_t width, size_t height, 17533 size_t bitsPerComponent, 17534 size_t bytesPerRow, 17535 CGColorSpaceRef colorspace, 17536 CGBitmapInfo bitmapInfo); 17537 void CGContextRelease(CGContextRef c); 17538 ubyte* CGBitmapContextGetData(CGContextRef c); 17539 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 17540 size_t CGBitmapContextGetWidth(CGContextRef c); 17541 size_t CGBitmapContextGetHeight(CGContextRef c); 17542 17543 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 17544 void CGColorSpaceRelease(CGColorSpaceRef cs); 17545 17546 void CGContextSetRGBStrokeColor(CGContextRef c, 17547 double red, double green, double blue, 17548 double alpha); 17549 void CGContextSetRGBFillColor(CGContextRef c, 17550 double red, double green, double blue, 17551 double alpha); 17552 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 17553 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, 17554 const(char)* str, size_t length); 17555 void CGContextStrokeLineSegments(CGContextRef c, 17556 const(CGPoint)* points, size_t count); 17557 17558 void CGContextBeginPath(CGContextRef c); 17559 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 17560 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 17561 void CGContextAddArc(CGContextRef c, double x, double y, double radius, 17562 double startAngle, double endAngle, long clockwise); 17563 void CGContextAddRect(CGContextRef c, CGRect rect); 17564 void CGContextAddLines(CGContextRef c, 17565 const(CGPoint)* points, size_t count); 17566 void CGContextSaveGState(CGContextRef c); 17567 void CGContextRestoreGState(CGContextRef c); 17568 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, 17569 ulong textEncoding); 17570 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 17571 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 17572 17573 void CGImageRelease(CGImageRef image); 17574 } 17575 17576 private: 17577 // A convenient method to create a CFString (=NSString) from a D string. 17578 CFStringRef createCFString(string str) { 17579 return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length, 17580 kCFStringEncodingUTF8, false); 17581 } 17582 17583 // Objective-C calls. 17584 RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) { 17585 auto _cmd = sel_registerName(selector.ptr); 17586 alias extern(C) RetType function(id, SEL, T) ExpectedType; 17587 return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args); 17588 } 17589 RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) { 17590 auto _cmd = sel_registerName(selector.ptr); 17591 auto cls = objc_getClass(className); 17592 alias extern(C) RetType function(id, SEL, T) ExpectedType; 17593 return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args); 17594 } 17595 RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) { 17596 return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args); 17597 } 17598 17599 alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay; 17600 alias objc_msgSend_classMethod!("alloc", id) alloc; 17601 alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:", 17602 id, NSRect, size_t, size_t, BOOL) initWithContentRect; 17603 alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle; 17604 alias objc_msgSend_specialized!("center", void) center; 17605 alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame; 17606 alias objc_msgSend_specialized!("setContentView:", void, id) setContentView; 17607 alias objc_msgSend_specialized!("release", void) release; 17608 alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor; 17609 alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor; 17610 alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront; 17611 alias objc_msgSend_specialized!("invalidate", void) invalidate; 17612 alias objc_msgSend_specialized!("close", void) close; 17613 alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:", 17614 id, double, id, SEL, id, BOOL) scheduledTimer; 17615 alias objc_msgSend_specialized!("run", void) run; 17616 alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext", 17617 id) currentNSGraphicsContext; 17618 alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort; 17619 alias objc_msgSend_specialized!("characters", CFStringRef) characters; 17620 alias objc_msgSend_specialized!("superclass", Class) superclass; 17621 alias objc_msgSend_specialized!("init", id) init; 17622 alias objc_msgSend_specialized!("addItem:", void, id) addItem; 17623 alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu; 17624 alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:", 17625 id, CFStringRef, SEL, CFStringRef) initWithTitle; 17626 alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu; 17627 alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate; 17628 alias objc_msgSend_specialized!("activateIgnoringOtherApps:", 17629 void, BOOL) activateIgnoringOtherApps; 17630 alias objc_msgSend_classMethod!("NSApplication", "sharedApplication", 17631 id) sharedNSApplication; 17632 alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy; 17633 } else static assert(0, "Unsupported operating system"); 17634 17635 17636 version(OSXCocoa) { 17637 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 17638 // 17639 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 17640 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 17641 // 17642 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 17643 // Probably won't even fully compile right now 17644 17645 import std.math : PI; 17646 import std.algorithm : map; 17647 import std.array : array; 17648 17649 alias SimpleWindow NativeWindowHandle; 17650 alias void delegate(id) NativeEventHandler; 17651 17652 __gshared Ivar simpleWindowIvar; 17653 17654 enum KEY_ESCAPE = 27; 17655 17656 mixin template NativeImageImplementation() { 17657 CGContextRef context; 17658 ubyte* rawData; 17659 final: 17660 17661 void convertToRgbaBytes(ubyte[] where) { 17662 assert(where.length == this.width * this.height * 4); 17663 17664 // if rawData had a length.... 17665 //assert(rawData.length == where.length); 17666 for(long idx = 0; idx < where.length; idx += 4) { 17667 auto alpha = rawData[idx + 3]; 17668 if(alpha == 255) { 17669 where[idx + 0] = rawData[idx + 0]; // r 17670 where[idx + 1] = rawData[idx + 1]; // g 17671 where[idx + 2] = rawData[idx + 2]; // b 17672 where[idx + 3] = rawData[idx + 3]; // a 17673 } else { 17674 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 17675 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 17676 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 17677 where[idx + 3] = rawData[idx + 3]; // a 17678 17679 } 17680 } 17681 } 17682 17683 void setFromRgbaBytes(in ubyte[] where) { 17684 // FIXME: this is probably wrong 17685 assert(where.length == this.width * this.height * 4); 17686 17687 // if rawData had a length.... 17688 //assert(rawData.length == where.length); 17689 for(long idx = 0; idx < where.length; idx += 4) { 17690 auto alpha = rawData[idx + 3]; 17691 if(alpha == 255) { 17692 rawData[idx + 0] = where[idx + 0]; // r 17693 rawData[idx + 1] = where[idx + 1]; // g 17694 rawData[idx + 2] = where[idx + 2]; // b 17695 rawData[idx + 3] = where[idx + 3]; // a 17696 } else { 17697 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 17698 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 17699 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 17700 rawData[idx + 3] = where[idx + 3]; // a 17701 17702 } 17703 } 17704 } 17705 17706 17707 void createImage(int width, int height, bool forcexshm=false) { 17708 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 17709 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 17710 colorSpace, 17711 kCGImageAlphaPremultipliedLast 17712 |kCGBitmapByteOrder32Big); 17713 CGColorSpaceRelease(colorSpace); 17714 rawData = CGBitmapContextGetData(context); 17715 } 17716 void dispose() { 17717 CGContextRelease(context); 17718 } 17719 17720 void setPixel(int x, int y, Color c) { 17721 auto offset = (y * width + x) * 4; 17722 if (c.a == 255) { 17723 rawData[offset + 0] = c.r; 17724 rawData[offset + 1] = c.g; 17725 rawData[offset + 2] = c.b; 17726 rawData[offset + 3] = c.a; 17727 } else { 17728 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 17729 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 17730 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 17731 rawData[offset + 3] = c.a; 17732 } 17733 } 17734 } 17735 17736 mixin template NativeScreenPainterImplementation() { 17737 CGContextRef context; 17738 ubyte[4] _outlineComponents; 17739 id view; 17740 17741 void create(NativeWindowHandle window) { 17742 context = window.drawingContext; 17743 view = window.view; 17744 } 17745 17746 void dispose() { 17747 setNeedsDisplay(view, true); 17748 } 17749 17750 bool manualInvalidations; 17751 void invalidateRect(Rectangle invalidRect) { } 17752 17753 // NotYetImplementedException 17754 Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); } 17755 void rasterOp(RasterOp op) {} 17756 Pen _activePen; 17757 Color _fillColor; 17758 Rectangle _clipRectangle; 17759 void setClipRectangle(int, int, int, int) {} 17760 void setFont(OperatingSystemFont) {} 17761 int fontHeight() { return 14; } 17762 17763 // end 17764 17765 void pen(Pen pen) { 17766 _activePen = pen; 17767 auto color = pen.color; // FIXME 17768 double alphaComponent = color.a/255.0f; 17769 CGContextSetRGBStrokeColor(context, 17770 color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 17771 17772 if (color.a != 255) { 17773 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 17774 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 17775 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 17776 _outlineComponents[3] = color.a; 17777 } else { 17778 _outlineComponents[0] = color.r; 17779 _outlineComponents[1] = color.g; 17780 _outlineComponents[2] = color.b; 17781 _outlineComponents[3] = color.a; 17782 } 17783 } 17784 17785 @property void fillColor(Color color) { 17786 CGContextSetRGBFillColor(context, 17787 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 17788 } 17789 17790 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 17791 // NotYetImplementedException for upper left/width/height 17792 auto cgImage = CGBitmapContextCreateImage(image.context); 17793 auto size = CGSize(CGBitmapContextGetWidth(image.context), 17794 CGBitmapContextGetHeight(image.context)); 17795 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 17796 CGImageRelease(cgImage); 17797 } 17798 17799 version(OSXCocoa) {} else // NotYetImplementedException 17800 void drawPixmap(Sprite image, int x, int y) { 17801 // FIXME: is this efficient? 17802 auto cgImage = CGBitmapContextCreateImage(image.context); 17803 auto size = CGSize(CGBitmapContextGetWidth(image.context), 17804 CGBitmapContextGetHeight(image.context)); 17805 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 17806 CGImageRelease(cgImage); 17807 } 17808 17809 17810 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 17811 // FIXME: alignment 17812 if (_outlineComponents[3] != 0) { 17813 CGContextSaveGState(context); 17814 auto invAlpha = 1.0f/_outlineComponents[3]; 17815 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 17816 _outlineComponents[1]*invAlpha, 17817 _outlineComponents[2]*invAlpha, 17818 _outlineComponents[3]/255.0f); 17819 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 17820 // auto cfstr = cast(id)createCFString(text); 17821 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 17822 // NSPoint(x, y), null); 17823 // CFRelease(cfstr); 17824 CGContextRestoreGState(context); 17825 } 17826 } 17827 17828 void drawPixel(int x, int y) { 17829 auto rawData = CGBitmapContextGetData(context); 17830 auto width = CGBitmapContextGetWidth(context); 17831 auto height = CGBitmapContextGetHeight(context); 17832 auto offset = ((height - y - 1) * width + x) * 4; 17833 rawData[offset .. offset+4] = _outlineComponents; 17834 } 17835 17836 void drawLine(int x1, int y1, int x2, int y2) { 17837 CGPoint[2] linePoints; 17838 linePoints[0] = CGPoint(x1, y1); 17839 linePoints[1] = CGPoint(x2, y2); 17840 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 17841 } 17842 17843 void drawRectangle(int x, int y, int width, int height) { 17844 CGContextBeginPath(context); 17845 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 17846 CGContextAddRect(context, rect); 17847 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17848 } 17849 17850 void drawEllipse(int x1, int y1, int x2, int y2) { 17851 CGContextBeginPath(context); 17852 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 17853 CGContextAddEllipseInRect(context, rect); 17854 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17855 } 17856 17857 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 17858 // @@@BUG@@@ Does not support elliptic arc (width != height). 17859 CGContextBeginPath(context); 17860 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 17861 start*PI/(180*64), finish*PI/(180*64), 0); 17862 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17863 } 17864 17865 void drawPolygon(Point[] intPoints) { 17866 CGContextBeginPath(context); 17867 auto points = array(map!(CGPoint.fromTuple)(intPoints)); 17868 CGContextAddLines(context, points.ptr, points.length); 17869 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17870 } 17871 } 17872 17873 mixin template NativeSimpleWindowImplementation() { 17874 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 17875 synchronized { 17876 if (NSApp == null) initializeApp(); 17877 } 17878 17879 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 17880 17881 // create the window. 17882 window = initWithContentRect(alloc("NSWindow"), 17883 contentRect, 17884 NSTitledWindowMask 17885 |NSClosableWindowMask 17886 |NSMiniaturizableWindowMask 17887 |NSResizableWindowMask, 17888 NSBackingStoreBuffered, 17889 true); 17890 17891 // set the title & move the window to center. 17892 auto windowTitle = createCFString(title); 17893 setTitle(window, windowTitle); 17894 CFRelease(windowTitle); 17895 center(window); 17896 17897 // create area to draw on. 17898 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 17899 drawingContext = CGBitmapContextCreate(null, width, height, 17900 8, 4*width, colorSpace, 17901 kCGImageAlphaPremultipliedLast 17902 |kCGBitmapByteOrder32Big); 17903 CGColorSpaceRelease(colorSpace); 17904 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 17905 auto matrix = CGContextGetTextMatrix(drawingContext); 17906 matrix.c = -matrix.c; 17907 matrix.d = -matrix.d; 17908 CGContextSetTextMatrix(drawingContext, matrix); 17909 17910 // create the subview that things will be drawn on. 17911 view = initWithFrame(alloc("SDGraphicsView"), contentRect); 17912 setContentView(window, view); 17913 object_setIvar(view, simpleWindowIvar, cast(id)this); 17914 release(view); 17915 17916 setBackgroundColor(window, whiteNSColor); 17917 makeKeyAndOrderFront(window, null); 17918 } 17919 void dispose() { 17920 closeWindow(); 17921 release(window); 17922 } 17923 void closeWindow() { 17924 invalidate(timer); 17925 .close(window); 17926 } 17927 17928 ScreenPainter getPainter(bool manualInvalidations) { 17929 return ScreenPainter(this, this, manualInvalidations); 17930 } 17931 17932 id window; 17933 id timer; 17934 id view; 17935 CGContextRef drawingContext; 17936 } 17937 17938 extern(C) { 17939 private: 17940 BOOL returnTrue3(id self, SEL _cmd, id app) { 17941 return true; 17942 } 17943 BOOL returnTrue2(id self, SEL _cmd) { 17944 return true; 17945 } 17946 17947 void pulse(id self, SEL _cmd) { 17948 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 17949 simpleWindow.handlePulse(); 17950 setNeedsDisplay(self, true); 17951 } 17952 void drawRect(id self, SEL _cmd, NSRect rect) { 17953 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 17954 auto curCtx = graphicsPort(currentNSGraphicsContext); 17955 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 17956 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), 17957 CGBitmapContextGetHeight(simpleWindow.drawingContext)); 17958 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 17959 CGImageRelease(cgImage); 17960 } 17961 void keyDown(id self, SEL _cmd, id event) { 17962 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 17963 17964 // the event may have multiple characters, and we send them all at 17965 // once. 17966 if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) { 17967 auto chars = characters(event); 17968 auto range = CFRange(0, CFStringGetLength(chars)); 17969 auto buffer = new char[range.length*3]; 17970 long actualLength; 17971 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false, 17972 buffer.ptr, cast(int) buffer.length, &actualLength); 17973 foreach (dchar dc; buffer[0..actualLength]) { 17974 if (simpleWindow.handleCharEvent) 17975 simpleWindow.handleCharEvent(dc); 17976 // NotYetImplementedException 17977 //if (simpleWindow.handleKeyEvent) 17978 //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp? 17979 } 17980 } 17981 17982 // the event's 'keyCode' is hardware-dependent. I don't think people 17983 // will like it. Let's leave it to the native handler. 17984 17985 // perform the default action. 17986 17987 // so the default action is to make a bomp sound and i dont want that 17988 // sooooooooo yeah not gonna do that. 17989 17990 //auto superData = objc_super(self, superclass(self)); 17991 //alias extern(C) void function(objc_super*, SEL, id) T; 17992 //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event); 17993 } 17994 } 17995 17996 // initialize the app so that it can be interacted with the user. 17997 // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html 17998 private void initializeApp() { 17999 // push an autorelease pool to avoid leaking. 18000 init(alloc("NSAutoreleasePool")); 18001 18002 // create a new NSApp instance 18003 sharedNSApplication; 18004 setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular); 18005 18006 // create the "Quit" menu. 18007 auto menuBar = init(alloc("NSMenu")); 18008 auto appMenuItem = init(alloc("NSMenuItem")); 18009 addItem(menuBar, appMenuItem); 18010 setMainMenu(NSApp, menuBar); 18011 release(appMenuItem); 18012 release(menuBar); 18013 18014 auto appMenu = init(alloc("NSMenu")); 18015 auto quitTitle = createCFString("Quit"); 18016 auto q = createCFString("q"); 18017 auto quitItem = initWithTitle(alloc("NSMenuItem"), 18018 quitTitle, sel_registerName("terminate:"), q); 18019 addItem(appMenu, quitItem); 18020 setSubmenu(appMenuItem, appMenu); 18021 release(quitItem); 18022 release(appMenu); 18023 CFRelease(q); 18024 CFRelease(quitTitle); 18025 18026 // assign a delegate for the application, allow it to quit when the last 18027 // window is closed. 18028 auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), 18029 "SDWindowCloseDelegate", 0); 18030 class_addMethod(delegateClass, 18031 sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"), 18032 &returnTrue3, "c@:@"); 18033 objc_registerClassPair(delegateClass); 18034 18035 auto appDelegate = init(alloc("SDWindowCloseDelegate")); 18036 setDelegate(NSApp, appDelegate); 18037 activateIgnoringOtherApps(NSApp, true); 18038 18039 // create a new view that draws the graphics and respond to keyDown 18040 // events. 18041 auto viewClass = objc_allocateClassPair(objc_getClass("NSView"), 18042 "SDGraphicsView", (void*).sizeof); 18043 class_addIvar(viewClass, "simpledisplay_simpleWindow", 18044 (void*).sizeof, (void*).alignof, "^v"); 18045 class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"), 18046 &pulse, "v@:"); 18047 class_addMethod(viewClass, sel_registerName("drawRect:"), 18048 &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); 18049 class_addMethod(viewClass, sel_registerName("isFlipped"), 18050 &returnTrue2, "c@:"); 18051 class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"), 18052 &returnTrue2, "c@:"); 18053 class_addMethod(viewClass, sel_registerName("keyDown:"), 18054 &keyDown, "v@:@"); 18055 objc_registerClassPair(viewClass); 18056 simpleWindowIvar = class_getInstanceVariable(viewClass, 18057 "simpledisplay_simpleWindow"); 18058 } 18059 } 18060 18061 version(without_opengl) {} else 18062 extern(System) nothrow @nogc { 18063 //enum uint GL_VERSION = 0x1F02; 18064 //const(char)* glGetString (/*GLenum*/uint); 18065 version(X11) { 18066 static if (!SdpyIsUsingIVGLBinds) { 18067 18068 enum GLX_X_RENDERABLE = 0x8012; 18069 enum GLX_DRAWABLE_TYPE = 0x8010; 18070 enum GLX_RENDER_TYPE = 0x8011; 18071 enum GLX_X_VISUAL_TYPE = 0x22; 18072 enum GLX_TRUE_COLOR = 0x8002; 18073 enum GLX_WINDOW_BIT = 0x00000001; 18074 enum GLX_RGBA_BIT = 0x00000001; 18075 enum GLX_COLOR_INDEX_BIT = 0x00000002; 18076 enum GLX_SAMPLE_BUFFERS = 0x186a0; 18077 enum GLX_SAMPLES = 0x186a1; 18078 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18079 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18080 } 18081 18082 // GLX_EXT_swap_control 18083 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 18084 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 18085 18086 //k8: ugly code to prevent warnings when sdpy is compiled into .a 18087 extern(System) { 18088 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 18089 } 18090 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 18091 18092 // this made public so we don't have to get it again and again 18093 public bool glXCreateContextAttribsARB_present () { 18094 if (glXCreateContextAttribsARBFn is cast(void*)1) { 18095 // get it 18096 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 18097 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 18098 } 18099 return (glXCreateContextAttribsARBFn !is null); 18100 } 18101 18102 // this made public so we don't have to get it again and again 18103 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 18104 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 18105 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 18106 } 18107 18108 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 18109 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 18110 18111 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 18112 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 18113 if (_glx_swapInterval_fn is null) { 18114 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 18115 if (_glx_swapInterval_fn is null) { 18116 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 18117 return; 18118 } 18119 version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); } 18120 } 18121 18122 if(glXSwapIntervalMESA is null) { 18123 // it seems to require both to actually take effect on many computers 18124 // idk why 18125 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 18126 if(glXSwapIntervalMESA is null) 18127 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 18128 } 18129 18130 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 18131 glXSwapIntervalMESA(wait ? 1 : 0); 18132 18133 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 18134 } 18135 } else version(Windows) { 18136 static if (!SdpyIsUsingIVGLBinds) { 18137 enum GL_TRUE = 1; 18138 enum GL_FALSE = 0; 18139 alias int GLint; 18140 18141 public void* glbindGetProcAddress (const(char)* name) { 18142 void* res = wglGetProcAddress(name); 18143 if (res is null) { 18144 /+ 18145 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 18146 import core.sys.windows.windef, core.sys.windows.winbase; 18147 __gshared HINSTANCE dll = null; 18148 if (dll is null) { 18149 dll = LoadLibraryA("opengl32.dll"); 18150 if (dll is null) return null; // <32, but idc 18151 } 18152 res = GetProcAddress(dll, name); 18153 +/ 18154 res = GetProcAddress(gl.libHandle, name); 18155 } 18156 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 18157 return res; 18158 } 18159 } 18160 18161 18162 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 18163 void wglSetVSync(bool wait) { 18164 if(wglSwapIntervalEXT is null) { 18165 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 18166 if(wglSwapIntervalEXT is null) 18167 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 18168 } 18169 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 18170 return; 18171 18172 wglSwapIntervalEXT(wait ? 1 : 0); 18173 } 18174 18175 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18176 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18177 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 18178 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 18179 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 18180 18181 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 18182 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 18183 18184 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 18185 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 18186 18187 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 18188 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 18189 18190 void wglInitOtherFunctions () { 18191 if (wglCreateContextAttribsARB is null) { 18192 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 18193 } 18194 } 18195 } 18196 18197 static if (!SdpyIsUsingIVGLBinds) { 18198 18199 interface GL { 18200 extern(System) @nogc nothrow: 18201 18202 void glGetIntegerv(int, void*); 18203 void glMatrixMode(int); 18204 void glPushMatrix(); 18205 void glLoadIdentity(); 18206 void glOrtho(double, double, double, double, double, double); 18207 void glFrustum(double, double, double, double, double, double); 18208 18209 void glPopMatrix(); 18210 void glEnable(int); 18211 void glDisable(int); 18212 void glClear(int); 18213 void glBegin(int); 18214 void glVertex2f(float, float); 18215 void glVertex3f(float, float, float); 18216 void glEnd(); 18217 void glColor3b(byte, byte, byte); 18218 void glColor3ub(ubyte, ubyte, ubyte); 18219 void glColor4b(byte, byte, byte, byte); 18220 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 18221 void glColor3i(int, int, int); 18222 void glColor3ui(uint, uint, uint); 18223 void glColor4i(int, int, int, int); 18224 void glColor4ui(uint, uint, uint, uint); 18225 void glColor3f(float, float, float); 18226 void glColor4f(float, float, float, float); 18227 void glTranslatef(float, float, float); 18228 void glScalef(float, float, float); 18229 version(X11) { 18230 void glSecondaryColor3b(byte, byte, byte); 18231 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 18232 void glSecondaryColor3i(int, int, int); 18233 void glSecondaryColor3ui(uint, uint, uint); 18234 void glSecondaryColor3f(float, float, float); 18235 } 18236 18237 void glDrawElements(int, int, int, void*); 18238 18239 void glRotatef(float, float, float, float); 18240 18241 uint glGetError(); 18242 18243 void glDeleteTextures(int, uint*); 18244 18245 18246 void glRasterPos2i(int, int); 18247 void glDrawPixels(int, int, uint, uint, void*); 18248 void glClearColor(float, float, float, float); 18249 18250 18251 void glPixelStorei(uint, int); 18252 18253 void glGenTextures(uint, uint*); 18254 void glBindTexture(int, int); 18255 void glTexParameteri(uint, uint, int); 18256 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18257 void glTexImage2D(int, int, int, int, int, int, int, int, in void*); 18258 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 18259 /*GLsizei*/int width, /*GLsizei*/int height, 18260 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 18261 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18262 18263 void glLineWidth(int); 18264 18265 18266 void glTexCoord2f(float, float); 18267 void glVertex2i(int, int); 18268 void glBlendFunc (int, int); 18269 void glDepthFunc (int); 18270 void glViewport(int, int, int, int); 18271 18272 void glClearDepth(double); 18273 18274 void glReadBuffer(uint); 18275 void glReadPixels(int, int, int, int, int, int, void*); 18276 18277 void glFlush(); 18278 void glFinish(); 18279 18280 version(Windows) { 18281 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 18282 HGLRC wglCreateContext(HDC); 18283 HGLRC wglCreateLayerContext(HDC, int); 18284 BOOL wglDeleteContext(HGLRC); 18285 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 18286 HGLRC wglGetCurrentContext(); 18287 HDC wglGetCurrentDC(); 18288 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 18289 PROC wglGetProcAddress(LPCSTR); 18290 BOOL wglMakeCurrent(HDC, HGLRC); 18291 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 18292 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 18293 BOOL wglShareLists(HGLRC, HGLRC); 18294 BOOL wglSwapLayerBuffers(HDC, UINT); 18295 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 18296 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 18297 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18298 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18299 } 18300 18301 } 18302 18303 interface GL3 { 18304 extern(System) @nogc nothrow: 18305 18306 void glGenVertexArrays(GLsizei, GLuint*); 18307 void glBindVertexArray(GLuint); 18308 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 18309 void glGenerateMipmap(GLenum); 18310 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 18311 void glStencilMask(GLuint); 18312 void glStencilFunc(GLenum, GLint, GLuint); 18313 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18314 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18315 GLuint glCreateProgram(); 18316 GLuint glCreateShader(GLenum); 18317 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 18318 void glCompileShader(GLuint); 18319 void glGetShaderiv(GLuint, GLenum, GLint*); 18320 void glAttachShader(GLuint, GLuint); 18321 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 18322 void glLinkProgram(GLuint); 18323 void glGetProgramiv(GLuint, GLenum, GLint*); 18324 void glDeleteProgram(GLuint); 18325 void glDeleteShader(GLuint); 18326 GLint glGetUniformLocation(GLuint, const(GLchar)*); 18327 void glGenBuffers(GLsizei, GLuint*); 18328 18329 void glUniform1f(GLint location, GLfloat v0); 18330 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 18331 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 18332 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 18333 void glUniform1i(GLint location, GLint v0); 18334 void glUniform2i(GLint location, GLint v0, GLint v1); 18335 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 18336 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 18337 void glUniform1ui(GLint location, GLuint v0); 18338 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 18339 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 18340 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 18341 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 18342 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 18343 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 18344 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 18345 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 18346 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 18347 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 18348 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 18349 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 18350 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 18351 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 18352 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 18353 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18354 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18355 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18356 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18357 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18358 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18359 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18360 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18361 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18362 18363 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 18364 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 18365 void glDrawArrays(GLenum, GLint, GLsizei); 18366 void glStencilOp(GLenum, GLenum, GLenum); 18367 void glUseProgram(GLuint); 18368 void glCullFace(GLenum); 18369 void glFrontFace(GLenum); 18370 void glActiveTexture(GLenum); 18371 void glBindBuffer(GLenum, GLuint); 18372 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 18373 void glEnableVertexAttribArray(GLuint); 18374 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 18375 void glUniform1i(GLint, GLint); 18376 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 18377 void glDisableVertexAttribArray(GLuint); 18378 void glDeleteBuffers(GLsizei, const(GLuint)*); 18379 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 18380 void glLogicOp (GLenum opcode); 18381 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 18382 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 18383 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 18384 GLenum glCheckFramebufferStatus (GLenum target); 18385 void glBindFramebuffer (GLenum target, GLuint framebuffer); 18386 } 18387 18388 interface GL4 { 18389 extern(System) @nogc nothrow: 18390 18391 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 18392 /*GLsizei*/int width, /*GLsizei*/int height, 18393 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 18394 } 18395 18396 interface GLU { 18397 extern(System) @nogc nothrow: 18398 18399 void gluLookAt(double, double, double, double, double, double, double, double, double); 18400 void gluPerspective(double, double, double, double); 18401 18402 char* gluErrorString(uint); 18403 } 18404 18405 18406 enum GL_RED = 0x1903; 18407 enum GL_ALPHA = 0x1906; 18408 18409 enum uint GL_FRONT = 0x0404; 18410 18411 enum uint GL_BLEND = 0x0be2; 18412 enum uint GL_LEQUAL = 0x0203; 18413 18414 18415 enum uint GL_RGB = 0x1907; 18416 enum uint GL_BGRA = 0x80e1; 18417 enum uint GL_RGBA = 0x1908; 18418 enum uint GL_TEXTURE_2D = 0x0DE1; 18419 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 18420 enum uint GL_NEAREST = 0x2600; 18421 enum uint GL_LINEAR = 0x2601; 18422 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 18423 enum uint GL_TEXTURE_WRAP_S = 0x2802; 18424 enum uint GL_TEXTURE_WRAP_T = 0x2803; 18425 enum uint GL_REPEAT = 0x2901; 18426 enum uint GL_CLAMP = 0x2900; 18427 enum uint GL_CLAMP_TO_EDGE = 0x812F; 18428 enum uint GL_CLAMP_TO_BORDER = 0x812D; 18429 enum uint GL_DECAL = 0x2101; 18430 enum uint GL_MODULATE = 0x2100; 18431 enum uint GL_TEXTURE_ENV = 0x2300; 18432 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 18433 enum uint GL_REPLACE = 0x1E01; 18434 enum uint GL_LIGHTING = 0x0B50; 18435 enum uint GL_DITHER = 0x0BD0; 18436 18437 enum uint GL_NO_ERROR = 0; 18438 18439 18440 18441 enum int GL_VIEWPORT = 0x0BA2; 18442 enum int GL_MODELVIEW = 0x1700; 18443 enum int GL_TEXTURE = 0x1702; 18444 enum int GL_PROJECTION = 0x1701; 18445 enum int GL_DEPTH_TEST = 0x0B71; 18446 18447 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 18448 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 18449 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 18450 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 18451 18452 enum int GL_POINTS = 0x0000; 18453 enum int GL_LINES = 0x0001; 18454 enum int GL_LINE_LOOP = 0x0002; 18455 enum int GL_LINE_STRIP = 0x0003; 18456 enum int GL_TRIANGLES = 0x0004; 18457 enum int GL_TRIANGLE_STRIP = 5; 18458 enum int GL_TRIANGLE_FAN = 6; 18459 enum int GL_QUADS = 7; 18460 enum int GL_QUAD_STRIP = 8; 18461 enum int GL_POLYGON = 9; 18462 18463 alias GLvoid = void; 18464 alias GLboolean = ubyte; 18465 alias GLuint = uint; 18466 alias GLenum = uint; 18467 alias GLchar = char; 18468 alias GLsizei = int; 18469 alias GLfloat = float; 18470 alias GLintptr = size_t; 18471 alias GLsizeiptr = ptrdiff_t; 18472 18473 18474 enum uint GL_INVALID_ENUM = 0x0500; 18475 18476 enum uint GL_ZERO = 0; 18477 enum uint GL_ONE = 1; 18478 18479 enum uint GL_BYTE = 0x1400; 18480 enum uint GL_UNSIGNED_BYTE = 0x1401; 18481 enum uint GL_SHORT = 0x1402; 18482 enum uint GL_UNSIGNED_SHORT = 0x1403; 18483 enum uint GL_INT = 0x1404; 18484 enum uint GL_UNSIGNED_INT = 0x1405; 18485 enum uint GL_FLOAT = 0x1406; 18486 enum uint GL_2_BYTES = 0x1407; 18487 enum uint GL_3_BYTES = 0x1408; 18488 enum uint GL_4_BYTES = 0x1409; 18489 enum uint GL_DOUBLE = 0x140A; 18490 18491 enum uint GL_STREAM_DRAW = 0x88E0; 18492 18493 enum uint GL_CCW = 0x0901; 18494 18495 enum uint GL_STENCIL_TEST = 0x0B90; 18496 enum uint GL_SCISSOR_TEST = 0x0C11; 18497 18498 enum uint GL_EQUAL = 0x0202; 18499 enum uint GL_NOTEQUAL = 0x0205; 18500 18501 enum uint GL_ALWAYS = 0x0207; 18502 enum uint GL_KEEP = 0x1E00; 18503 18504 enum uint GL_INCR = 0x1E02; 18505 18506 enum uint GL_INCR_WRAP = 0x8507; 18507 enum uint GL_DECR_WRAP = 0x8508; 18508 18509 enum uint GL_CULL_FACE = 0x0B44; 18510 enum uint GL_BACK = 0x0405; 18511 18512 enum uint GL_FRAGMENT_SHADER = 0x8B30; 18513 enum uint GL_VERTEX_SHADER = 0x8B31; 18514 18515 enum uint GL_COMPILE_STATUS = 0x8B81; 18516 enum uint GL_LINK_STATUS = 0x8B82; 18517 18518 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 18519 18520 enum uint GL_STATIC_DRAW = 0x88E4; 18521 18522 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 18523 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 18524 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 18525 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 18526 18527 enum uint GL_GENERATE_MIPMAP = 0x8191; 18528 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 18529 18530 enum uint GL_TEXTURE0 = 0x84C0U; 18531 enum uint GL_TEXTURE1 = 0x84C1U; 18532 18533 enum uint GL_ARRAY_BUFFER = 0x8892; 18534 18535 enum uint GL_SRC_COLOR = 0x0300; 18536 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 18537 enum uint GL_SRC_ALPHA = 0x0302; 18538 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 18539 enum uint GL_DST_ALPHA = 0x0304; 18540 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 18541 enum uint GL_DST_COLOR = 0x0306; 18542 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 18543 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 18544 18545 enum uint GL_INVERT = 0x150AU; 18546 18547 enum uint GL_DEPTH_STENCIL = 0x84F9U; 18548 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 18549 18550 enum uint GL_FRAMEBUFFER = 0x8D40U; 18551 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 18552 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 18553 18554 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 18555 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 18556 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 18557 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 18558 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 18559 18560 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 18561 enum uint GL_CLEAR = 0x1500U; 18562 enum uint GL_COPY = 0x1503U; 18563 enum uint GL_XOR = 0x1506U; 18564 18565 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 18566 18567 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 18568 18569 } 18570 } 18571 18572 /++ 18573 History: 18574 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. 18575 +/ 18576 __gshared bool gluSuccessfullyLoaded = true; 18577 18578 version(without_opengl) {} else { 18579 static if(!SdpyIsUsingIVGLBinds) { 18580 version(Windows) { 18581 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 18582 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 18583 } else { 18584 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 18585 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 18586 } 18587 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 18588 18589 18590 shared static this() { 18591 gl.loadDynamicLibrary(); 18592 18593 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 18594 // unless those functions are actually used 18595 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 18596 glu.loadDynamicLibrary(); 18597 } 18598 } 18599 } 18600 18601 /++ 18602 Convenience method for converting D arrays to opengl buffer data 18603 18604 I would LOVE to overload it with the original glBufferData, but D won't 18605 let me since glBufferData is a function pointer :( 18606 18607 Added: August 25, 2020 (version 8.5) 18608 +/ 18609 version(without_opengl) {} else 18610 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 18611 glBufferData(target, data.length, data.ptr, usage); 18612 } 18613 18614 /+ 18615 /++ 18616 A matrix for simple uses that easily integrates with [OpenGlShader]. 18617 18618 Might not be useful to you since it only as some simple functions and 18619 probably isn't that fast. 18620 18621 Note it uses an inline static array for its storage, so copying it 18622 may be expensive. 18623 +/ 18624 struct BasicMatrix(int columns, int rows, T = float) { 18625 import core.stdc.math; 18626 18627 T[columns * rows] data = 0.0; 18628 18629 /++ 18630 Basic operations that operate *in place*. 18631 +/ 18632 void translate() { 18633 18634 } 18635 18636 /// ditto 18637 void scale() { 18638 18639 } 18640 18641 /// ditto 18642 void rotate() { 18643 18644 } 18645 18646 /++ 18647 18648 +/ 18649 static if(columns == rows) 18650 static BasicMatrix identity() { 18651 BasicMatrix m; 18652 foreach(i; 0 .. columns) 18653 data[0 + i + i * columns] = 1.0; 18654 return m; 18655 } 18656 18657 static BasicMatrix ortho() { 18658 return BasicMatrix.init; 18659 } 18660 } 18661 +/ 18662 18663 /++ 18664 Convenience class for using opengl shaders. 18665 18666 Ensure that you've loaded opengl 3+ and set your active 18667 context before trying to use this. 18668 18669 Added: August 25, 2020 (version 8.5) 18670 +/ 18671 version(without_opengl) {} else 18672 final class OpenGlShader { 18673 private int shaderProgram_; 18674 private @property void shaderProgram(int a) { 18675 shaderProgram_ = a; 18676 } 18677 /// Get the program ID for use in OpenGL functions. 18678 public @property int shaderProgram() { 18679 return shaderProgram_; 18680 } 18681 18682 /++ 18683 18684 +/ 18685 static struct Source { 18686 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 18687 string code; /// 18688 } 18689 18690 /++ 18691 Helper method to just compile some shader code and check for errors 18692 while you do glCreateShader, etc. on the outside yourself. 18693 18694 This just does `glShaderSource` and `glCompileShader` for the given code. 18695 18696 If you the OpenGlShader class constructor, you never need to call this yourself. 18697 +/ 18698 static void compile(int sid, Source code) { 18699 const(char)*[1] buffer; 18700 int[1] lengthBuffer; 18701 18702 buffer[0] = code.code.ptr; 18703 lengthBuffer[0] = cast(int) code.code.length; 18704 18705 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 18706 glCompileShader(sid); 18707 18708 int success; 18709 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 18710 if(!success) { 18711 char[512] info; 18712 int len; 18713 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 18714 18715 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 18716 } 18717 } 18718 18719 /++ 18720 Calls `glLinkProgram` and throws if error a occurs. 18721 18722 If you the OpenGlShader class constructor, you never need to call this yourself. 18723 +/ 18724 static void link(int shaderProgram) { 18725 glLinkProgram(shaderProgram); 18726 int success; 18727 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 18728 if(!success) { 18729 char[512] info; 18730 int len; 18731 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 18732 18733 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 18734 } 18735 } 18736 18737 /++ 18738 Constructs the shader object by calling `glCreateProgram`, then 18739 compiling each given [Source], and finally, linking them together. 18740 18741 Throws: on compile or link failure. 18742 +/ 18743 this(Source[] codes...) { 18744 shaderProgram = glCreateProgram(); 18745 18746 int[16] shadersBufferStack; 18747 18748 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 18749 shadersBufferStack[0 .. codes.length] : 18750 new int[](codes.length); 18751 18752 foreach(idx, code; codes) { 18753 shadersBuffer[idx] = glCreateShader(code.type); 18754 18755 compile(shadersBuffer[idx], code); 18756 18757 glAttachShader(shaderProgram, shadersBuffer[idx]); 18758 } 18759 18760 link(shaderProgram); 18761 18762 foreach(s; shadersBuffer) 18763 glDeleteShader(s); 18764 } 18765 18766 /// Calls `glUseProgram(this.shaderProgram)` 18767 void use() { 18768 glUseProgram(this.shaderProgram); 18769 } 18770 18771 /// Deletes the program. 18772 void delete_() { 18773 glDeleteProgram(shaderProgram); 18774 shaderProgram = 0; 18775 } 18776 18777 /++ 18778 [OpenGlShader.uniforms].name gives you one of these. 18779 18780 You can get the id out of it or just assign 18781 +/ 18782 static struct Uniform { 18783 /// the id passed to glUniform* 18784 int id; 18785 18786 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 18787 void opAssign(float x, float y, float z, float w) { 18788 if(id != -1) 18789 glUniform4f(id, x, y, z, w); 18790 } 18791 18792 void opAssign(float x) { 18793 if(id != -1) 18794 glUniform1f(id, x); 18795 } 18796 18797 void opAssign(float x, float y) { 18798 if(id != -1) 18799 glUniform2f(id, x, y); 18800 } 18801 18802 void opAssign(T)(T t) { 18803 t.glUniform(id); 18804 } 18805 } 18806 18807 static struct UniformsHelper { 18808 OpenGlShader _shader; 18809 18810 @property Uniform opDispatch(string name)() { 18811 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 18812 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 18813 //if(i == -1) 18814 //throw new Exception("Could not find uniform " ~ name); 18815 return Uniform(i); 18816 } 18817 18818 @property void opDispatch(string name, T)(T t) { 18819 Uniform f = this.opDispatch!name; 18820 t.glUniform(f); 18821 } 18822 } 18823 18824 /++ 18825 Gives access to the uniforms through dot access. 18826 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 18827 +/ 18828 @property UniformsHelper uniforms() { return UniformsHelper(this); } 18829 } 18830 18831 version(without_opengl) {} else { 18832 /++ 18833 A static container of experimental types and value constructors for opengl 3+ shaders. 18834 18835 18836 You can declare variables like: 18837 18838 ``` 18839 OGL.vec3f something; 18840 ``` 18841 18842 But generally it would be used with [OpenGlShader]'s uniform helpers like 18843 18844 ``` 18845 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 18846 ``` 18847 18848 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 18849 18850 18851 History: 18852 Added December 7, 2021. Not yet stable. 18853 +/ 18854 final class OGL { 18855 static: 18856 18857 private template typeFromSpecifier(string specifier) { 18858 static if(specifier == "f") 18859 alias typeFromSpecifier = GLfloat; 18860 else static if(specifier == "i") 18861 alias typeFromSpecifier = GLint; 18862 else static if(specifier == "ui") 18863 alias typeFromSpecifier = GLuint; 18864 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 18865 } 18866 18867 private template CommonType(T...) { 18868 static if(T.length == 1) 18869 alias CommonType = T[0]; 18870 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 18871 alias CommonType = CommonType!(C, T[2 .. $]); 18872 } 18873 18874 private template typesToSpecifier(T...) { 18875 static if(is(CommonType!T == float)) 18876 enum typesToSpecifier = "f"; 18877 else static if(is(CommonType!T == int)) 18878 enum typesToSpecifier = "i"; 18879 else static if(is(CommonType!T == uint)) 18880 enum typesToSpecifier = "ui"; 18881 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 18882 } 18883 18884 private template genNames(size_t dim, size_t dim2 = 0) { 18885 string helper() { 18886 string s; 18887 if(dim2) { 18888 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;"; 18889 } else { 18890 if(dim > 0) s ~= "type x = 0;"; 18891 if(dim > 1) s ~= "type y = 0;"; 18892 if(dim > 2) s ~= "type z = 0;"; 18893 if(dim > 3) s ~= "type w = 0;"; 18894 } 18895 return s; 18896 } 18897 18898 enum genNames = helper(); 18899 } 18900 18901 // there's vec, arrays of vec, mat, and arrays of mat 18902 template opDispatch(string name) 18903 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 18904 { 18905 static if(name[4] == 'x') { 18906 enum dimX = cast(int) (name[3] - '0'); 18907 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 18908 18909 enum dimY = cast(int) (name[5] - '0'); 18910 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 18911 18912 enum isArray = name[$ - 1] == 'v'; 18913 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 18914 alias type = typeFromSpecifier!typeSpecifier; 18915 } else { 18916 enum dim = cast(int) (name[3] - '0'); 18917 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 18918 enum isArray = name[$ - 1] == 'v'; 18919 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 18920 alias type = typeFromSpecifier!typeSpecifier; 18921 } 18922 18923 align(1) 18924 struct opDispatch { 18925 align(1): 18926 static if(name[4] == 'x') 18927 mixin(genNames!(dimX, dimY)); 18928 else 18929 mixin(genNames!dim); 18930 18931 private void glUniform(OpenGlShader.Uniform assignTo) { 18932 glUniform(assignTo.id); 18933 } 18934 private void glUniform(int assignTo) { 18935 static if(name[4] == 'x') { 18936 // FIXME 18937 pragma(msg, "This matrix uniform helper has never been tested!!!!"); 18938 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr); 18939 } else 18940 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 18941 } 18942 } 18943 } 18944 18945 auto vec(T...)(T members) { 18946 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 18947 } 18948 } 18949 } 18950 18951 version(linux) { 18952 version(with_eventloop) {} else { 18953 private int epollFd = -1; 18954 void prepareEventLoop() { 18955 if(epollFd != -1) 18956 return; // already initialized, no need to do it again 18957 import ep = core.sys.linux.epoll; 18958 18959 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 18960 if(epollFd == -1) 18961 throw new Exception("epoll create failure"); 18962 } 18963 } 18964 } else version(Posix) { 18965 void prepareEventLoop() {} 18966 } 18967 18968 version(X11) { 18969 import core.stdc.locale : LC_ALL; // rdmd fix 18970 __gshared bool sdx_isUTF8Locale; 18971 18972 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 18973 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 18974 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 18975 // anal magic is here. I (Ketmar) hope you like it. 18976 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 18977 // always return correct unicode symbols. The detection is here 'cause user can change locale 18978 // later. 18979 18980 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 18981 shared static this () { 18982 if(!librariesSuccessfullyLoaded) 18983 return; 18984 18985 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 18986 18987 // this doesn't hurt; it may add some locking, but the speed is still 18988 // allows doing 60 FPS videogames; also, ignore the result, as most 18989 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 18990 // never seen this failing). 18991 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 18992 18993 setlocale(LC_ALL, ""); 18994 // check if out locale is UTF-8 18995 auto lct = setlocale(LC_CTYPE, null); 18996 if (lct is null) { 18997 sdx_isUTF8Locale = false; 18998 } else { 18999 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 19000 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 19001 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 19002 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 19003 { 19004 sdx_isUTF8Locale = true; 19005 break; 19006 } 19007 } 19008 } 19009 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 19010 } 19011 } 19012 19013 class ExperimentalTextComponent2 { 19014 /+ 19015 Stage 1: get it working monospace 19016 Stage 2: use proportional font 19017 Stage 3: allow changes in inline style 19018 Stage 4: allow new fonts and sizes in the middle 19019 Stage 5: optimize gap buffer 19020 Stage 6: optimize layout 19021 Stage 7: word wrap 19022 Stage 8: justification 19023 Stage 9: editing, selection, etc. 19024 19025 Operations: 19026 insert text 19027 overstrike text 19028 select 19029 cut 19030 modify 19031 +/ 19032 19033 /++ 19034 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. 19035 +/ 19036 this(SimpleWindow window) { 19037 this.window = window; 19038 } 19039 19040 private SimpleWindow window; 19041 19042 19043 /++ 19044 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 19045 representing the internal parts. The first pass is focused on the x parameter, then the 19046 renderer is responsible for going back to the parts in the current line and calling 19047 adjustDownForAscent to change the y params. 19048 +/ 19049 static interface ComponentRenderHelper { 19050 19051 /+ 19052 When you do an edit, possibly stuff on the same line previously need to move (to adjust 19053 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 19054 to move (adjust y to make room for new line) until you get back to the same position, 19055 then you can stop - if one thing is unchanged, nothing after it is changed too. 19056 19057 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 19058 once you reach something that is unchanged, you can stop. 19059 +/ 19060 19061 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 19062 19063 int ascent() const; 19064 int descent() const; 19065 19066 int advance() const; 19067 19068 bool endsWithExplititLineBreak() const; 19069 } 19070 19071 static interface RenderResult { 19072 /++ 19073 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. 19074 +/ 19075 void popFront(); 19076 @property bool empty() const; 19077 @property ComponentRenderHelper front() const; 19078 19079 void repositionForNextLine(Point baseline, int availableWidth); 19080 } 19081 19082 static interface ComponentInFlow { 19083 void draw(ScreenPainter painter); 19084 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 19085 19086 bool startsWithExplicitLineBreak() const; 19087 } 19088 19089 static class TextFlowComponent : ComponentInFlow { 19090 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 19091 19092 Color foreground; 19093 Color background; 19094 19095 OperatingSystemFont font; // should NEVER be null 19096 19097 ubyte attributes; // underline, strike through, display on new block 19098 19099 version(Windows) 19100 const(wchar)[] content; 19101 else 19102 const(char)[] content; // this should NEVER have a newline, except at the end 19103 19104 RenderedComponent[] rendered; // entirely controlled by [rerender] 19105 19106 // could prolly put some spacing around it too like margin / padding 19107 19108 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 19109 in { assert(font !is null); 19110 assert(!font.isNull); } 19111 do 19112 { 19113 this.foreground = f; 19114 this.background = b; 19115 this.font = font; 19116 19117 this.attributes = attr; 19118 version(Windows) { 19119 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 19120 auto sz = sizeOfConvertedWstring(c, conversionFlags); 19121 auto buffer = new wchar[](sz); 19122 this.content = makeWindowsString(c, buffer, conversionFlags); 19123 } else { 19124 this.content = c.dup; 19125 } 19126 } 19127 19128 void draw(ScreenPainter painter) { 19129 painter.setFont(this.font); 19130 painter.outlineColor = this.foreground; 19131 painter.fillColor = Color.transparent; 19132 foreach(rendered; this.rendered) { 19133 // the component works in term of baseline, 19134 // but the painter works in term of upper left bounding box 19135 // so need to translate that 19136 19137 if(this.background.a) { 19138 painter.fillColor = this.background; 19139 painter.outlineColor = this.background; 19140 19141 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 19142 19143 painter.outlineColor = this.foreground; 19144 painter.fillColor = Color.transparent; 19145 } 19146 19147 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 19148 19149 // FIXME: strike through, underline, highlight selection, etc. 19150 } 19151 } 19152 } 19153 19154 // I could split the parts into words on render 19155 // for easier word-wrap, each one being an unbreakable "inline-block" 19156 private TextFlowComponent[] parts; 19157 private int needsRerenderFrom; 19158 19159 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 19160 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 19161 parts ~= new TextFlowComponent(f, b, font, attr, c); 19162 } 19163 19164 static struct RenderedComponent { 19165 int startX; 19166 int startY; 19167 short width; 19168 // 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! 19169 // for individual chars in here you've gotta process on demand 19170 version(Windows) 19171 const(wchar)[] slice; 19172 else 19173 const(char)[] slice; 19174 } 19175 19176 19177 void rerender(Rectangle boundingBox) { 19178 Point baseline = boundingBox.upperLeft; 19179 19180 this.boundingBox.left = boundingBox.left; 19181 this.boundingBox.top = boundingBox.top; 19182 19183 auto remainingParts = parts; 19184 19185 int largestX; 19186 19187 19188 foreach(part; parts) 19189 part.font.prepareContext(window); 19190 scope(exit) 19191 foreach(part; parts) 19192 part.font.releaseContext(); 19193 19194 calculateNextLine: 19195 19196 int nextLineHeight = 0; 19197 int nextBiggestDescent = 0; 19198 19199 foreach(part; remainingParts) { 19200 auto height = part.font.ascent; 19201 if(height > nextLineHeight) 19202 nextLineHeight = height; 19203 if(part.font.descent > nextBiggestDescent) 19204 nextBiggestDescent = part.font.descent; 19205 if(part.content.length && part.content[$-1] == '\n') 19206 break; 19207 } 19208 19209 baseline.y += nextLineHeight; 19210 auto lineStart = baseline; 19211 19212 while(remainingParts.length) { 19213 remainingParts[0].rendered = null; 19214 19215 bool eol; 19216 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 19217 eol = true; 19218 19219 // FIXME: word wrap 19220 auto font = remainingParts[0].font; 19221 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 19222 auto width = font.stringWidth(slice, window); 19223 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 19224 19225 remainingParts = remainingParts[1 .. $]; 19226 baseline.x += width; 19227 19228 if(eol) { 19229 baseline.y += nextBiggestDescent; 19230 if(baseline.x > largestX) 19231 largestX = baseline.x; 19232 baseline.x = lineStart.x; 19233 goto calculateNextLine; 19234 } 19235 } 19236 19237 if(baseline.x > largestX) 19238 largestX = baseline.x; 19239 19240 this.boundingBox.right = largestX; 19241 this.boundingBox.bottom = baseline.y; 19242 } 19243 19244 // you must call rerender first! 19245 void draw(ScreenPainter painter) { 19246 foreach(part; parts) { 19247 part.draw(painter); 19248 } 19249 } 19250 19251 struct IdentifyResult { 19252 TextFlowComponent part; 19253 int charIndexInPart; 19254 int totalCharIndex = -1; // if this is -1, it just means the end 19255 19256 Rectangle boundingBox; 19257 } 19258 19259 IdentifyResult identify(Point pt, bool exact = false) { 19260 if(parts.length == 0) 19261 return IdentifyResult(null, 0); 19262 19263 if(pt.y < boundingBox.top) { 19264 if(exact) 19265 return IdentifyResult(null, 1); 19266 return IdentifyResult(parts[0], 0); 19267 } 19268 if(pt.y > boundingBox.bottom) { 19269 if(exact) 19270 return IdentifyResult(null, 2); 19271 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 19272 } 19273 19274 int tci = 0; 19275 19276 // I should probably like binary search this or something... 19277 foreach(ref part; parts) { 19278 foreach(rendered; part.rendered) { 19279 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 19280 if(rect.contains(pt)) { 19281 auto x = pt.x - rendered.startX; 19282 auto estimatedIdx = x / part.font.averageWidth; 19283 19284 if(estimatedIdx < 0) 19285 estimatedIdx = 0; 19286 19287 if(estimatedIdx > rendered.slice.length) 19288 estimatedIdx = cast(int) rendered.slice.length; 19289 19290 int idx; 19291 int x1, x2; 19292 if(part.font.isMonospace) { 19293 auto w = part.font.averageWidth; 19294 if(!exact && x > (estimatedIdx + 1) * w) 19295 return IdentifyResult(null, 4); 19296 idx = estimatedIdx; 19297 x1 = idx * w; 19298 x2 = (idx + 1) * w; 19299 } else { 19300 idx = estimatedIdx; 19301 19302 part.font.prepareContext(window); 19303 scope(exit) part.font.releaseContext(); 19304 19305 // int iterations; 19306 19307 while(true) { 19308 // iterations++; 19309 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 19310 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 19311 19312 x1 += rendered.startX; 19313 x2 += rendered.startX; 19314 19315 if(pt.x < x1) { 19316 if(idx == 0) { 19317 if(exact) 19318 return IdentifyResult(null, 6); 19319 else 19320 break; 19321 } 19322 idx--; 19323 } else if(pt.x > x2) { 19324 idx++; 19325 if(idx > rendered.slice.length) { 19326 if(exact) 19327 return IdentifyResult(null, 5); 19328 else 19329 break; 19330 } 19331 } else if(pt.x >= x1 && pt.x <= x2) { 19332 if(idx) 19333 idx--; // point it at the original index 19334 break; // we fit 19335 } 19336 } 19337 19338 // import std.stdio; writeln(iterations) 19339 } 19340 19341 19342 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 19343 } 19344 } 19345 tci += cast(int) part.content.length; // FIXME: utf-8? 19346 } 19347 return IdentifyResult(null, 3); 19348 } 19349 19350 Rectangle boundingBox; // only set after [rerender] 19351 19352 // text will be positioned around the exclusion zone 19353 static struct ExclusionZone { 19354 19355 } 19356 19357 ExclusionZone[] exclusionZones; 19358 } 19359 19360 19361 // Don't use this yet. When I'm happy with it, I will move it to the 19362 // regular module namespace. 19363 mixin template ExperimentalTextComponent() { 19364 19365 static: 19366 19367 alias Rectangle = arsd.color.Rectangle; 19368 19369 struct ForegroundColor { 19370 Color color; 19371 alias color this; 19372 19373 this(Color c) { 19374 color = c; 19375 } 19376 19377 this(int r, int g, int b, int a = 255) { 19378 color = Color(r, g, b, a); 19379 } 19380 19381 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 19382 return ForegroundColor(mixin("Color." ~ s)); 19383 } 19384 } 19385 19386 struct BackgroundColor { 19387 Color color; 19388 alias color this; 19389 19390 this(Color c) { 19391 color = c; 19392 } 19393 19394 this(int r, int g, int b, int a = 255) { 19395 color = Color(r, g, b, a); 19396 } 19397 19398 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 19399 return BackgroundColor(mixin("Color." ~ s)); 19400 } 19401 } 19402 19403 static class InlineElement { 19404 string text; 19405 19406 BlockElement containingBlock; 19407 19408 Color color = Color.black; 19409 Color backgroundColor = Color.transparent; 19410 ushort styles; 19411 19412 string font; 19413 int fontSize; 19414 19415 int lineHeight; 19416 19417 void* identifier; 19418 19419 Rectangle boundingBox; 19420 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 19421 19422 bool isMergeCompatible(InlineElement other) { 19423 return 19424 containingBlock is other.containingBlock && 19425 color == other.color && 19426 backgroundColor == other.backgroundColor && 19427 styles == other.styles && 19428 font == other.font && 19429 fontSize == other.fontSize && 19430 lineHeight == other.lineHeight && 19431 true; 19432 } 19433 19434 int xOfIndex(size_t index) { 19435 if(index < letterXs.length) 19436 return letterXs[index]; 19437 else 19438 return boundingBox.right; 19439 } 19440 19441 InlineElement clone() { 19442 auto ie = new InlineElement(); 19443 ie.tupleof = this.tupleof; 19444 return ie; 19445 } 19446 19447 InlineElement getPreviousInlineElement() { 19448 InlineElement prev = null; 19449 foreach(ie; this.containingBlock.parts) { 19450 if(ie is this) 19451 break; 19452 prev = ie; 19453 } 19454 if(prev is null) { 19455 BlockElement pb; 19456 BlockElement cb = this.containingBlock; 19457 moar: 19458 foreach(ie; this.containingBlock.containingLayout.blocks) { 19459 if(ie is cb) 19460 break; 19461 pb = ie; 19462 } 19463 if(pb is null) 19464 return null; 19465 if(pb.parts.length == 0) { 19466 cb = pb; 19467 goto moar; 19468 } 19469 19470 prev = pb.parts[$-1]; 19471 19472 } 19473 return prev; 19474 } 19475 19476 InlineElement getNextInlineElement() { 19477 InlineElement next = null; 19478 foreach(idx, ie; this.containingBlock.parts) { 19479 if(ie is this) { 19480 if(idx + 1 < this.containingBlock.parts.length) 19481 next = this.containingBlock.parts[idx + 1]; 19482 break; 19483 } 19484 } 19485 if(next is null) { 19486 BlockElement n; 19487 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 19488 if(ie is this.containingBlock) { 19489 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 19490 n = this.containingBlock.containingLayout.blocks[idx + 1]; 19491 break; 19492 } 19493 } 19494 if(n is null) 19495 return null; 19496 19497 if(n.parts.length) 19498 next = n.parts[0]; 19499 else {} // FIXME 19500 19501 } 19502 return next; 19503 } 19504 19505 } 19506 19507 // Block elements are used entirely for positioning inline elements, 19508 // which are the things that are actually drawn. 19509 class BlockElement { 19510 InlineElement[] parts; 19511 uint alignment; 19512 19513 int whiteSpace; // pre, pre-wrap, wrap 19514 19515 TextLayout containingLayout; 19516 19517 // inputs 19518 Point where; 19519 Size minimumSize; 19520 Size maximumSize; 19521 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 19522 void* identifier; 19523 19524 Rectangle margin; 19525 Rectangle padding; 19526 19527 // outputs 19528 Rectangle[] boundingBoxes; 19529 } 19530 19531 struct TextIdentifyResult { 19532 InlineElement element; 19533 int offset; 19534 19535 private TextIdentifyResult fixupNewline() { 19536 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 19537 offset--; 19538 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 19539 offset--; 19540 } 19541 return this; 19542 } 19543 } 19544 19545 class TextLayout { 19546 BlockElement[] blocks; 19547 Rectangle boundingBox_; 19548 Rectangle boundingBox() { return boundingBox_; } 19549 void boundingBox(Rectangle r) { 19550 if(r != boundingBox_) { 19551 boundingBox_ = r; 19552 layoutInvalidated = true; 19553 } 19554 } 19555 19556 Rectangle contentBoundingBox() { 19557 Rectangle r; 19558 foreach(block; blocks) 19559 foreach(ie; block.parts) { 19560 if(ie.boundingBox.right > r.right) 19561 r.right = ie.boundingBox.right; 19562 if(ie.boundingBox.bottom > r.bottom) 19563 r.bottom = ie.boundingBox.bottom; 19564 } 19565 return r; 19566 } 19567 19568 BlockElement[] getBlocks() { 19569 return blocks; 19570 } 19571 19572 InlineElement[] getTexts() { 19573 InlineElement[] elements; 19574 foreach(block; blocks) 19575 elements ~= block.parts; 19576 return elements; 19577 } 19578 19579 string getPlainText() { 19580 string text; 19581 foreach(block; blocks) 19582 foreach(part; block.parts) 19583 text ~= part.text; 19584 return text; 19585 } 19586 19587 string getHtml() { 19588 return null; // FIXME 19589 } 19590 19591 this(Rectangle boundingBox) { 19592 this.boundingBox = boundingBox; 19593 } 19594 19595 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 19596 auto be = new BlockElement(); 19597 be.containingLayout = this; 19598 if(after is null) 19599 blocks ~= be; 19600 else { 19601 foreach(idx, b; blocks) { 19602 if(b is after.containingBlock) { 19603 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 19604 break; 19605 } 19606 } 19607 } 19608 return be; 19609 } 19610 19611 void clear() { 19612 blocks = null; 19613 selectionStart = selectionEnd = caret = Caret.init; 19614 } 19615 19616 void addText(Args...)(Args args) { 19617 if(blocks.length == 0) 19618 addBlock(); 19619 19620 InlineElement ie = new InlineElement(); 19621 foreach(idx, arg; args) { 19622 static if(is(typeof(arg) == ForegroundColor)) 19623 ie.color = arg; 19624 else static if(is(typeof(arg) == TextFormat)) { 19625 if(arg & 0x8000) // ~TextFormat.something turns it off 19626 ie.styles &= arg; 19627 else 19628 ie.styles |= arg; 19629 } else static if(is(typeof(arg) == string)) { 19630 static if(idx == 0 && args.length > 1) 19631 static assert(0, "Put styles before the string."); 19632 size_t lastLineIndex; 19633 foreach(cidx, char a; arg) { 19634 if(a == '\n') { 19635 ie.text = arg[lastLineIndex .. cidx + 1]; 19636 lastLineIndex = cidx + 1; 19637 ie.containingBlock = blocks[$-1]; 19638 blocks[$-1].parts ~= ie.clone; 19639 ie.text = null; 19640 } else { 19641 19642 } 19643 } 19644 19645 ie.text = arg[lastLineIndex .. $]; 19646 ie.containingBlock = blocks[$-1]; 19647 blocks[$-1].parts ~= ie.clone; 19648 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 19649 } 19650 } 19651 19652 invalidateLayout(); 19653 } 19654 19655 void tryMerge(InlineElement into, InlineElement what) { 19656 if(!into.isMergeCompatible(what)) { 19657 return; // cannot merge, different configs 19658 } 19659 19660 // cool, can merge, bring text together... 19661 into.text ~= what.text; 19662 19663 // and remove what 19664 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 19665 if(what.containingBlock.parts[a] is what) { 19666 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 19667 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 19668 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 19669 19670 } 19671 } 19672 19673 // FIXME: ensure no other carets have a reference to it 19674 } 19675 19676 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 19677 TextIdentifyResult identify(int x, int y, bool exact = false) { 19678 TextIdentifyResult inexactMatch; 19679 foreach(block; blocks) { 19680 foreach(part; block.parts) { 19681 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 19682 19683 // FIXME binary search 19684 int tidx; 19685 int lastX; 19686 foreach_reverse(idxo, lx; part.letterXs) { 19687 int idx = cast(int) idxo; 19688 if(lx <= x) { 19689 if(lastX && lastX - x < x - lx) 19690 tidx = idx + 1; 19691 else 19692 tidx = idx; 19693 break; 19694 } 19695 lastX = lx; 19696 } 19697 19698 return TextIdentifyResult(part, tidx).fixupNewline; 19699 } else if(!exact) { 19700 // we're not in the box, but are we on the same line? 19701 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 19702 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 19703 } 19704 } 19705 } 19706 19707 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 19708 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 19709 19710 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 19711 } 19712 19713 void moveCaretToPixelCoordinates(int x, int y) { 19714 auto result = identify(x, y); 19715 caret.inlineElement = result.element; 19716 caret.offset = result.offset; 19717 } 19718 19719 void selectToPixelCoordinates(int x, int y) { 19720 auto result = identify(x, y); 19721 19722 if(y < caretLastDrawnY1) { 19723 // on a previous line, carat is selectionEnd 19724 selectionEnd = caret; 19725 19726 selectionStart = Caret(this, result.element, result.offset); 19727 } else if(y > caretLastDrawnY2) { 19728 // on a later line 19729 selectionStart = caret; 19730 19731 selectionEnd = Caret(this, result.element, result.offset); 19732 } else { 19733 // on the same line... 19734 if(x <= caretLastDrawnX) { 19735 selectionEnd = caret; 19736 selectionStart = Caret(this, result.element, result.offset); 19737 } else { 19738 selectionStart = caret; 19739 selectionEnd = Caret(this, result.element, result.offset); 19740 } 19741 19742 } 19743 } 19744 19745 19746 /// Call this if the inputs change. It will reflow everything 19747 void redoLayout(ScreenPainter painter) { 19748 //painter.setClipRectangle(boundingBox); 19749 auto pos = Point(boundingBox.left, boundingBox.top); 19750 19751 int lastHeight; 19752 void nl() { 19753 pos.x = boundingBox.left; 19754 pos.y += lastHeight; 19755 } 19756 foreach(block; blocks) { 19757 nl(); 19758 foreach(part; block.parts) { 19759 part.letterXs = null; 19760 19761 auto size = painter.textSize(part.text); 19762 version(Windows) 19763 if(part.text.length && part.text[$-1] == '\n') 19764 size.height /= 2; // windows counts the new line at the end, but we don't want that 19765 19766 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 19767 19768 foreach(idx, char c; part.text) { 19769 // FIXME: unicode 19770 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 19771 } 19772 19773 pos.x += size.width; 19774 if(pos.x >= boundingBox.right) { 19775 pos.y += size.height; 19776 pos.x = boundingBox.left; 19777 lastHeight = 0; 19778 } else { 19779 lastHeight = size.height; 19780 } 19781 19782 if(part.text.length && part.text[$-1] == '\n') 19783 nl(); 19784 } 19785 } 19786 19787 layoutInvalidated = false; 19788 } 19789 19790 bool layoutInvalidated = true; 19791 void invalidateLayout() { 19792 layoutInvalidated = true; 19793 } 19794 19795 // FIXME: caret can remain sometimes when inserting 19796 // FIXME: inserting at the beginning once you already have something can eff it up. 19797 void drawInto(ScreenPainter painter, bool focused = false) { 19798 if(layoutInvalidated) 19799 redoLayout(painter); 19800 foreach(block; blocks) { 19801 foreach(part; block.parts) { 19802 painter.outlineColor = part.color; 19803 painter.fillColor = part.backgroundColor; 19804 19805 auto pos = part.boundingBox.upperLeft; 19806 auto size = part.boundingBox.size; 19807 19808 painter.drawText(pos, part.text); 19809 if(part.styles & TextFormat.underline) 19810 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 19811 if(part.styles & TextFormat.strikethrough) 19812 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 19813 } 19814 } 19815 19816 // on every redraw, I will force the caret to be 19817 // redrawn too, in order to eliminate perceived lag 19818 // when moving around with the mouse. 19819 eraseCaret(painter); 19820 19821 if(focused) { 19822 highlightSelection(painter); 19823 drawCaret(painter); 19824 } 19825 } 19826 19827 Color selectionXorColor = Color(255, 255, 127); 19828 19829 void highlightSelection(ScreenPainter painter) { 19830 if(selectionStart is selectionEnd) 19831 return; // no selection 19832 19833 if(selectionStart.inlineElement is null) return; 19834 if(selectionEnd.inlineElement is null) return; 19835 19836 assert(selectionStart.inlineElement !is null); 19837 assert(selectionEnd.inlineElement !is null); 19838 19839 painter.rasterOp = RasterOp.xor; 19840 painter.outlineColor = Color.transparent; 19841 painter.fillColor = selectionXorColor; 19842 19843 auto at = selectionStart.inlineElement; 19844 auto atOffset = selectionStart.offset; 19845 bool done; 19846 while(at) { 19847 auto box = at.boundingBox; 19848 if(atOffset < at.letterXs.length) 19849 box.left = at.letterXs[atOffset]; 19850 19851 if(at is selectionEnd.inlineElement) { 19852 if(selectionEnd.offset < at.letterXs.length) 19853 box.right = at.letterXs[selectionEnd.offset]; 19854 done = true; 19855 } 19856 19857 painter.drawRectangle(box.upperLeft, box.width, box.height); 19858 19859 if(done) 19860 break; 19861 19862 at = at.getNextInlineElement(); 19863 atOffset = 0; 19864 } 19865 } 19866 19867 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 19868 bool caretShowingOnScreen = false; 19869 void drawCaret(ScreenPainter painter) { 19870 //painter.setClipRectangle(boundingBox); 19871 int x, y1, y2; 19872 if(caret.inlineElement is null) { 19873 x = boundingBox.left; 19874 y1 = boundingBox.top + 2; 19875 y2 = boundingBox.top + painter.fontHeight; 19876 } else { 19877 x = caret.inlineElement.xOfIndex(caret.offset); 19878 y1 = caret.inlineElement.boundingBox.top + 2; 19879 y2 = caret.inlineElement.boundingBox.bottom - 2; 19880 } 19881 19882 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 19883 eraseCaret(painter); 19884 19885 painter.pen = Pen(Color.white, 1); 19886 painter.rasterOp = RasterOp.xor; 19887 painter.drawLine( 19888 Point(x, y1), 19889 Point(x, y2) 19890 ); 19891 painter.rasterOp = RasterOp.normal; 19892 caretShowingOnScreen = !caretShowingOnScreen; 19893 19894 if(caretShowingOnScreen) { 19895 caretLastDrawnX = x; 19896 caretLastDrawnY1 = y1; 19897 caretLastDrawnY2 = y2; 19898 } 19899 } 19900 19901 Rectangle caretBoundingBox() { 19902 int x, y1, y2; 19903 if(caret.inlineElement is null) { 19904 x = boundingBox.left; 19905 y1 = boundingBox.top + 2; 19906 y2 = boundingBox.top + 16; 19907 } else { 19908 x = caret.inlineElement.xOfIndex(caret.offset); 19909 y1 = caret.inlineElement.boundingBox.top + 2; 19910 y2 = caret.inlineElement.boundingBox.bottom - 2; 19911 } 19912 19913 return Rectangle(x, y1, x + 1, y2); 19914 } 19915 19916 void eraseCaret(ScreenPainter painter) { 19917 //painter.setClipRectangle(boundingBox); 19918 if(!caretShowingOnScreen) return; 19919 painter.pen = Pen(Color.white, 1); 19920 painter.rasterOp = RasterOp.xor; 19921 painter.drawLine( 19922 Point(caretLastDrawnX, caretLastDrawnY1), 19923 Point(caretLastDrawnX, caretLastDrawnY2) 19924 ); 19925 19926 caretShowingOnScreen = false; 19927 painter.rasterOp = RasterOp.normal; 19928 } 19929 19930 /// Caret movement api 19931 /// These should give the user a logical result based on what they see on screen... 19932 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 19933 void moveUp() { 19934 if(caret.inlineElement is null) return; 19935 auto x = caret.inlineElement.xOfIndex(caret.offset); 19936 auto y = caret.inlineElement.boundingBox.top + 2; 19937 19938 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 19939 if(y < 0) 19940 return; 19941 19942 auto i = identify(x, y); 19943 19944 if(i.element) { 19945 caret.inlineElement = i.element; 19946 caret.offset = i.offset; 19947 } 19948 } 19949 void moveDown() { 19950 if(caret.inlineElement is null) return; 19951 auto x = caret.inlineElement.xOfIndex(caret.offset); 19952 auto y = caret.inlineElement.boundingBox.bottom - 2; 19953 19954 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 19955 19956 auto i = identify(x, y); 19957 if(i.element) { 19958 caret.inlineElement = i.element; 19959 caret.offset = i.offset; 19960 } 19961 } 19962 void moveLeft() { 19963 if(caret.inlineElement is null) return; 19964 if(caret.offset) 19965 caret.offset--; 19966 else { 19967 auto p = caret.inlineElement.getPreviousInlineElement(); 19968 if(p) { 19969 caret.inlineElement = p; 19970 if(p.text.length && p.text[$-1] == '\n') 19971 caret.offset = cast(int) p.text.length - 1; 19972 else 19973 caret.offset = cast(int) p.text.length; 19974 } 19975 } 19976 } 19977 void moveRight() { 19978 if(caret.inlineElement is null) return; 19979 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 19980 caret.offset++; 19981 } else { 19982 auto p = caret.inlineElement.getNextInlineElement(); 19983 if(p) { 19984 caret.inlineElement = p; 19985 caret.offset = 0; 19986 } 19987 } 19988 } 19989 void moveHome() { 19990 if(caret.inlineElement is null) return; 19991 auto x = 0; 19992 auto y = caret.inlineElement.boundingBox.top + 2; 19993 19994 auto i = identify(x, y); 19995 19996 if(i.element) { 19997 caret.inlineElement = i.element; 19998 caret.offset = i.offset; 19999 } 20000 } 20001 void moveEnd() { 20002 if(caret.inlineElement is null) return; 20003 auto x = int.max; 20004 auto y = caret.inlineElement.boundingBox.top + 2; 20005 20006 auto i = identify(x, y); 20007 20008 if(i.element) { 20009 caret.inlineElement = i.element; 20010 caret.offset = i.offset; 20011 } 20012 20013 } 20014 void movePageUp(ref Caret caret) {} 20015 void movePageDown(ref Caret caret) {} 20016 20017 void moveDocumentStart(ref Caret caret) { 20018 if(blocks.length && blocks[0].parts.length) 20019 caret = Caret(this, blocks[0].parts[0], 0); 20020 else 20021 caret = Caret.init; 20022 } 20023 20024 void moveDocumentEnd(ref Caret caret) { 20025 if(blocks.length) { 20026 auto parts = blocks[$-1].parts; 20027 if(parts.length) { 20028 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 20029 } else { 20030 caret = Caret.init; 20031 } 20032 } else 20033 caret = Caret.init; 20034 } 20035 20036 void deleteSelection() { 20037 if(selectionStart is selectionEnd) 20038 return; 20039 20040 if(selectionStart.inlineElement is null) return; 20041 if(selectionEnd.inlineElement is null) return; 20042 20043 assert(selectionStart.inlineElement !is null); 20044 assert(selectionEnd.inlineElement !is null); 20045 20046 auto at = selectionStart.inlineElement; 20047 20048 if(selectionEnd.inlineElement is at) { 20049 // same element, need to chop out 20050 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 20051 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 20052 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 20053 } else { 20054 // different elements, we can do it with slicing 20055 at.text = at.text[0 .. selectionStart.offset]; 20056 if(selectionStart.offset < at.letterXs.length) 20057 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 20058 20059 at = at.getNextInlineElement(); 20060 20061 while(at) { 20062 if(at is selectionEnd.inlineElement) { 20063 at.text = at.text[selectionEnd.offset .. $]; 20064 if(selectionEnd.offset < at.letterXs.length) 20065 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 20066 selectionEnd.offset = 0; 20067 break; 20068 } else { 20069 auto cfd = at; 20070 cfd.text = null; // delete the whole thing 20071 20072 at = at.getNextInlineElement(); 20073 20074 if(cfd.text.length == 0) { 20075 // and remove cfd 20076 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 20077 if(cfd.containingBlock.parts[a] is cfd) { 20078 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 20079 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 20080 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 20081 20082 } 20083 } 20084 } 20085 } 20086 } 20087 } 20088 20089 caret = selectionEnd; 20090 selectNone(); 20091 20092 invalidateLayout(); 20093 20094 } 20095 20096 /// Plain text editing api. These work at the current caret inside the selected inline element. 20097 void insert(in char[] text) { 20098 foreach(dchar ch; text) 20099 insert(ch); 20100 } 20101 /// ditto 20102 void insert(dchar ch) { 20103 20104 bool selectionDeleted = false; 20105 if(selectionStart !is selectionEnd) { 20106 deleteSelection(); 20107 selectionDeleted = true; 20108 } 20109 20110 if(ch == 127) { 20111 delete_(); 20112 return; 20113 } 20114 if(ch == 8) { 20115 if(!selectionDeleted) 20116 backspace(); 20117 return; 20118 } 20119 20120 invalidateLayout(); 20121 20122 if(ch == 13) ch = 10; 20123 auto e = caret.inlineElement; 20124 if(e is null) { 20125 addText("" ~ cast(char) ch) ; // FIXME 20126 return; 20127 } 20128 20129 if(caret.offset == e.text.length) { 20130 e.text ~= cast(char) ch; // FIXME 20131 caret.offset++; 20132 if(ch == 10) { 20133 auto c = caret.inlineElement.clone; 20134 c.text = null; 20135 c.letterXs = null; 20136 insertPartAfter(c,e); 20137 caret = Caret(this, c, 0); 20138 } 20139 } else { 20140 // FIXME cast char sucks 20141 if(ch == 10) { 20142 auto c = caret.inlineElement.clone; 20143 c.text = e.text[caret.offset .. $]; 20144 if(caret.offset < c.letterXs.length) 20145 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 20146 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 20147 if(caret.offset <= e.letterXs.length) { 20148 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 20149 } 20150 insertPartAfter(c,e); 20151 caret = Caret(this, c, 0); 20152 } else { 20153 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 20154 caret.offset++; 20155 } 20156 } 20157 } 20158 20159 void insertPartAfter(InlineElement what, InlineElement where) { 20160 foreach(idx, p; where.containingBlock.parts) { 20161 if(p is where) { 20162 if(idx + 1 == where.containingBlock.parts.length) 20163 where.containingBlock.parts ~= what; 20164 else 20165 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 20166 return; 20167 } 20168 } 20169 } 20170 20171 void cleanupStructures() { 20172 for(size_t i = 0; i < blocks.length; i++) { 20173 auto block = blocks[i]; 20174 for(size_t a = 0; a < block.parts.length; a++) { 20175 auto part = block.parts[a]; 20176 if(part.text.length == 0) { 20177 for(size_t b = a; b < block.parts.length - 1; b++) 20178 block.parts[b] = block.parts[b+1]; 20179 block.parts = block.parts[0 .. $-1]; 20180 } 20181 } 20182 if(block.parts.length == 0) { 20183 for(size_t a = i; a < blocks.length - 1; a++) 20184 blocks[a] = blocks[a+1]; 20185 blocks = blocks[0 .. $-1]; 20186 } 20187 } 20188 } 20189 20190 void backspace() { 20191 try_again: 20192 auto e = caret.inlineElement; 20193 if(e is null) 20194 return; 20195 if(caret.offset == 0) { 20196 auto prev = e.getPreviousInlineElement(); 20197 if(prev is null) 20198 return; 20199 auto newOffset = cast(int) prev.text.length; 20200 tryMerge(prev, e); 20201 caret.inlineElement = prev; 20202 caret.offset = prev is null ? 0 : newOffset; 20203 20204 goto try_again; 20205 } else if(caret.offset == e.text.length) { 20206 e.text = e.text[0 .. $-1]; 20207 caret.offset--; 20208 } else { 20209 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 20210 caret.offset--; 20211 } 20212 //cleanupStructures(); 20213 20214 invalidateLayout(); 20215 } 20216 void delete_() { 20217 if(selectionStart !is selectionEnd) 20218 deleteSelection(); 20219 else { 20220 auto before = caret; 20221 moveRight(); 20222 if(caret != before) { 20223 backspace(); 20224 } 20225 } 20226 20227 invalidateLayout(); 20228 } 20229 void overstrike() {} 20230 20231 /// Selection API. See also: caret movement. 20232 void selectAll() { 20233 moveDocumentStart(selectionStart); 20234 moveDocumentEnd(selectionEnd); 20235 } 20236 bool selectNone() { 20237 if(selectionStart != selectionEnd) { 20238 selectionStart = selectionEnd = Caret.init; 20239 return true; 20240 } 20241 return false; 20242 } 20243 20244 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 20245 /// They will modify the current selection if there is one and will splice one in if needed. 20246 void changeAttributes() {} 20247 20248 20249 /// Text search api. They manipulate the selection and/or caret. 20250 void findText(string text) {} 20251 void findIndex(size_t textIndex) {} 20252 20253 // sample event handlers 20254 20255 void handleEvent(KeyEvent event) { 20256 //if(event.type == KeyEvent.Type.KeyPressed) { 20257 20258 //} 20259 } 20260 20261 void handleEvent(dchar ch) { 20262 20263 } 20264 20265 void handleEvent(MouseEvent event) { 20266 20267 } 20268 20269 bool contentEditable; // can it be edited? 20270 bool contentCaretable; // is there a caret/cursor that moves around in there? 20271 bool contentSelectable; // selectable? 20272 20273 Caret caret; 20274 Caret selectionStart; 20275 Caret selectionEnd; 20276 20277 bool insertMode; 20278 } 20279 20280 struct Caret { 20281 TextLayout layout; 20282 InlineElement inlineElement; 20283 int offset; 20284 } 20285 20286 enum TextFormat : ushort { 20287 // decorations 20288 underline = 1, 20289 strikethrough = 2, 20290 20291 // font selectors 20292 20293 bold = 0x4000 | 1, // weight 700 20294 light = 0x4000 | 2, // weight 300 20295 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 20296 // bold | light is really invalid but should give weight 500 20297 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 20298 20299 italic = 0x4000 | 8, 20300 smallcaps = 0x4000 | 16, 20301 } 20302 20303 void* findFont(string family, int weight, TextFormat formats) { 20304 return null; 20305 } 20306 20307 } 20308 20309 /++ 20310 $(PITFALL This is not yet stable and may break in future versions without notice.) 20311 20312 History: 20313 Added February 19, 2021 20314 +/ 20315 /// Group: drag_and_drop 20316 interface DropHandler { 20317 /++ 20318 Called when the drag enters the handler's area. 20319 +/ 20320 DragAndDropAction dragEnter(DropPackage*); 20321 /++ 20322 Called when the drag leaves the handler's area or is 20323 cancelled. You should free your resources when this is called. 20324 +/ 20325 void dragLeave(); 20326 /++ 20327 Called continually as the drag moves over the handler's area. 20328 20329 Returns: feedback to the dragger 20330 +/ 20331 DropParameters dragOver(Point pt); 20332 /++ 20333 The user dropped the data and you should process it now. You can 20334 access the data through the given [DropPackage]. 20335 +/ 20336 void drop(scope DropPackage*); 20337 /++ 20338 Called when the drop is complete. You should free whatever temporary 20339 resources you were using. It is often reasonable to simply forward 20340 this call to [dragLeave]. 20341 +/ 20342 void finish(); 20343 20344 /++ 20345 Parameters returned by [DropHandler.drop]. 20346 +/ 20347 static struct DropParameters { 20348 /++ 20349 Acceptable action over this area. 20350 +/ 20351 DragAndDropAction action; 20352 /++ 20353 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 20354 20355 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 20356 +/ 20357 Rectangle consistentWithin; 20358 } 20359 } 20360 20361 /++ 20362 History: 20363 Added February 19, 2021 20364 +/ 20365 /// Group: drag_and_drop 20366 enum DragAndDropAction { 20367 none = 0, 20368 copy, 20369 move, 20370 link, 20371 ask, 20372 custom 20373 } 20374 20375 /++ 20376 An opaque structure representing dropped data. It contains 20377 private, platform-specific data that your `drop` function 20378 should simply forward. 20379 20380 $(PITFALL This is not yet stable and may break in future versions without notice.) 20381 20382 History: 20383 Added February 19, 2021 20384 +/ 20385 /// Group: drag_and_drop 20386 struct DropPackage { 20387 /++ 20388 Lists the available formats as magic numbers. You should compare these 20389 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 20390 understand the passed data. 20391 +/ 20392 DraggableData.FormatId[] availableFormats() { 20393 version(X11) { 20394 return xFormats; 20395 } else version(Windows) { 20396 if(pDataObj is null) 20397 return null; 20398 20399 typeof(return) ret; 20400 20401 IEnumFORMATETC ef; 20402 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 20403 FORMATETC fmt; 20404 ULONG fetched; 20405 while(ef.Next(1, &fmt, &fetched) == S_OK) { 20406 if(fetched == 0) 20407 break; 20408 20409 if(fmt.lindex != -1) 20410 continue; 20411 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 20412 continue; 20413 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 20414 continue; 20415 20416 ret ~= fmt.cfFormat; 20417 } 20418 } 20419 20420 return ret; 20421 } 20422 } 20423 20424 /++ 20425 Gets data from the drop and optionally accepts it. 20426 20427 Returns: 20428 void because the data is fed asynchronously through the `dg` parameter. 20429 20430 Params: 20431 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. 20432 20433 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. 20434 20435 Calling `getData` again after accepting a drop is not permitted. 20436 20437 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 20438 20439 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. 20440 20441 Throws: 20442 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 20443 20444 History: 20445 Included in first release of [DropPackage]. 20446 +/ 20447 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 20448 version(X11) { 20449 20450 auto display = XDisplayConnection.get(); 20451 auto selectionAtom = GetAtom!"XdndSelection"(display); 20452 auto best = format; 20453 20454 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 20455 20456 XDisplay* display; 20457 Atom selectionAtom; 20458 DraggableData.FormatId best; 20459 DraggableData.FormatId format; 20460 void delegate(scope ubyte[] data) dg; 20461 DragAndDropAction acceptedAction; 20462 Window sourceWindow; 20463 SimpleWindow win; 20464 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 20465 this.display = display; 20466 this.win = win; 20467 this.sourceWindow = sourceWindow; 20468 this.format = format; 20469 this.selectionAtom = selectionAtom; 20470 this.best = best; 20471 this.dg = dg; 20472 this.acceptedAction = acceptedAction; 20473 } 20474 20475 20476 mixin X11GetSelectionHandler_Basics; 20477 20478 void handleData(Atom target, in ubyte[] data) { 20479 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 20480 20481 dg(cast(ubyte[]) data); 20482 20483 if(acceptedAction != DragAndDropAction.none) { 20484 auto display = XDisplayConnection.get; 20485 20486 XClientMessageEvent xclient; 20487 20488 xclient.type = EventType.ClientMessage; 20489 xclient.window = sourceWindow; 20490 xclient.message_type = GetAtom!"XdndFinished"(display); 20491 xclient.format = 32; 20492 xclient.data.l[0] = win.impl.window; 20493 xclient.data.l[1] = 1; // drop successful 20494 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 20495 20496 XSendEvent( 20497 display, 20498 sourceWindow, 20499 false, 20500 EventMask.NoEventMask, 20501 cast(XEvent*) &xclient 20502 ); 20503 20504 XFlush(display); 20505 } 20506 } 20507 20508 Atom findBestFormat(Atom[] answer) { 20509 Atom best = None; 20510 foreach(option; answer) { 20511 if(option == format) { 20512 best = option; 20513 break; 20514 } 20515 /* 20516 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 20517 best = option; 20518 break; 20519 } else if(option == XA_STRING) { 20520 best = option; 20521 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 20522 best = option; 20523 } 20524 */ 20525 } 20526 return best; 20527 } 20528 } 20529 20530 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 20531 20532 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 20533 20534 } else version(Windows) { 20535 20536 // clean up like DragLeave 20537 // pass effect back up 20538 20539 FORMATETC t; 20540 assert(format >= 0 && format <= ushort.max); 20541 t.cfFormat = cast(ushort) format; 20542 t.lindex = -1; 20543 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 20544 t.tymed = TYMED.TYMED_HGLOBAL; 20545 20546 STGMEDIUM m; 20547 20548 if(pDataObj.GetData(&t, &m) != S_OK) { 20549 // fail 20550 } else { 20551 // succeed, take the data and clean up 20552 20553 // FIXME: ensure it is legit HGLOBAL 20554 auto handle = m.hGlobal; 20555 20556 if(handle) { 20557 auto sz = GlobalSize(handle); 20558 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 20559 scope(exit) GlobalUnlock(handle); 20560 scope(exit) GlobalFree(handle); 20561 20562 auto data = ptr[0 .. sz]; 20563 20564 dg(data); 20565 } 20566 } 20567 } 20568 } 20569 } 20570 20571 private: 20572 20573 version(X11) { 20574 SimpleWindow win; 20575 Window sourceWindow; 20576 Time dataTimestamp; 20577 20578 Atom[] xFormats; 20579 } 20580 version(Windows) { 20581 IDataObject pDataObj; 20582 } 20583 } 20584 20585 /++ 20586 A generic helper base class for making a drop handler with a preference list of custom types. 20587 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 20588 droppers too. 20589 20590 It assumes the whole window it used, but you can subclass to change that. 20591 20592 $(PITFALL This is not yet stable and may break in future versions without notice.) 20593 20594 History: 20595 Added February 19, 2021 20596 +/ 20597 /// Group: drag_and_drop 20598 class GenericDropHandlerBase : DropHandler { 20599 // no fancy state here so no need to do anything here 20600 void finish() { } 20601 void dragLeave() { } 20602 20603 private DragAndDropAction acceptedAction; 20604 private DraggableData.FormatId acceptedFormat; 20605 private void delegate(scope ubyte[]) acceptedHandler; 20606 20607 struct FormatHandler { 20608 DraggableData.FormatId format; 20609 void delegate(scope ubyte[]) handler; 20610 } 20611 20612 protected abstract FormatHandler[] formatHandlers(); 20613 20614 DragAndDropAction dragEnter(DropPackage* pkg) { 20615 debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 20616 foreach(fmt; formatHandlers()) 20617 foreach(f; pkg.availableFormats()) 20618 if(f == fmt.format) { 20619 acceptedFormat = f; 20620 acceptedHandler = fmt.handler; 20621 return acceptedAction = DragAndDropAction.copy; 20622 } 20623 return acceptedAction = DragAndDropAction.none; 20624 } 20625 DropParameters dragOver(Point pt) { 20626 return DropParameters(acceptedAction); 20627 } 20628 20629 void drop(scope DropPackage* dropPackage) { 20630 if(!acceptedFormat || acceptedHandler is null) { 20631 debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 20632 return; // prolly shouldn't happen anyway... 20633 } 20634 20635 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 20636 } 20637 } 20638 20639 /++ 20640 A simple handler for making your window accept drops of plain text. 20641 20642 $(PITFALL This is not yet stable and may break in future versions without notice.) 20643 20644 History: 20645 Added February 22, 2021 20646 +/ 20647 /// Group: drag_and_drop 20648 class TextDropHandler : GenericDropHandlerBase { 20649 private void delegate(in char[] text) dg; 20650 20651 /++ 20652 20653 +/ 20654 this(void delegate(in char[] text) dg) { 20655 this.dg = dg; 20656 } 20657 20658 protected override FormatHandler[] formatHandlers() { 20659 version(X11) 20660 return [ 20661 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 20662 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 20663 ]; 20664 else version(Windows) 20665 return [ 20666 FormatHandler(CF_UNICODETEXT, &translator), 20667 ]; 20668 } 20669 20670 private void translator(scope ubyte[] data) { 20671 version(X11) 20672 dg(cast(char[]) data); 20673 else version(Windows) 20674 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 20675 } 20676 } 20677 20678 /++ 20679 A simple handler for making your window accept drops of files, issued to you as file names. 20680 20681 $(PITFALL This is not yet stable and may break in future versions without notice.) 20682 20683 History: 20684 Added February 22, 2021 20685 +/ 20686 /// Group: drag_and_drop 20687 20688 class FilesDropHandler : GenericDropHandlerBase { 20689 private void delegate(in char[][]) dg; 20690 20691 /++ 20692 20693 +/ 20694 this(void delegate(in char[][] fileNames) dg) { 20695 this.dg = dg; 20696 } 20697 20698 protected override FormatHandler[] formatHandlers() { 20699 version(X11) 20700 return [ 20701 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 20702 ]; 20703 else version(Windows) 20704 return [ 20705 FormatHandler(CF_HDROP, &translator), 20706 ]; 20707 } 20708 20709 private void translator(scope ubyte[] data) { 20710 version(X11) { 20711 char[] listString = cast(char[]) data; 20712 char[][16] buffer; 20713 int count; 20714 char[][] result = buffer[]; 20715 20716 void commit(char[] s) { 20717 if(count == result.length) 20718 result.length += 16; 20719 if(s.length > 7 && s[0 ..7] == "file://") 20720 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 20721 result[count++] = s; 20722 } 20723 20724 size_t last; 20725 foreach(idx, char c; listString) { 20726 if(c == '\n') { 20727 commit(listString[last .. idx - 1]); // a \r 20728 last = idx + 1; // a \n 20729 } 20730 } 20731 20732 if(last < listString.length) { 20733 commit(listString[last .. $]); 20734 } 20735 20736 // FIXME: they are uris now, should I translate it to local file names? 20737 // of course the host name is supposed to be there cuz of X rokking... 20738 20739 dg(result[0 .. count]); 20740 } else version(Windows) { 20741 20742 static struct DROPFILES { 20743 DWORD pFiles; 20744 POINT pt; 20745 BOOL fNC; 20746 BOOL fWide; 20747 } 20748 20749 20750 const(char)[][16] buffer; 20751 int count; 20752 const(char)[][] result = buffer[]; 20753 size_t last; 20754 20755 void commitA(in char[] stuff) { 20756 if(count == result.length) 20757 result.length += 16; 20758 result[count++] = stuff; 20759 } 20760 20761 void commitW(in wchar[] stuff) { 20762 commitA(makeUtf8StringFromWindowsString(stuff)); 20763 } 20764 20765 void magic(T)(T chars) { 20766 size_t idx; 20767 while(chars[idx]) { 20768 last = idx; 20769 while(chars[idx]) { 20770 idx++; 20771 } 20772 static if(is(T == char*)) 20773 commitA(chars[last .. idx]); 20774 else 20775 commitW(chars[last .. idx]); 20776 idx++; 20777 } 20778 } 20779 20780 auto df = cast(DROPFILES*) data.ptr; 20781 if(df.fWide) { 20782 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 20783 magic(chars); 20784 } else { 20785 char* chars = cast(char*) (data.ptr + df.pFiles); 20786 magic(chars); 20787 } 20788 dg(result[0 .. count]); 20789 } 20790 } 20791 } 20792 20793 /++ 20794 Interface to describe data being dragged. See also [draggable] helper function. 20795 20796 $(PITFALL This is not yet stable and may break in future versions without notice.) 20797 20798 History: 20799 Added February 19, 2021 20800 +/ 20801 interface DraggableData { 20802 version(X11) 20803 alias FormatId = Atom; 20804 else 20805 alias FormatId = uint; 20806 /++ 20807 Gets the platform-specific FormatId associated with the given named format. 20808 20809 This may be a MIME type, but may also be other various strings defined by the 20810 programs you want to interoperate with. 20811 20812 FIXME: sdpy needs to offer data adapter things that look for compatible formats 20813 and convert it to some particular type for you. 20814 +/ 20815 static FormatId getFormatId(string name)() { 20816 version(X11) 20817 return GetAtom!name(XDisplayConnection.get); 20818 else version(Windows) { 20819 static UINT cache; 20820 if(!cache) 20821 cache = RegisterClipboardFormatA(name); 20822 return cache; 20823 } else 20824 throw new NotYetImplementedException(); 20825 } 20826 20827 /++ 20828 Looks up a string to represent the name for the given format, if there is one. 20829 20830 You should avoid using this function because it is slow. It is provided more for 20831 debugging than for primary use. 20832 +/ 20833 static string getFormatName(FormatId format) { 20834 version(X11) { 20835 if(format == 0) 20836 return "None"; 20837 else 20838 return getAtomName(format, XDisplayConnection.get); 20839 } else version(Windows) { 20840 switch(format) { 20841 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 20842 case CF_DIBV5: return "CF_DIBV5"; 20843 case CF_RIFF: return "CF_RIFF"; 20844 case CF_WAVE: return "CF_WAVE"; 20845 case CF_HDROP: return "CF_HDROP"; 20846 default: 20847 char[1024] name; 20848 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 20849 return name[0 .. count].idup; 20850 } 20851 } 20852 } 20853 20854 FormatId[] availableFormats(); 20855 // Return the slice of data you filled, empty slice if done. 20856 // this is to support the incremental thing 20857 ubyte[] getData(FormatId format, return scope ubyte[] data); 20858 20859 size_t dataLength(FormatId format); 20860 } 20861 20862 /++ 20863 $(PITFALL This is not yet stable and may break in future versions without notice.) 20864 20865 History: 20866 Added February 19, 2021 20867 +/ 20868 DraggableData draggable(string s) { 20869 version(X11) 20870 return new class X11SetSelectionHandler_Text, DraggableData { 20871 this() { 20872 super(s); 20873 } 20874 20875 override FormatId[] availableFormats() { 20876 return X11SetSelectionHandler_Text.availableFormats(); 20877 } 20878 20879 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 20880 return X11SetSelectionHandler_Text.getData(format, data); 20881 } 20882 20883 size_t dataLength(FormatId format) { 20884 return s.length; 20885 } 20886 }; 20887 version(Windows) 20888 return new class DraggableData { 20889 FormatId[] availableFormats() { 20890 return [CF_UNICODETEXT]; 20891 } 20892 20893 ubyte[] getData(FormatId format, return scope ubyte[] data) { 20894 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 20895 } 20896 20897 size_t dataLength(FormatId format) { 20898 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 20899 } 20900 }; 20901 } 20902 20903 /++ 20904 $(PITFALL This is not yet stable and may break in future versions without notice.) 20905 20906 History: 20907 Added February 19, 2021 20908 +/ 20909 /// Group: drag_and_drop 20910 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 20911 in { 20912 assert(window !is null); 20913 assert(handler !is null); 20914 } 20915 do 20916 { 20917 version(X11) { 20918 auto sh = cast(X11SetSelectionHandler) handler; 20919 if(sh is null) { 20920 // gotta make my own adapter. 20921 sh = new class X11SetSelectionHandler { 20922 mixin X11SetSelectionHandler_Basics; 20923 20924 Atom[] availableFormats() { return handler.availableFormats(); } 20925 ubyte[] getData(Atom format, return scope ubyte[] data) { 20926 return handler.getData(format, data); 20927 } 20928 20929 // since the drop selection is only ever used once it isn't important 20930 // to reset it. 20931 void done() {} 20932 }; 20933 } 20934 return doDragDropX11(window, sh, action); 20935 } else version(Windows) { 20936 return doDragDropWindows(window, handler, action); 20937 } else throw new NotYetImplementedException(); 20938 } 20939 20940 version(Windows) 20941 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 20942 IDataObject obj = new class IDataObject { 20943 ULONG refCount; 20944 ULONG AddRef() { 20945 return ++refCount; 20946 } 20947 ULONG Release() { 20948 return --refCount; 20949 } 20950 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 20951 if (IID_IUnknown == *riid) { 20952 *ppv = cast(void*) cast(IUnknown) this; 20953 } 20954 else if (IID_IDataObject == *riid) { 20955 *ppv = cast(void*) cast(IDataObject) this; 20956 } 20957 else { 20958 *ppv = null; 20959 return E_NOINTERFACE; 20960 } 20961 20962 AddRef(); 20963 return NOERROR; 20964 } 20965 20966 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 20967 // import std.stdio; writeln("Advise"); 20968 return E_NOTIMPL; 20969 } 20970 HRESULT DUnadvise(DWORD dwConnection) { 20971 return E_NOTIMPL; 20972 } 20973 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 20974 // import std.stdio; writeln("EnumDAdvise"); 20975 return OLE_E_ADVISENOTSUPPORTED; 20976 } 20977 // tell what formats it supports 20978 20979 FORMATETC[] types; 20980 this() { 20981 FORMATETC t; 20982 foreach(ty; handler.availableFormats()) { 20983 assert(ty <= ushort.max && ty >= 0); 20984 t.cfFormat = cast(ushort) ty; 20985 t.lindex = -1; 20986 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 20987 t.tymed = TYMED.TYMED_HGLOBAL; 20988 } 20989 types ~= t; 20990 } 20991 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 20992 if(dwDirection == DATADIR.DATADIR_GET) { 20993 *ppenumFormatEtc = new class IEnumFORMATETC { 20994 ULONG refCount; 20995 ULONG AddRef() { 20996 return ++refCount; 20997 } 20998 ULONG Release() { 20999 return --refCount; 21000 } 21001 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21002 if (IID_IUnknown == *riid) { 21003 *ppv = cast(void*) cast(IUnknown) this; 21004 } 21005 else if (IID_IEnumFORMATETC == *riid) { 21006 *ppv = cast(void*) cast(IEnumFORMATETC) this; 21007 } 21008 else { 21009 *ppv = null; 21010 return E_NOINTERFACE; 21011 } 21012 21013 AddRef(); 21014 return NOERROR; 21015 } 21016 21017 21018 int pos; 21019 this() { 21020 pos = 0; 21021 } 21022 21023 HRESULT Clone(IEnumFORMATETC* ppenum) { 21024 // import std.stdio; writeln("clone"); 21025 return E_NOTIMPL; // FIXME 21026 } 21027 21028 // Caller is responsible for freeing memory 21029 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 21030 // fetched may be null if celt is one 21031 if(celt != 1) 21032 return E_NOTIMPL; // FIXME 21033 21034 if(celt + pos > types.length) 21035 return S_FALSE; 21036 21037 *rgelt = types[pos++]; 21038 21039 if(pceltFetched !is null) 21040 *pceltFetched = 1; 21041 21042 // import std.stdio; writeln("ok celt ", celt); 21043 return S_OK; 21044 } 21045 21046 HRESULT Reset() { 21047 pos = 0; 21048 return S_OK; 21049 } 21050 21051 HRESULT Skip(ULONG celt) { 21052 if(celt + pos <= types.length) { 21053 pos += celt; 21054 return S_OK; 21055 } 21056 return S_FALSE; 21057 } 21058 }; 21059 21060 return S_OK; 21061 } else 21062 return E_NOTIMPL; 21063 } 21064 // given a format, return the format you'd prefer to use cuz it is identical 21065 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 21066 // FIXME: prolly could be better but meh 21067 // import std.stdio; writeln("gcf: ", *pformatectIn); 21068 *pformatetcOut = *pformatectIn; 21069 return S_OK; 21070 } 21071 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21072 foreach(ty; types) { 21073 if(ty == *pformatetcIn) { 21074 auto format = ty.cfFormat; 21075 // import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty); 21076 STGMEDIUM medium; 21077 medium.tymed = TYMED.TYMED_HGLOBAL; 21078 21079 auto sz = handler.dataLength(format); 21080 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 21081 if(handle is null) throw new Exception("GlobalAlloc"); 21082 if(auto data = cast(wchar*) GlobalLock(handle)) { 21083 auto slice = data[0 .. sz]; 21084 scope(exit) 21085 GlobalUnlock(handle); 21086 21087 handler.getData(format, cast(ubyte[]) slice[]); 21088 } 21089 21090 21091 medium.hGlobal = handle; // FIXME 21092 *pmedium = medium; 21093 return S_OK; 21094 } 21095 } 21096 return DV_E_FORMATETC; 21097 } 21098 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21099 // import std.stdio; writeln("GDH: ", *pformatetcIn); 21100 return E_NOTIMPL; // FIXME 21101 } 21102 HRESULT QueryGetData(FORMATETC* pformatetc) { 21103 auto search = *pformatetc; 21104 search.tymed &= TYMED.TYMED_HGLOBAL; 21105 foreach(ty; types) 21106 if(ty == search) { 21107 // import std.stdio; writeln("QueryGetData ", search, " ", types[0]); 21108 return S_OK; 21109 } 21110 if(pformatetc.cfFormat==CF_UNICODETEXT) { 21111 //import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]); 21112 } 21113 return S_FALSE; 21114 } 21115 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 21116 // import std.stdio; writeln("SetData: "); 21117 return E_NOTIMPL; 21118 } 21119 }; 21120 21121 21122 IDropSource src = new class IDropSource { 21123 ULONG refCount; 21124 ULONG AddRef() { 21125 return ++refCount; 21126 } 21127 ULONG Release() { 21128 return --refCount; 21129 } 21130 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21131 if (IID_IUnknown == *riid) { 21132 *ppv = cast(void*) cast(IUnknown) this; 21133 } 21134 else if (IID_IDropSource == *riid) { 21135 *ppv = cast(void*) cast(IDropSource) this; 21136 } 21137 else { 21138 *ppv = null; 21139 return E_NOINTERFACE; 21140 } 21141 21142 AddRef(); 21143 return NOERROR; 21144 } 21145 21146 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 21147 if(fEscapePressed) 21148 return DRAGDROP_S_CANCEL; 21149 if(!(grfKeyState & MK_LBUTTON)) 21150 return DRAGDROP_S_DROP; 21151 return S_OK; 21152 } 21153 21154 int GiveFeedback(uint dwEffect) { 21155 return DRAGDROP_S_USEDEFAULTCURSORS; 21156 } 21157 }; 21158 21159 DWORD effect; 21160 21161 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 21162 21163 DROPEFFECT de = win32DragAndDropAction(action); 21164 21165 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 21166 // but still prolly a FIXME 21167 21168 auto ret = DoDragDrop(obj, src, de, &effect); 21169 /+ 21170 import std.stdio; 21171 if(ret == DRAGDROP_S_DROP) 21172 writeln("drop ", effect); 21173 else if(ret == DRAGDROP_S_CANCEL) 21174 writeln("cancel"); 21175 else if(ret == S_OK) 21176 writeln("ok"); 21177 else writeln(ret); 21178 +/ 21179 21180 return ret; 21181 } 21182 21183 version(Windows) 21184 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 21185 DROPEFFECT de; 21186 21187 with(DragAndDropAction) 21188 with(DROPEFFECT) 21189 final switch(action) { 21190 case none: de = DROPEFFECT_NONE; break; 21191 case copy: de = DROPEFFECT_COPY; break; 21192 case move: de = DROPEFFECT_MOVE; break; 21193 case link: de = DROPEFFECT_LINK; break; 21194 case ask: throw new Exception("ask not implemented yet"); 21195 case custom: throw new Exception("custom not implemented yet"); 21196 } 21197 21198 return de; 21199 } 21200 21201 21202 /++ 21203 History: 21204 Added February 19, 2021 21205 +/ 21206 /// Group: drag_and_drop 21207 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 21208 version(X11) { 21209 auto display = XDisplayConnection.get; 21210 21211 Atom atom = 5; // right??? 21212 21213 XChangeProperty( 21214 display, 21215 window.impl.window, 21216 GetAtom!"XdndAware"(display), 21217 XA_ATOM, 21218 32 /* bits */, 21219 PropModeReplace, 21220 &atom, 21221 1); 21222 21223 window.dropHandler = handler; 21224 } else version(Windows) { 21225 21226 initDnd(); 21227 21228 auto dropTarget = new class (handler) IDropTarget { 21229 DropHandler handler; 21230 this(DropHandler handler) { 21231 this.handler = handler; 21232 } 21233 ULONG refCount; 21234 ULONG AddRef() { 21235 return ++refCount; 21236 } 21237 ULONG Release() { 21238 return --refCount; 21239 } 21240 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21241 if (IID_IUnknown == *riid) { 21242 *ppv = cast(void*) cast(IUnknown) this; 21243 } 21244 else if (IID_IDropTarget == *riid) { 21245 *ppv = cast(void*) cast(IDropTarget) this; 21246 } 21247 else { 21248 *ppv = null; 21249 return E_NOINTERFACE; 21250 } 21251 21252 AddRef(); 21253 return NOERROR; 21254 } 21255 21256 21257 // /////////////////// 21258 21259 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21260 DropPackage dropPackage = DropPackage(pDataObj); 21261 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 21262 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 21263 } 21264 21265 HRESULT DragLeave() { 21266 handler.dragLeave(); 21267 // release the IDataObject if needed 21268 return S_OK; 21269 } 21270 21271 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21272 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 21273 21274 *pdwEffect = win32DragAndDropAction(res.action); 21275 // same as DragEnter basically 21276 return S_OK; 21277 } 21278 21279 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21280 DropPackage pkg = DropPackage(pDataObj); 21281 handler.drop(&pkg); 21282 21283 return S_OK; 21284 } 21285 }; 21286 // Windows can hold on to the handler and try to call it 21287 // during which time the GC can't see it. so important to 21288 // manually manage this. At some point i'll FIXME and make 21289 // all my com instances manually managed since they supposed 21290 // to respect the refcount. 21291 import core.memory; 21292 GC.addRoot(cast(void*) dropTarget); 21293 21294 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 21295 throw new Exception("register"); 21296 21297 window.dropHandler = handler; 21298 } else throw new NotYetImplementedException(); 21299 } 21300 21301 21302 21303 static if(UsingSimpledisplayX11) { 21304 21305 enum _NET_WM_STATE_ADD = 1; 21306 enum _NET_WM_STATE_REMOVE = 0; 21307 enum _NET_WM_STATE_TOGGLE = 2; 21308 21309 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 21310 void demandAttention(SimpleWindow window, bool needs = true) { 21311 demandAttention(window.impl.window, needs); 21312 } 21313 21314 /// ditto 21315 void demandAttention(Window window, bool needs = true) { 21316 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 21317 } 21318 21319 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 21320 auto display = XDisplayConnection.get(); 21321 if(atom == None) 21322 return; // non-failure error 21323 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 21324 21325 XClientMessageEvent xclient; 21326 21327 xclient.type = EventType.ClientMessage; 21328 xclient.window = window; 21329 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 21330 xclient.format = 32; 21331 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 21332 xclient.data.l[1] = atom; 21333 xclient.data.l[2] = atom2; 21334 xclient.data.l[3] = 1; 21335 // [3] == source. 0 == unknown, 1 == app, 2 == else 21336 21337 XSendEvent( 21338 display, 21339 RootWindow(display, DefaultScreen(display)), 21340 false, 21341 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 21342 cast(XEvent*) &xclient 21343 ); 21344 21345 /+ 21346 XChangeProperty( 21347 display, 21348 window.impl.window, 21349 GetAtom!"_NET_WM_STATE"(display), 21350 XA_ATOM, 21351 32 /* bits */, 21352 PropModeAppend, 21353 &atom, 21354 1); 21355 +/ 21356 } 21357 21358 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 21359 Atom actionAtom; 21360 with(DragAndDropAction) 21361 final switch(action) { 21362 case none: actionAtom = None; break; 21363 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 21364 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 21365 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 21366 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 21367 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 21368 } 21369 21370 return actionAtom; 21371 } 21372 21373 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 21374 // FIXME: I need to show user feedback somehow. 21375 auto display = XDisplayConnection.get; 21376 21377 auto actionAtom = dndActionAtom(display, action); 21378 assert(actionAtom, "Don't use action none to accept a drop"); 21379 21380 setX11Selection!"XdndSelection"(window, handler, null); 21381 21382 auto oldKeyHandler = window.handleKeyEvent; 21383 scope(exit) window.handleKeyEvent = oldKeyHandler; 21384 21385 auto oldCharHandler = window.handleCharEvent; 21386 scope(exit) window.handleCharEvent = oldCharHandler; 21387 21388 auto oldMouseHandler = window.handleMouseEvent; 21389 scope(exit) window.handleMouseEvent = oldMouseHandler; 21390 21391 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 21392 21393 import core.sys.posix.sys.time; 21394 timeval tv; 21395 gettimeofday(&tv, null); 21396 21397 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 21398 21399 Time lastMouseTimestamp; 21400 21401 bool dnding = true; 21402 Window lastIn = None; 21403 21404 void leave() { 21405 if(lastIn == None) 21406 return; 21407 21408 XEvent ev; 21409 ev.xclient.type = EventType.ClientMessage; 21410 ev.xclient.window = lastIn; 21411 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 21412 ev.xclient.format = 32; 21413 ev.xclient.data.l[0] = window.impl.window; 21414 21415 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21416 XFlush(display); 21417 21418 lastIn = None; 21419 } 21420 21421 void enter(Window w) { 21422 assert(lastIn == None); 21423 21424 lastIn = w; 21425 21426 XEvent ev; 21427 ev.xclient.type = EventType.ClientMessage; 21428 ev.xclient.window = lastIn; 21429 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 21430 ev.xclient.format = 32; 21431 ev.xclient.data.l[0] = window.impl.window; 21432 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 21433 21434 auto types = handler.availableFormats(); 21435 assert(types.length > 0); 21436 21437 ev.xclient.data.l[2] = types[0]; 21438 if(types.length > 1) 21439 ev.xclient.data.l[3] = types[1]; 21440 if(types.length > 2) 21441 ev.xclient.data.l[4] = types[2]; 21442 21443 // FIXME: other types?!?!? and make sure we skip TARGETS 21444 21445 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21446 XFlush(display); 21447 } 21448 21449 void position(int rootX, int rootY) { 21450 assert(lastIn != None); 21451 21452 XEvent ev; 21453 ev.xclient.type = EventType.ClientMessage; 21454 ev.xclient.window = lastIn; 21455 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 21456 ev.xclient.format = 32; 21457 ev.xclient.data.l[0] = window.impl.window; 21458 ev.xclient.data.l[1] = 0; // reserved 21459 ev.xclient.data.l[2] = (rootX << 16) | rootY; 21460 ev.xclient.data.l[3] = dataTimestamp; 21461 ev.xclient.data.l[4] = actionAtom; 21462 21463 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21464 XFlush(display); 21465 21466 } 21467 21468 void drop() { 21469 XEvent ev; 21470 ev.xclient.type = EventType.ClientMessage; 21471 ev.xclient.window = lastIn; 21472 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 21473 ev.xclient.format = 32; 21474 ev.xclient.data.l[0] = window.impl.window; 21475 ev.xclient.data.l[1] = 0; // reserved 21476 ev.xclient.data.l[2] = dataTimestamp; 21477 21478 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21479 XFlush(display); 21480 21481 lastIn = None; 21482 dnding = false; 21483 } 21484 21485 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 21486 // but idk if i should... 21487 21488 window.setEventHandlers( 21489 delegate(KeyEvent ev) { 21490 if(ev.pressed == true && ev.key == Key.Escape) { 21491 // cancel 21492 dnding = false; 21493 } 21494 }, 21495 delegate(MouseEvent ev) { 21496 if(ev.timestamp < lastMouseTimestamp) 21497 return; 21498 21499 lastMouseTimestamp = ev.timestamp; 21500 21501 if(ev.type == MouseEventType.motion) { 21502 auto display = XDisplayConnection.get; 21503 auto root = RootWindow(display, DefaultScreen(display)); 21504 21505 Window topWindow; 21506 int rootX, rootY; 21507 21508 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 21509 21510 if(topWindow == None) 21511 return; 21512 21513 top: 21514 if(auto result = topWindow in eligibility) { 21515 auto dropWindow = *result; 21516 if(dropWindow == None) { 21517 leave(); 21518 return; 21519 } 21520 21521 if(dropWindow != lastIn) { 21522 leave(); 21523 enter(dropWindow); 21524 position(rootX, rootY); 21525 } else { 21526 position(rootX, rootY); 21527 } 21528 } else { 21529 // determine eligibility 21530 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 21531 if(data.length == 1) { 21532 // in case there is no WM or it isn't reparenting 21533 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 21534 } else { 21535 21536 Window tryScanChildren(Window search, int maxRecurse) { 21537 // could be reparenting window manager, so gotta check the next few children too 21538 Window child; 21539 int x; 21540 int y; 21541 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 21542 21543 if(child == None) 21544 return None; 21545 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 21546 if(data.length == 1) { 21547 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 21548 } else { 21549 if(maxRecurse) 21550 return tryScanChildren(child, maxRecurse - 1); 21551 else 21552 return None; 21553 } 21554 21555 } 21556 21557 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 21558 auto topResult = tryScanChildren(topWindow, 3); 21559 // it is easy to have a false negative due to the mouse going over a WM 21560 // child window like the close button if separate from the frame... so I 21561 // can't really cache negatives, :( 21562 if(topResult != None) { 21563 eligibility[topWindow] = topResult; 21564 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 21565 } 21566 } 21567 21568 } 21569 21570 } else if(ev.type == MouseEventType.buttonReleased) { 21571 drop(); 21572 dnding = false; 21573 } 21574 } 21575 ); 21576 21577 window.grabInput(); 21578 scope(exit) 21579 window.releaseInputGrab(); 21580 21581 21582 EventLoop.get.run(() => dnding); 21583 21584 return 0; 21585 } 21586 21587 /// X-specific 21588 TrueColorImage getWindowNetWmIcon(Window window) { 21589 try { 21590 auto display = XDisplayConnection.get; 21591 21592 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 21593 21594 if (data.length > arch_ulong.sizeof * 2) { 21595 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 21596 // these are an array of rgba images that we have to convert into pixmaps ourself 21597 21598 int width = cast(int) meta[0]; 21599 int height = cast(int) meta[1]; 21600 21601 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 21602 21603 static if(arch_ulong.sizeof == 4) { 21604 bytes = bytes[0 .. width * height * 4]; 21605 alias imageData = bytes; 21606 } else static if(arch_ulong.sizeof == 8) { 21607 bytes = bytes[0 .. width * height * 8]; 21608 auto imageData = new ubyte[](4 * width * height); 21609 } else static assert(0); 21610 21611 21612 21613 // this returns ARGB. Remember it is little-endian so 21614 // we have BGRA 21615 // our thing uses RGBA, which in little endian, is ABGR 21616 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 21617 auto r = bytes[idx + 2]; 21618 auto g = bytes[idx + 1]; 21619 auto b = bytes[idx + 0]; 21620 auto a = bytes[idx + 3]; 21621 21622 imageData[idx2 + 0] = r; 21623 imageData[idx2 + 1] = g; 21624 imageData[idx2 + 2] = b; 21625 imageData[idx2 + 3] = a; 21626 } 21627 21628 return new TrueColorImage(width, height, imageData); 21629 } 21630 21631 return null; 21632 } catch(Exception e) { 21633 return null; 21634 } 21635 } 21636 21637 } /* UsingSimpledisplayX11 */ 21638 21639 21640 void loadBinNameToWindowClassName () { 21641 import core.stdc.stdlib : realloc; 21642 version(linux) { 21643 // args[0] MAY be empty, so we'll just use this 21644 import core.sys.posix.unistd : readlink; 21645 char[1024] ebuf = void; // 1KB should be enough for everyone! 21646 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 21647 if (len < 1) return; 21648 } else /*version(Windows)*/ { 21649 import core.runtime : Runtime; 21650 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 21651 auto ebuf = Runtime.args[0]; 21652 auto len = ebuf.length; 21653 } 21654 auto pos = len; 21655 while (pos > 0 && ebuf[pos-1] != '/') --pos; 21656 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 21657 if (sdpyWindowClassStr is null) return; // oops 21658 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 21659 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 21660 } 21661 21662 /++ 21663 An interface representing a font that is drawn with custom facilities. 21664 21665 You might want [OperatingSystemFont] instead, which represents 21666 a font loaded and drawn by functions native to the operating system. 21667 21668 WARNING: I might still change this. 21669 +/ 21670 interface DrawableFont : MeasurableFont { 21671 /++ 21672 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 21673 21674 Implementations must use the painter's fillColor to draw a rectangle behind the string, 21675 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 21676 fill color, but that's up to the implementation. 21677 +/ 21678 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 21679 21680 /++ 21681 Requests that the given string is added to the image cache. You should only do this rarely, but 21682 if you have a string that you know will be used over and over again, adding it to a cache can 21683 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 21684 to implement this as a do-nothing method). 21685 +/ 21686 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 21687 } 21688 21689 /++ 21690 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 21691 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 21692 21693 You should also consider [OperatingSystemFont], which loads and draws a font with 21694 facilities native to the user's operating system. You might also consider 21695 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 21696 of game, as they have their own ways to draw text too. 21697 21698 Be warned: this can be slow, especially on remote connections to the X server, since 21699 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 21700 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 21701 experiment in your specific case. 21702 21703 Please note that the return type of [DrawableFont] also includes an implementation of 21704 [MeasurableFont]. 21705 +/ 21706 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 21707 import arsd.ttf; 21708 static class ArsdTtfFont : DrawableFont { 21709 TtfFont font; 21710 int size; 21711 this(in ubyte[] data, int size) { 21712 font = TtfFont(data); 21713 this.size = size; 21714 21715 21716 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 21717 int ascent_, descent_, line_gap; 21718 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 21719 21720 int advance, lsb; 21721 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 21722 xWidth = cast(int) (advance * scale); 21723 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 21724 MWidth = cast(int) (advance * scale); 21725 } 21726 21727 private int ascent_; 21728 private int descent_; 21729 private int xWidth; 21730 private int MWidth; 21731 21732 bool isMonospace() { 21733 return xWidth == MWidth; 21734 } 21735 int averageWidth() { 21736 return xWidth; 21737 } 21738 int height() { 21739 return size; 21740 } 21741 int ascent() { 21742 return ascent_; 21743 } 21744 int descent() { 21745 return descent_; 21746 } 21747 21748 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 21749 int width, height; 21750 font.getStringSize(s, size, width, height); 21751 return width; 21752 } 21753 21754 21755 21756 Sprite[string] cache; 21757 21758 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 21759 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 21760 cache[text] = sprite; 21761 } 21762 21763 Image stringToImage(Color fg, Color bg, in char[] text) { 21764 int width, height; 21765 auto data = font.renderString(text, size, width, height); 21766 auto image = new TrueColorImage(width, height); 21767 int pos = 0; 21768 foreach(y; 0 .. height) 21769 foreach(x; 0 .. width) { 21770 fg.a = data[0]; 21771 bg.a = 255; 21772 auto color = alphaBlend(fg, bg); 21773 image.imageData.bytes[pos++] = color.r; 21774 image.imageData.bytes[pos++] = color.g; 21775 image.imageData.bytes[pos++] = color.b; 21776 image.imageData.bytes[pos++] = data[0]; 21777 data = data[1 .. $]; 21778 } 21779 assert(data.length == 0); 21780 21781 return Image.fromMemoryImage(image); 21782 } 21783 21784 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 21785 Sprite sprite = (text in cache) ? *(text in cache) : null; 21786 21787 auto fg = painter.impl._outlineColor; 21788 auto bg = painter.impl._fillColor; 21789 21790 if(sprite !is null) { 21791 auto w = cast(SimpleWindow) painter.window; 21792 assert(w !is null); 21793 21794 sprite.drawAt(painter, upperLeft); 21795 } else { 21796 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 21797 } 21798 } 21799 } 21800 21801 return new ArsdTtfFont(data, size); 21802 } 21803 21804 class NotYetImplementedException : Exception { 21805 this(string file = __FILE__, size_t line = __LINE__) { 21806 super("Not yet implemented", file, line); 21807 } 21808 } 21809 21810 /// 21811 __gshared bool librariesSuccessfullyLoaded = true; 21812 /// 21813 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 21814 21815 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 21816 mixin(staticForeachReplacement!Iface); 21817 21818 void loadDynamicLibrary() @nogc { 21819 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 21820 } 21821 21822 void loadDynamicLibraryForReal() { 21823 foreach(name; __traits(derivedMembers, Iface)) { 21824 mixin("alias tmp = " ~ name ~ ";"); 21825 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 21826 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 21827 } 21828 } 21829 } 21830 21831 private const(char)[] staticForeachReplacement(Iface)() pure { 21832 /* 21833 // just this for gdc 9.... 21834 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 21835 21836 static foreach(name; __traits(derivedMembers, Iface)) 21837 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 21838 */ 21839 21840 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 21841 size_t pos; 21842 21843 void append(in char[] what) { 21844 if(pos + what.length > code.length) 21845 code.length = (code.length * 3) / 2; 21846 code[pos .. pos + what.length] = what[]; 21847 pos += what.length; 21848 } 21849 21850 foreach(name; __traits(derivedMembers, Iface)) { 21851 append(`__gshared typeof(&__traits(getMember, Iface, "`); 21852 append(name); 21853 append(`")) `); 21854 append(name); 21855 append(";"); 21856 } 21857 21858 return code[0 .. pos]; 21859 } 21860 21861 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 21862 mixin(staticForeachReplacement!Iface); 21863 21864 private __gshared void* libHandle; 21865 private __gshared bool attempted; 21866 21867 void loadDynamicLibrary() @nogc { 21868 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 21869 } 21870 21871 bool loadAttempted() { 21872 return attempted; 21873 } 21874 bool loadSuccessful() { 21875 return libHandle !is null; 21876 } 21877 21878 void loadDynamicLibraryForReal() { 21879 attempted = true; 21880 version(Posix) { 21881 import core.sys.posix.dlfcn; 21882 version(OSX) { 21883 version(X11) 21884 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 21885 else 21886 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 21887 } else { 21888 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 21889 if(libHandle is null) 21890 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 21891 } 21892 21893 static void* loadsym(void* l, const char* name) { 21894 import core.stdc.stdlib; 21895 if(l is null) 21896 return &abort; 21897 return dlsym(l, name); 21898 } 21899 } else version(Windows) { 21900 import core.sys.windows.winbase; 21901 libHandle = LoadLibrary(library ~ ".dll"); 21902 static void* loadsym(void* l, const char* name) { 21903 import core.stdc.stdlib; 21904 if(l is null) 21905 return &abort; 21906 return GetProcAddress(l, name); 21907 } 21908 } 21909 if(libHandle is null) { 21910 success = false; 21911 //throw new Exception("load failure of library " ~ library); 21912 } 21913 foreach(name; __traits(derivedMembers, Iface)) { 21914 mixin("alias tmp = " ~ name ~ ";"); 21915 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 21916 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 21917 } 21918 } 21919 21920 void unloadDynamicLibrary() { 21921 version(Posix) { 21922 import core.sys.posix.dlfcn; 21923 dlclose(libHandle); 21924 } else version(Windows) { 21925 import core.sys.windows.winbase; 21926 FreeLibrary(libHandle); 21927 } 21928 foreach(name; __traits(derivedMembers, Iface)) 21929 mixin(name ~ " = null;"); 21930 } 21931 } 21932 21933 /+ 21934 The GC can be called from any thread, and a lot of cleanup must be done 21935 on the gui thread. Since the GC can interrupt any locks - including being 21936 triggered inside a critical section - it is vital to avoid deadlocks to get 21937 these functions called from the right place. 21938 21939 If the buffer overflows, things are going to get leaked. I'm kinda ok with that 21940 right now. 21941 21942 The cleanup function is run when the event loop gets around to it, which is just 21943 whenever there's something there after it has been woken up for other work. It does 21944 NOT wake up the loop itself - can't risk doing that from inside the GC in another thread. 21945 (Well actually it might be ok but i don't wanna mess with it right now.) 21946 +/ 21947 private struct CleanupQueue { 21948 import core.stdc.stdlib; 21949 21950 void queue(alias func, T...)(T args) { 21951 static struct Args { 21952 T args; 21953 } 21954 static struct RealJob { 21955 Job j; 21956 Args a; 21957 } 21958 static void call(Job* data) { 21959 auto rj = cast(RealJob*) data; 21960 func(rj.a.args); 21961 } 21962 21963 RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof); 21964 thing.j.call = &call; 21965 thing.a.args = args; 21966 21967 buffer[tail++] = cast(Job*) thing; 21968 21969 // FIXME: set overflowed 21970 } 21971 21972 void process() { 21973 const tail = this.tail; 21974 21975 while(tail != head) { 21976 Job* job = cast(Job*) buffer[head++]; 21977 job.call(job); 21978 free(job); 21979 } 21980 21981 if(overflowed) 21982 throw new Exception("cleanup overflowed"); 21983 } 21984 21985 private: 21986 21987 ubyte tail; // must ONLY be written by queue 21988 ubyte head; // must ONLY be written by process 21989 bool overflowed; 21990 21991 static struct Job { 21992 void function(Job*) call; 21993 } 21994 21995 void*[256] buffer; 21996 } 21997 private __gshared CleanupQueue cleanupQueue; 21998 21999 version(X11) 22000 /++ 22001 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 22002 22003 $(WARNING 22004 This function is exempted from stability guarantees. 22005 ) 22006 +/ 22007 float customScalingFactorForMonitor(int monitorNumber) { 22008 import core.stdc.stdlib; 22009 auto val = getenv("ARSD_SCALING_FACTOR"); 22010 22011 if(val is null) 22012 return 1.0; 22013 22014 char[16] buffer = 0; 22015 int pos; 22016 22017 const(char)* at = val; 22018 22019 foreach(item; 0 .. monitorNumber + 1) { 22020 if(*at == 0) 22021 break; // reuse the last number when we at the end of the string 22022 pos = 0; 22023 while(pos + 1 < buffer.length && *at && *at != ';') { 22024 buffer[pos++] = *at; 22025 at++; 22026 } 22027 if(*at) 22028 at++; // skip the semicolon 22029 buffer[pos] = 0; 22030 } 22031 22032 //sdpyPrintDebugString(buffer[0 .. pos]); 22033 22034 import core.stdc.math; 22035 auto f = atof(buffer.ptr); 22036 22037 if(f <= 0.0 || isnan(f) || isinf(f)) 22038 return 1.0; 22039 22040 return f; 22041 } 22042 22043 void guiAbortProcess(string msg) { 22044 import core.stdc.stdlib; 22045 version(Windows) { 22046 WCharzBuffer t = WCharzBuffer(msg); 22047 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 22048 } else { 22049 import core.stdc.stdio; 22050 fwrite(msg.ptr, 1, msg.length, stderr); 22051 msg = "\n"; 22052 fwrite(msg.ptr, 1, msg.length, stderr); 22053 fflush(stderr); 22054 } 22055 22056 abort(); 22057 } 22058 22059 private int minInternal(int a, int b) { 22060 return (a < b) ? a : b; 22061 } 22062 22063 private alias scriptable = arsd_jsvar_compatible;