1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 // https://freedesktop.org/wiki/Specifications/XDND/ 4 5 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format 6 7 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html 8 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html 9 10 11 // on Mac with X11: -L-L/usr/X11/lib 12 13 /+ 14 15 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works 16 17 Progress bar in taskbar 18 - i can probably just set a property on the window... 19 it sets that prop to an integer 0 .. 100. Taskbar 20 deletes it or window deletes it when it is handled. 21 - prolly display it as a nice little line at the bottom. 22 23 24 from gtk: 25 26 #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" 27 #define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" 28 29 >+ if (cardinal > 0) 30 >+ { 31 >+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display), 32 >+ xid, 33 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name), 34 >+ XA_CARDINAL, 32, 35 >+ PropModeReplace, 36 >+ (guchar *) &cardinal, 1); 37 >+ } 38 >+ else 39 >+ { 40 >+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), 41 >+ xid, 42 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name)); 43 >+ } 44 45 from Windows: 46 47 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3 48 49 interface 50 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 51 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 52 listen for msg, return TRUE 53 interface->SetProgressState(hwnd, TBPF_NORMAL); 54 interface->SetProgressValue(hwnd, 40, 100); 55 56 57 My new notification system. 58 - use a unix socket? or a x property? or a udp port? 59 - could of course also get on the dbus train but ugh. 60 - it could also reply with the info as a string for easy remote examination. 61 62 +/ 63 64 /* 65 Event Loop would be nices: 66 67 * add on idle - runs when nothing else happens 68 * which can specify how long to yield for 69 * send messages without a recipient window 70 * setTimeout 71 * setInterval 72 */ 73 74 /* 75 Classic games I want to add: 76 * my tetris clone 77 * pac man 78 */ 79 80 /* 81 Text layout needs a lot of work. Plain drawText is useful but too 82 limited. It will need some kind of text context thing which it will 83 update and you can pass it on and get more details out of it. 84 85 It will need a bounding box, a current cursor location that is updated 86 as drawing continues, and various changable facts (which can also be 87 changed on the painter i guess) like font, color, size, background, 88 etc. 89 90 We can also fetch the caret location from it somehow. 91 92 Should prolly be an overload of drawText 93 94 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 95 96 WS_EX_NOACTIVATE 97 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 98 full screen windows. Can just set the atom on X. Windows will be harder. 99 100 moving windows. resizing windows. 101 102 hide cursor, capture cursor, change cursor. 103 104 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 105 sure the pieces are there to do its job easily and make other jobs possible. 106 */ 107 108 /++ 109 simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality, 110 including creating windows, drawing on them, working with the clipboard, 111 timers, OpenGL, and more. However, it does NOT provide high level GUI 112 widgets. See my minigui.d, an extension to this module, for that 113 functionality. 114 115 simpledisplay provides cross-platform wrapping for Windows and Linux 116 (and perhaps other OSes that use X11), but also does not prevent you 117 from using the underlying facilities if you need them. It has a goal 118 of working efficiently over a remote X link (at least as far as Xlib 119 reasonably allows.) 120 121 simpledisplay depends on [arsd.color|color.d], which should be available from the 122 same place where you got this file. Other than that, however, it has 123 very few dependencies and ones that don't come with the OS and/or the 124 compiler are all opt-in. 125 126 simpledisplay.d's home base is on my arsd repo on Github. The file is: 127 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 128 129 simpledisplay is basically stable. I plan to refactor the internals, 130 and may add new features and fix bugs, but It do not expect to 131 significantly change the API. It has been stable a few years already now. 132 133 Installation_instructions: 134 135 `simpledisplay.d` does not have any dependencies outside the 136 operating system and `color.d`, so it should just work most the 137 time, but there are a few caveats on some systems: 138 139 On Win32, you can pass `-L/subsystem:windows` if you don't want a 140 console to be automatically allocated. 141 142 Please note when compiling on Win64, you need to explicitly list 143 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 144 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 145 146 If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`; 147 note the "w". 148 149 I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you, 150 but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi). 151 See [EnableWindowsSubsystem] for more information. 152 153 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. 154 155 On Ubuntu, you might need to install X11 development libraries to 156 successfully link. 157 158 $(CONSOLE 159 $ sudo apt-get install libglc-dev 160 $ sudo apt-get install libx11-dev 161 ) 162 163 164 Jump_list: 165 166 Don't worry, you don't have to read this whole documentation file! 167 168 Check out the [#event-example] and [#Pong-example] to get started quickly. 169 170 The main classes you may want to create are [SimpleWindow], [Timer], 171 [Image], and [Sprite]. 172 173 The main functions you'll want are [setClipboardText] and [getClipboardText]. 174 175 There are also platform-specific functions available such as [XDisplayConnection] 176 and [GetAtom] for X11, among others. 177 178 See the examples and topics list below to learn more. 179 180 $(WARNING 181 There should only be one GUI thread per application, 182 and all windows should be created in it and your 183 event loop should run there. 184 185 To do otherwise is undefined behavior and has no 186 cross platform guarantees. 187 ) 188 189 $(H2 About this documentation) 190 191 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. 192 193 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! 194 195 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. 196 197 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. 198 199 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. 200 201 At points, I will talk about implementation details in the documentation. These are sometimes 202 subject to change, but nevertheless useful to understand what is really going on. You can learn 203 more about some of the referenced things by searching the web for info about using them from C. 204 You can always look at the source of simpledisplay.d too for the most authoritative source on 205 its specific implementation. If you disagree with how I did something, please contact me so we 206 can discuss it! 207 208 $(H2 Using with fibers) 209 210 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). 211 212 $(H2 Topics) 213 214 $(H3 $(ID topic-windows) Windows) 215 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 216 window on the user's screen. 217 218 You may create multiple windows, if the underlying platform supports it. You may check 219 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 220 SimpleWindow's constructor at runtime to handle those cases. 221 222 A single running event loop will handle as many windows as needed. 223 224 $(H3 $(ID topic-event-loops) Event loops) 225 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 226 227 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: 228 229 --- 230 // dmd example.d simpledisplay.d color.d 231 import arsd.simpledisplay; 232 void main() { 233 auto window = new SimpleWindow(200, 200); 234 window.eventLoop(0, 235 delegate (dchar) { /* got a character key press */ } 236 ); 237 } 238 --- 239 240 $(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.) 241 242 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. 243 244 On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 245 246 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 247 248 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. 249 250 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 251 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. 252 253 See the [NotificationAreaIcon] class. 254 255 $(H3 $(ID topic-input-handling) Input handling) 256 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 257 258 See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent]. 259 260 $(H3 $(ID topic-2d-drawing) 2d Drawing) 261 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 262 263 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: 264 265 --- 266 // dmd example.d simpledisplay.d color.d 267 import arsd.simpledisplay; 268 void main() { 269 auto window = new SimpleWindow(200, 200); 270 { // introduce sub-scope 271 auto painter = window.draw(); // begin drawing 272 /* draw here */ 273 painter.outlineColor = Color.red; 274 painter.fillColor = Color.black; 275 painter.drawRectangle(Point(0, 0), 200, 200); 276 } // end scope, calling `painter`'s destructor, drawing to the screen. 277 window.eventLoop(0); // handle events 278 } 279 --- 280 281 Painting is done based on two color properties, a pen and a brush. 282 283 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. 284 285 FIXME Add example of 2d opengl drawing here. 286 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 287 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 288 289 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. 290 291 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 292 293 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 294 295 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]. 296 297 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. 298 299 This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color: 300 301 --- 302 import arsd.simpledisplay; 303 304 void main() { 305 auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing); 306 307 float otherColor = 0.0; 308 float colorDelta = 0.05; 309 310 window.redrawOpenGlScene = delegate() { 311 glLoadIdentity(); 312 glBegin(GL_QUADS); 313 314 glColor3f(1.0, otherColor, 0); 315 glVertex3f(-0.8, -0.8, 0); 316 317 glColor3f(1.0, otherColor, 1.0); 318 glVertex3f(0.8, -0.8, 0); 319 320 glColor3f(0, 1.0, otherColor); 321 glVertex3f(0.8, 0.8, 0); 322 323 glColor3f(otherColor, 0, 1.0); 324 glVertex3f(-0.8, 0.8, 0); 325 326 glEnd(); 327 }; 328 329 window.eventLoop(50, () { 330 otherColor += colorDelta; 331 if(otherColor > 1.0) { 332 otherColor = 1.0; 333 colorDelta = -0.05; 334 } 335 if(otherColor < 0) { 336 otherColor = 0; 337 colorDelta = 0.05; 338 } 339 // at the end of the timer, we have to request a redraw 340 // or we won't see the changes. 341 window.redrawOpenGlSceneSoon(); 342 }); 343 } 344 --- 345 346 My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow]. 347 $(H3 $(ID topic-modern-opengl) Modern OpenGL) 348 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. 349 350 This example program shows how you can set up a shader to draw a rectangle: 351 352 --- 353 module opengl3test; 354 import arsd.simpledisplay; 355 356 // based on https://learnopengl.com/Getting-started/Hello-Triangle 357 358 void main() { 359 // First thing we do, before creating the window, is declare what version we want. 360 setOpenGLContextVersion(3, 3); 361 // turning off legacy compat is required to use version 3.3 and newer 362 openGLContextCompatible = false; 363 364 uint VAO; 365 OpenGlShader shader; 366 367 // then we can create the window. 368 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 369 370 // additional setup needs to be done when it is visible, simpledisplay offers a property 371 // for exactly that: 372 window.visibleForTheFirstTime = delegate() { 373 // now with the window loaded, we can start loading the modern opengl functions. 374 375 // you MUST set the context first. 376 window.setAsCurrentOpenGlContext; 377 // then load the remainder of the library 378 gl3.loadDynamicLibrary(); 379 380 // now you can create the shaders, etc. 381 shader = new OpenGlShader( 382 OpenGlShader.Source(GL_VERTEX_SHADER, ` 383 #version 330 core 384 layout (location = 0) in vec3 aPos; 385 void main() { 386 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 387 } 388 `), 389 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 390 #version 330 core 391 out vec4 FragColor; 392 uniform vec4 mycolor; 393 void main() { 394 FragColor = mycolor; 395 } 396 `), 397 ); 398 399 // and do whatever other setup you want. 400 401 float[] vertices = [ 402 0.5f, 0.5f, 0.0f, // top right 403 0.5f, -0.5f, 0.0f, // bottom right 404 -0.5f, -0.5f, 0.0f, // bottom left 405 -0.5f, 0.5f, 0.0f // top left 406 ]; 407 uint[] indices = [ // note that we start from 0! 408 0, 1, 3, // first Triangle 409 1, 2, 3 // second Triangle 410 ]; 411 uint VBO, EBO; 412 glGenVertexArrays(1, &VAO); 413 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 414 glBindVertexArray(VAO); 415 416 glGenBuffers(1, &VBO); 417 glGenBuffers(1, &EBO); 418 419 glBindBuffer(GL_ARRAY_BUFFER, VBO); 420 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 421 422 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 423 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 424 425 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 426 glEnableVertexAttribArray(0); 427 428 // the library will set the initial viewport and trigger our first draw, 429 // so these next two lines are NOT needed. they are just here as comments 430 // to show what would happen next. 431 432 // glViewport(0, 0, window.width, window.height); 433 // window.redrawOpenGlSceneNow(); 434 }; 435 436 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 437 // it is our render method. 438 window.redrawOpenGlScene = delegate() { 439 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 440 glClear(GL_COLOR_BUFFER_BIT); 441 442 glUseProgram(shader.shaderProgram); 443 444 // the shader helper class has methods to set uniforms too 445 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 446 447 glBindVertexArray(VAO); 448 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 449 }; 450 451 window.eventLoop(0); 452 } 453 --- 454 455 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. 456 457 458 $(H3 $(ID topic-images) Displaying images) 459 You can also load PNG images using [arsd.png]. 460 461 --- 462 // dmd example.d simpledisplay.d color.d png.d 463 import arsd.simpledisplay; 464 import arsd.png; 465 466 void main() { 467 auto image = Image.fromMemoryImage(readPng("image.png")); 468 displayImage(image); 469 } 470 --- 471 472 Compile with `dmd example.d simpledisplay.d png.d`. 473 474 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. 475 476 $(H3 $(ID topic-sprites) Sprites) 477 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. 478 479 [Sprite] is also the only facility that currently supports alpha blending without using OpenGL . 480 481 $(H3 $(ID topic-clipboard) Clipboard) 482 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 483 484 It also has helpers for handling X-specific events. 485 486 $(H3 $(ID topic-dnd) Drag and Drop) 487 See [enableDragAndDrop] and [draggable]. 488 489 $(H3 $(ID topic-timers) Timers) 490 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]. 491 492 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. 493 494 --- 495 import arsd.simpledisplay; 496 497 void main() { 498 auto window = new SimpleWindow(400, 400); 499 // every 100 ms, it will draw a random line 500 // on the window. 501 window.eventLoop(100, { 502 auto painter = window.draw(); 503 504 import std.random; 505 // random color 506 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 507 // random line 508 painter.drawLine( 509 Point(uniform(0, window.width), uniform(0, window.height)), 510 Point(uniform(0, window.width), uniform(0, window.height))); 511 512 }); 513 } 514 --- 515 516 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. 517 518 The pulse timer and instances of the [Timer] class may be combined at will. 519 520 --- 521 import arsd.simpledisplay; 522 523 void main() { 524 auto window = new SimpleWindow(400, 400); 525 auto timer = new Timer(1000, delegate { 526 auto painter = window.draw(); 527 painter.clear(); 528 }); 529 530 window.eventLoop(0); 531 } 532 --- 533 534 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 535 536 $(H3 $(ID topic-os-helpers) OS-specific helpers) 537 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. 538 539 See also: `xwindows.d` from my github. 540 541 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 542 `handleNativeEvent` and `handleNativeGlobalEvent`. 543 544 $(H3 $(ID topic-integration) Integration with other libraries) 545 Integration with a third-party event loop is possible. 546 547 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. 548 549 $(H3 $(ID topic-guis) GUI widgets) 550 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! 551 552 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. 553 554 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.) 555 556 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 557 558 $(H2 Platform-specific tips and tricks) 559 560 Windows_tips: 561 562 You can add icons or manifest files to your exe using a resource file. 563 564 To create a Windows .ico file, use the gimp or something. I'll write a helper 565 program later. 566 567 Create `yourapp.rc`: 568 569 ```rc 570 1 ICON filename.ico 571 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 572 ``` 573 574 And `yourapp.exe.manifest`: 575 576 ```xml 577 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 578 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 579 <assemblyIdentity 580 version="1.0.0.0" 581 processorArchitecture="*" 582 name="CompanyName.ProductName.YourApplication" 583 type="win32" 584 /> 585 <description>Your application description here.</description> 586 <dependency> 587 <dependentAssembly> 588 <assemblyIdentity 589 type="win32" 590 name="Microsoft.Windows.Common-Controls" 591 version="6.0.0.0" 592 processorArchitecture="*" 593 publicKeyToken="6595b64144ccf1df" 594 language="*" 595 /> 596 </dependentAssembly> 597 </dependency> 598 <application xmlns="urn:schemas-microsoft-com:asm.v3"> 599 <windowsSettings> 600 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style --> 601 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style --> 602 <!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text --> 603 <!-- to render crisply in DPI-unaware contexts --> 604 <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>--> 605 </windowsSettings> 606 </application> 607 </assembly> 608 ``` 609 610 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`. 611 612 Doing this lets you opt into various new things since Windows XP. 613 614 See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests 615 616 $(H2 Tips) 617 618 $(H3 Name conflicts) 619 620 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: 621 622 --- 623 static import sdpy = arsd.simpledisplay; 624 import arsd.simpledisplay : SimpleWindow; 625 626 void main() { 627 auto window = new SimpleWindow(); 628 sdpy.EventLoop.get.run(); 629 } 630 --- 631 632 $(H2 $(ID developer-notes) Developer notes) 633 634 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 635 implementation though. 636 637 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 638 suck. If I was rewriting it, I wouldn't do it that way again. 639 640 This file must not have any more required dependencies. If you need bindings, add 641 them right to this file. Once it gets into druntime and is there for a while, remove 642 bindings from here to avoid conflicts (or put them in an appropriate version block 643 so it continues to just work on old dmd), but wait a couple releases before making the 644 transition so this module remains usable with older versions of dmd. 645 646 You may have optional dependencies if needed by putting them in version blocks or 647 template functions. You may also extend the module with other modules with UFCS without 648 actually editing this - that is nice to do if you can. 649 650 Try to make functions work the same way across operating systems. I typically make 651 it thinly wrap Windows, then emulate that on Linux. 652 653 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 654 Phobos! So try to avoid it. 655 656 See more comments throughout the source. 657 658 I realize this file is fairly large, but over half that is just bindings at the bottom 659 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 660 to understand. I suggest you jump around the source by looking for a particular 661 declaration you're interested in, like `class SimpleWindow` using your editor's search 662 function, then look at one piece at a time. 663 664 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 665 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 666 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 667 668 I live in the eastern United States, so I will most likely not be around at night in 669 that US east timezone. 670 671 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 672 673 Building documentation: use my adrdox generator, `dub run adrdox`. 674 675 Examples: 676 677 $(DIV $(ID Event-example)) 678 $(H3 $(ID event-example) Event example) 679 This program creates a window and draws events inside them as they 680 happen, scrolling the text in the window as needed. Run this program 681 and experiment to get a feel for where basic input events take place 682 in the library. 683 684 --- 685 // dmd example.d simpledisplay.d color.d 686 import arsd.simpledisplay; 687 import std.conv; 688 689 void main() { 690 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 691 692 int y = 0; 693 694 void addLine(string text) { 695 auto painter = window.draw(); 696 697 if(y + painter.fontHeight >= window.height) { 698 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 699 y -= painter.fontHeight; 700 } 701 702 painter.outlineColor = Color.red; 703 painter.fillColor = Color.black; 704 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 705 706 painter.outlineColor = Color.white; 707 708 painter.drawText(Point(10, y), text); 709 710 y += painter.fontHeight; 711 } 712 713 window.eventLoop(1000, 714 () { 715 addLine("Timer went off!"); 716 }, 717 (KeyEvent event) { 718 addLine(to!string(event)); 719 }, 720 (MouseEvent event) { 721 addLine(to!string(event)); 722 }, 723 (dchar ch) { 724 addLine(to!string(ch)); 725 } 726 ); 727 } 728 --- 729 730 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. 731 732 $(COMMENT 733 This program displays a pie chart. Clicking on a color will increase its share of the pie. 734 735 --- 736 737 --- 738 ) 739 740 741 +/ 742 module arsd.simpledisplay; 743 744 // FIXME: tetris demo 745 // FIXME: space invaders demo 746 // FIXME: asteroids demo 747 748 /++ $(ID Pong-example) 749 $(H3 Pong) 750 751 This program creates a little Pong-like game. Player one is controlled 752 with the keyboard. Player two is controlled with the mouse. It demos 753 the pulse timer, event handling, and some basic drawing. 754 +/ 755 version(demos) 756 unittest { 757 // dmd example.d simpledisplay.d color.d 758 import arsd.simpledisplay; 759 760 enum paddleMovementSpeed = 8; 761 enum paddleHeight = 48; 762 763 void main() { 764 auto window = new SimpleWindow(600, 400, "Pong game!"); 765 766 int playerOnePosition, playerTwoPosition; 767 int playerOneMovement, playerTwoMovement; 768 int playerOneScore, playerTwoScore; 769 770 int ballX, ballY; 771 int ballDx, ballDy; 772 773 void serve() { 774 import std.random; 775 776 ballX = window.width / 2; 777 ballY = window.height / 2; 778 ballDx = uniform(-4, 4) * 3; 779 ballDy = uniform(-4, 4) * 3; 780 if(ballDx == 0) 781 ballDx = uniform(0, 2) == 0 ? 3 : -3; 782 } 783 784 serve(); 785 786 window.eventLoop(50, // set a 50 ms timer pulls 787 // This runs once per timer pulse 788 delegate () { 789 auto painter = window.draw(); 790 791 painter.clear(); 792 793 // Update everyone's motion 794 playerOnePosition += playerOneMovement; 795 playerTwoPosition += playerTwoMovement; 796 797 ballX += ballDx; 798 ballY += ballDy; 799 800 // Bounce off the top and bottom edges of the window 801 if(ballY + 7 >= window.height) 802 ballDy = -ballDy; 803 if(ballY - 8 <= 0) 804 ballDy = -ballDy; 805 806 // Bounce off the paddle, if it is in position 807 if(ballX - 8 <= 16) { 808 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 809 ballDx = -ballDx + 1; // add some speed to keep it interesting 810 ballDy += playerOneMovement; // and y movement based on your controls too 811 ballX = 24; // move it past the paddle so it doesn't wiggle inside 812 } else { 813 // Missed it 814 playerTwoScore ++; 815 serve(); 816 } 817 } 818 819 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 820 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 821 ballDx = -ballDx - 1; 822 ballDy += playerTwoMovement; 823 ballX = window.width - 24; 824 } else { 825 // Missed it 826 playerOneScore ++; 827 serve(); 828 } 829 } 830 831 // Draw the paddles 832 painter.outlineColor = Color.black; 833 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 834 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 835 836 // Draw the ball 837 painter.fillColor = Color.red; 838 painter.outlineColor = Color.yellow; 839 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 840 841 // Draw the score 842 painter.outlineColor = Color.blue; 843 import std.conv; 844 painter.drawText(Point(64, 4), to!string(playerOneScore)); 845 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 846 847 }, 848 delegate (KeyEvent event) { 849 // Player 1's controls are the arrow keys on the keyboard 850 if(event.key == Key.Down) 851 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 852 if(event.key == Key.Up) 853 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 854 855 }, 856 delegate (MouseEvent event) { 857 // Player 2's controls are mouse movement while the left button is held down 858 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 859 if(event.dy > 0) 860 playerTwoMovement = paddleMovementSpeed; 861 else if(event.dy < 0) 862 playerTwoMovement = -paddleMovementSpeed; 863 } else { 864 playerTwoMovement = 0; 865 } 866 } 867 ); 868 } 869 } 870 871 /++ $(H3 $(ID example-minesweeper) Minesweeper) 872 873 This minesweeper demo shows how we can implement another classic 874 game with simpledisplay and shows some mouse input and basic output 875 code. 876 +/ 877 version(demos) 878 unittest { 879 import arsd.simpledisplay; 880 881 enum GameSquare { 882 mine = 0, 883 clear, 884 m1, m2, m3, m4, m5, m6, m7, m8 885 } 886 887 enum UserSquare { 888 unknown, 889 revealed, 890 flagged, 891 questioned 892 } 893 894 enum GameState { 895 inProgress, 896 lose, 897 win 898 } 899 900 GameSquare[] board; 901 UserSquare[] userState; 902 GameState gameState; 903 int boardWidth; 904 int boardHeight; 905 906 bool isMine(int x, int y) { 907 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 908 return false; 909 return board[y * boardWidth + x] == GameSquare.mine; 910 } 911 912 GameState reveal(int x, int y) { 913 if(board[y * boardWidth + x] == GameSquare.clear) { 914 floodFill(userState, boardWidth, boardHeight, 915 UserSquare.unknown, UserSquare.revealed, 916 x, y, 917 (x, y) { 918 if(board[y * boardWidth + x] == GameSquare.clear) 919 return true; 920 else { 921 userState[y * boardWidth + x] = UserSquare.revealed; 922 return false; 923 } 924 }); 925 } else { 926 userState[y * boardWidth + x] = UserSquare.revealed; 927 if(isMine(x, y)) 928 return GameState.lose; 929 } 930 931 foreach(state; userState) { 932 if(state == UserSquare.unknown || state == UserSquare.questioned) 933 return GameState.inProgress; 934 } 935 936 return GameState.win; 937 } 938 939 void initializeBoard(int width, int height, int numberOfMines) { 940 boardWidth = width; 941 boardHeight = height; 942 board.length = width * height; 943 944 userState.length = width * height; 945 userState[] = UserSquare.unknown; 946 947 import std.algorithm, std.random, std.range; 948 949 board[] = GameSquare.clear; 950 951 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 952 board[minePosition] = GameSquare.mine; 953 954 int x; 955 int y; 956 foreach(idx, ref square; board) { 957 if(square == GameSquare.clear) { 958 int danger = 0; 959 danger += isMine(x-1, y-1)?1:0; 960 danger += isMine(x-1, y)?1:0; 961 danger += isMine(x-1, y+1)?1:0; 962 danger += isMine(x, y-1)?1:0; 963 danger += isMine(x, y+1)?1:0; 964 danger += isMine(x+1, y-1)?1:0; 965 danger += isMine(x+1, y)?1:0; 966 danger += isMine(x+1, y+1)?1:0; 967 968 square = cast(GameSquare) (danger + 1); 969 } 970 971 x++; 972 if(x == width) { 973 x = 0; 974 y++; 975 } 976 } 977 } 978 979 void redraw(SimpleWindow window) { 980 import std.conv; 981 982 auto painter = window.draw(); 983 984 painter.clear(); 985 986 final switch(gameState) with(GameState) { 987 case inProgress: 988 break; 989 case win: 990 painter.fillColor = Color.green; 991 painter.drawRectangle(Point(0, 0), window.width, window.height); 992 return; 993 case lose: 994 painter.fillColor = Color.red; 995 painter.drawRectangle(Point(0, 0), window.width, window.height); 996 return; 997 } 998 999 int x = 0; 1000 int y = 0; 1001 1002 foreach(idx, square; board) { 1003 auto state = userState[idx]; 1004 1005 final switch(state) with(UserSquare) { 1006 case unknown: 1007 painter.outlineColor = Color.black; 1008 painter.fillColor = Color(128,128,128); 1009 1010 painter.drawRectangle( 1011 Point(x * 20, y * 20), 1012 20, 20 1013 ); 1014 break; 1015 case revealed: 1016 if(square == GameSquare.clear) { 1017 painter.outlineColor = Color.white; 1018 painter.fillColor = Color.white; 1019 1020 painter.drawRectangle( 1021 Point(x * 20, y * 20), 1022 20, 20 1023 ); 1024 } else { 1025 painter.outlineColor = Color.black; 1026 painter.fillColor = Color.white; 1027 1028 painter.drawText( 1029 Point(x * 20, y * 20), 1030 to!string(square)[1..2], 1031 Point(x * 20 + 20, y * 20 + 20), 1032 TextAlignment.Center | TextAlignment.VerticalCenter); 1033 } 1034 break; 1035 case flagged: 1036 painter.outlineColor = Color.black; 1037 painter.fillColor = Color.red; 1038 painter.drawRectangle( 1039 Point(x * 20, y * 20), 1040 20, 20 1041 ); 1042 break; 1043 case questioned: 1044 painter.outlineColor = Color.black; 1045 painter.fillColor = Color.yellow; 1046 painter.drawRectangle( 1047 Point(x * 20, y * 20), 1048 20, 20 1049 ); 1050 break; 1051 } 1052 1053 x++; 1054 if(x == boardWidth) { 1055 x = 0; 1056 y++; 1057 } 1058 } 1059 1060 } 1061 1062 void main() { 1063 auto window = new SimpleWindow(200, 200); 1064 1065 initializeBoard(10, 10, 10); 1066 1067 redraw(window); 1068 window.eventLoop(0, 1069 delegate (MouseEvent me) { 1070 if(me.type != MouseEventType.buttonPressed) 1071 return; 1072 auto x = me.x / 20; 1073 auto y = me.y / 20; 1074 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1075 if(me.button == MouseButton.left) { 1076 gameState = reveal(x, y); 1077 } else { 1078 userState[y*boardWidth+x] = UserSquare.flagged; 1079 } 1080 redraw(window); 1081 } 1082 } 1083 ); 1084 } 1085 } 1086 1087 /* 1088 version(OSX) { 1089 version=without_opengl; 1090 version=allow_unimplemented_features; 1091 version=OSXCocoa; 1092 pragma(linkerDirective, "-framework Cocoa"); 1093 } 1094 */ 1095 1096 version(without_opengl) { 1097 enum SdpyIsUsingIVGLBinds = false; 1098 } else /*version(Posix)*/ { 1099 static if (__traits(compiles, (){import iv.glbinds;})) { 1100 enum SdpyIsUsingIVGLBinds = true; 1101 public import iv.glbinds; 1102 //pragma(msg, "SDPY: using iv.glbinds"); 1103 } else { 1104 enum SdpyIsUsingIVGLBinds = false; 1105 } 1106 //} else { 1107 // enum SdpyIsUsingIVGLBinds = false; 1108 } 1109 1110 1111 version(Windows) { 1112 //import core.sys.windows.windows; 1113 import core.sys.windows.winnls; 1114 import core.sys.windows.windef; 1115 import core.sys.windows.basetyps; 1116 import core.sys.windows.winbase; 1117 import core.sys.windows.winuser; 1118 import core.sys.windows.shellapi; 1119 import core.sys.windows.wingdi; 1120 static import gdi = core.sys.windows.wingdi; // so i 1121 1122 pragma(lib, "gdi32"); 1123 pragma(lib, "user32"); 1124 1125 // for AlphaBlend... a breaking change.... 1126 version(CRuntime_DigitalMars) { } else 1127 pragma(lib, "msimg32"); 1128 } else version (linux) { 1129 //k8: this is hack for rdmd. sorry. 1130 static import core.sys.linux.epoll; 1131 static import core.sys.linux.timerfd; 1132 } 1133 1134 1135 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1136 1137 // http://wiki.dlang.org/Simpledisplay.d 1138 1139 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1140 1141 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1142 // but can i control the scroll lock led 1143 1144 1145 // Note: if you are using Image on X, you might want to do: 1146 /* 1147 static if(UsingSimpledisplayX11) { 1148 if(!Image.impl.xshmAvailable) { 1149 // the images will use the slower XPutImage, you might 1150 // want to consider an alternative method to get better speed 1151 } 1152 } 1153 1154 If the shared memory extension is available though, simpledisplay uses it 1155 for a significant speed boost whenever you draw large Images. 1156 */ 1157 1158 // 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. 1159 1160 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1161 1162 /* 1163 Biggest FIXME: 1164 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1165 1166 clean up opengl contexts when their windows close 1167 1168 fix resizing the bitmaps/pixmaps 1169 */ 1170 1171 // BTW on Windows: 1172 // -L/SUBSYSTEM:WINDOWS:5.0 1173 // to dmd will make a nice windows binary w/o a console if you want that. 1174 1175 /* 1176 Stuff to add: 1177 1178 use multibyte functions everywhere we can 1179 1180 OpenGL windows 1181 more event stuff 1182 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1183 1184 1185 resizeEvent 1186 and make the windows non-resizable by default, 1187 or perhaps stretched (if I can find something in X like StretchBlt) 1188 1189 take a screenshot function! 1190 1191 Pens and brushes? 1192 Maybe a global event loop? 1193 1194 Mouse deltas 1195 Key items 1196 */ 1197 1198 /* 1199 From MSDN: 1200 1201 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1202 1203 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. 1204 1205 */ 1206 1207 version(linux) { 1208 version = X11; 1209 version(without_libnotify) { 1210 // we cool 1211 } 1212 else 1213 version = libnotify; 1214 } 1215 1216 version(libnotify) { 1217 pragma(lib, "dl"); 1218 import core.sys.posix.dlfcn; 1219 1220 void delegate()[int] libnotify_action_delegates; 1221 int libnotify_action_delegates_count; 1222 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1223 auto idx = cast(int) user_data; 1224 if(auto dgptr = idx in libnotify_action_delegates) { 1225 (*dgptr)(); 1226 libnotify_action_delegates.remove(idx); 1227 } 1228 } 1229 1230 struct C_DynamicLibrary { 1231 void* handle; 1232 this(string name) { 1233 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1234 if(handle is null) 1235 throw new Exception("dlopen"); 1236 } 1237 1238 void close() { 1239 dlclose(handle); 1240 } 1241 1242 ~this() { 1243 // close 1244 } 1245 1246 // FIXME: this looks up by name every time.... 1247 template call(string func, Ret, Args...) { 1248 extern(C) Ret function(Args) fptr; 1249 typeof(fptr) call() { 1250 fptr = cast(typeof(fptr)) dlsym(handle, func); 1251 return fptr; 1252 } 1253 } 1254 } 1255 1256 C_DynamicLibrary* libnotify; 1257 } 1258 1259 version(OSX) { 1260 version(OSXCocoa) {} 1261 else { version = X11; } 1262 } 1263 //version = OSXCocoa; // this was written by KennyTM 1264 version(FreeBSD) 1265 version = X11; 1266 version(Solaris) 1267 version = X11; 1268 1269 version(X11) { 1270 version(without_xft) {} 1271 else version=with_xft; 1272 } 1273 1274 void featureNotImplemented()() { 1275 version(allow_unimplemented_features) 1276 throw new NotYetImplementedException(); 1277 else 1278 static assert(0); 1279 } 1280 1281 // these are so the static asserts don't trigger unless you want to 1282 // add support to it for an OS 1283 version(Windows) 1284 version = with_timer; 1285 version(linux) 1286 version = with_timer; 1287 1288 version(with_timer) 1289 enum bool SimpledisplayTimerAvailable = true; 1290 else 1291 enum bool SimpledisplayTimerAvailable = false; 1292 1293 /// 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. 1294 version(Windows) 1295 enum bool UsingSimpledisplayWindows = true; 1296 else 1297 enum bool UsingSimpledisplayWindows = false; 1298 1299 /// 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. 1300 version(X11) 1301 enum bool UsingSimpledisplayX11 = true; 1302 else 1303 enum bool UsingSimpledisplayX11 = false; 1304 1305 /// 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. 1306 version(OSXCocoa) 1307 enum bool UsingSimpledisplayCocoa = true; 1308 else 1309 enum bool UsingSimpledisplayCocoa = false; 1310 1311 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1312 version(Windows) 1313 enum multipleWindowsSupported = true; 1314 else version(X11) 1315 enum multipleWindowsSupported = true; 1316 else version(OSXCocoa) 1317 enum multipleWindowsSupported = true; 1318 else 1319 static assert(0); 1320 1321 version(without_opengl) 1322 enum bool OpenGlEnabled = false; 1323 else 1324 enum bool OpenGlEnabled = true; 1325 1326 /++ 1327 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1328 If you mix this in above your `main` function, you no longer need to use the linker 1329 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1330 1331 It does nothing if not compiling for Windows, so you need not version it out yourself. 1332 1333 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1334 stderr writeln. It will fail and throw an exception. 1335 1336 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1337 1338 History: 1339 Added November 24, 2021 (dub v10.4) 1340 +/ 1341 mixin template EnableWindowsSubsystem() { 1342 version(Windows) 1343 version(CRuntime_Microsoft) { 1344 pragma(linkerDirective, "/subsystem:windows"); 1345 version(LDC) 1346 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1347 else 1348 pragma(linkerDirective, "/entry:mainCRTStartup"); 1349 } 1350 } 1351 1352 1353 /++ 1354 After selecting a type from [WindowTypes], you may further customize 1355 its behavior by setting one or more of these flags. 1356 1357 1358 The different window types have different meanings of `normal`. If the 1359 window type already is a good match for what you want to do, you should 1360 just use [WindowFlags.normal], the default, which will do the right thing 1361 for your users. 1362 1363 The window flags will not always be honored by the operating system 1364 and window managers; they are hints, not commands. 1365 +/ 1366 enum WindowFlags : int { 1367 normal = 0, /// 1368 skipTaskbar = 1, /// 1369 alwaysOnTop = 2, /// 1370 alwaysOnBottom = 4, /// 1371 cannotBeActivated = 8, /// 1372 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. 1373 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. 1374 /++ 1375 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1376 it is still a top-level window. This should NOT be set separately for most window types. 1377 1378 A transient window will not keep the application open if its main window closes. 1379 1380 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1381 1382 1383 From the ICCM: 1384 1385 $(BLOCKQUOTE 1386 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. 1387 1388 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1389 ) 1390 1391 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1392 1393 History: 1394 Added February 23, 2021 but not yet stabilized. 1395 +/ 1396 transient = 64, 1397 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1398 } 1399 1400 /++ 1401 When creating a window, you can pass a type to SimpleWindow's constructor, 1402 then further customize the window by changing `WindowFlags`. 1403 1404 1405 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1406 use. The others are there to build a foundation for a higher level GUI toolkit, 1407 but are themselves not as high level as you might think from their names. 1408 1409 This list is based on the EMWH spec for X11. 1410 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1411 +/ 1412 enum WindowTypes : int { 1413 /// An ordinary application window. 1414 normal, 1415 /// 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. 1416 undecorated, 1417 /// 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. 1418 eventOnly, 1419 /// A drop down menu, such as from a menu bar 1420 dropdownMenu, 1421 /// A popup menu, such as from a right click 1422 popupMenu, 1423 /// A popup bubble notification 1424 notification, 1425 /* 1426 menu, /// a tearable menu bar 1427 splashScreen, /// a loading splash screen for your application 1428 tooltip, /// A tiny window showing temporary help text or something. 1429 comboBoxDropdown, 1430 dialog, 1431 toolbar 1432 */ 1433 /// a child nested inside the parent. You must pass a parent window to the ctor 1434 nestedChild, 1435 } 1436 1437 1438 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1439 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1440 private __gshared char* sdpyWindowClassStr = null; 1441 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1442 1443 /** 1444 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1445 You may want to change context version if you want to use advanced shaders or 1446 other modern OpenGL techinques. This setting doesn't affect already created 1447 windows. You may use version 2.1 as your default, which should be supported 1448 by any box since 2006, so seems to be a reasonable choice. 1449 1450 Note that by default version is set to `0`, which forces SimpleDisplay to use 1451 old context creation code without any version specified. This is the safest 1452 way to init OpenGL, but it may not give you access to advanced features. 1453 1454 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1455 */ 1456 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1457 1458 /** 1459 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1460 pipeline functions, and without "compatible" mode you won't be able to use 1461 your old non-shader-based code with such contexts. By default SimpleDisplay 1462 creates compatible context, so you can gradually upgrade your OpenGL code if 1463 you want to (or leave it as is, as it should "just work"). 1464 */ 1465 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1466 1467 /** 1468 Set to `true` to allow creating OpenGL context with lower version than requested 1469 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1470 `openGLContextFallbackActivated()` will return `true`. 1471 */ 1472 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1473 1474 /** 1475 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1476 */ 1477 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1478 1479 1480 /** 1481 Set window class name for all following `new SimpleWindow()` calls. 1482 1483 WARNING! For Windows, you should set your class name before creating any 1484 window, and NEVER change it after that! 1485 */ 1486 void sdpyWindowClass (const(char)[] v) { 1487 import core.stdc.stdlib : realloc; 1488 if (v.length == 0) v = "SimpleDisplayWindow"; 1489 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1490 if (sdpyWindowClassStr is null) return; // oops 1491 sdpyWindowClassStr[0..v.length+1] = 0; 1492 sdpyWindowClassStr[0..v.length] = v[]; 1493 } 1494 1495 /** 1496 Get current window class name. 1497 */ 1498 string sdpyWindowClass () { 1499 if (sdpyWindowClassStr is null) return null; 1500 foreach (immutable idx; 0..size_t.max-1) { 1501 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1502 } 1503 return null; 1504 } 1505 1506 /++ 1507 Returns the 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. 1508 1509 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1510 +/ 1511 float[2] getDpi() { 1512 float[2] dpi; 1513 version(Windows) { 1514 HDC screen = GetDC(null); 1515 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1516 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1517 } else version(X11) { 1518 auto display = XDisplayConnection.get; 1519 auto screen = DefaultScreen(display); 1520 1521 void fallback() { 1522 // 25.4 millimeters in an inch... 1523 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1524 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1525 } 1526 1527 char* resourceString = XResourceManagerString(display); 1528 XrmInitialize(); 1529 1530 auto db = XrmGetStringDatabase(resourceString); 1531 1532 if (resourceString) { 1533 XrmValue value; 1534 char* type; 1535 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1536 if (value.addr) { 1537 import core.stdc.stdlib; 1538 dpi[0] = atof(cast(char*) value.addr); 1539 dpi[1] = dpi[0]; 1540 } else { 1541 fallback(); 1542 } 1543 } else { 1544 fallback(); 1545 } 1546 } else { 1547 fallback(); 1548 } 1549 } 1550 1551 return dpi; 1552 } 1553 1554 /++ 1555 Implementation used by [SimpleWindow.takeScreenshot]. 1556 1557 Params: 1558 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1559 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1560 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1561 x = the x-offset of the image to capture, from the left. 1562 y = the y-offset of the image to capture, from the top. 1563 1564 History: 1565 Added on March 14, 2021 1566 1567 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1568 1569 +/ 1570 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1571 TrueColorImage got; 1572 version(X11) { 1573 auto display = XDisplayConnection.get; 1574 if(handle == 0) 1575 handle = RootWindow(display, DefaultScreen(display)); 1576 1577 if(width == 0 || height == 0) { 1578 Window root; 1579 int xpos, ypos; 1580 uint widthret, heightret, borderret, depthret; 1581 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1582 1583 if(width == 0) 1584 width = widthret; 1585 if(height == 0) 1586 height = heightret; 1587 } 1588 1589 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1590 1591 // https://github.com/adamdruppe/arsd/issues/98 1592 1593 auto i = new Image(image); 1594 got = i.toTrueColorImage(); 1595 1596 XDestroyImage(image); 1597 } else version(Windows) { 1598 auto hdc = GetDC(handle); 1599 scope(exit) ReleaseDC(handle, hdc); 1600 1601 if(width == 0 || height == 0) { 1602 BITMAP bmHeader; 1603 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1604 GetObject(bm, BITMAP.sizeof, &bmHeader); 1605 if(width == 0) 1606 width = bmHeader.bmWidth; 1607 if(height == 0) 1608 height = bmHeader.bmHeight; 1609 } 1610 1611 auto i = new Image(width, height); 1612 HDC hdcMem = CreateCompatibleDC(hdc); 1613 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1614 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1615 SelectObject(hdcMem, hbmOld); 1616 DeleteDC(hdcMem); 1617 1618 got = i.toTrueColorImage(); 1619 } else featureNotImplemented(); 1620 1621 return got; 1622 } 1623 1624 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1625 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1626 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1627 1628 version(Windows) 1629 shared static this() { 1630 auto lib = LoadLibrary("User32.dll"); 1631 if(lib is null) 1632 return; 1633 scope(exit) 1634 FreeLibrary(lib); 1635 1636 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1637 1638 if(SetProcessDpiAwarenessContext is null) 1639 return; 1640 1641 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1642 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1643 //writeln(GetLastError()); 1644 } 1645 1646 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1647 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1648 } 1649 1650 /++ 1651 The flagship window class. 1652 1653 1654 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1655 out of more advanced or complex features of the underlying windowing system. 1656 1657 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1658 and get a suitable window to work with. 1659 1660 From there, you can opt into additional features, like custom resizability and OpenGL support 1661 with the next two constructor arguments. Or, if you need even more, you can set a window type 1662 and customization flags with the final two constructor arguments. 1663 1664 If none of that works for you, you can also create a window using native function calls, then 1665 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1666 though, if you do this, managing the window is still your own responsibility! Notably, you 1667 will need to destroy it yourself. 1668 +/ 1669 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1670 1671 /++ 1672 Copies the window's current state into a [TrueColorImage]. 1673 1674 Be warned: this can be a very slow operation 1675 1676 History: 1677 Actually implemented on March 14, 2021 1678 +/ 1679 TrueColorImage takeScreenshot() { 1680 version(Windows) 1681 return trueColorImageFromNativeHandle(impl.hwnd, width, height); 1682 else version(OSXCocoa) 1683 throw new NotYetImplementedException(); 1684 else 1685 return trueColorImageFromNativeHandle(impl.window, width, height); 1686 } 1687 1688 /++ 1689 Returns the actual physical DPI for the window on its current display monitor. If the window 1690 straddles monitors, it will return the value of one or the other in a platform-defined manner. 1691 1692 Please note this function may return zero if it doesn't know the answer! 1693 1694 1695 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 1696 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 1697 1698 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 1699 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 1700 window primarily resides on by checking the center point of the window against the monitor map. 1701 1702 Returns: 1703 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 1704 assumes the X and Y dpi are the same. 1705 1706 History: 1707 Added November 26, 2021 (dub v10.4) 1708 1709 Bugs: 1710 Probably plenty. I haven't done a lot of tests on this. I know it doesn't 1711 1712 See_Also: 1713 [getDpi] gives the value provided for the default monitor. Not necessarily the same 1714 as this since the window many be on a different monitor, but it is a reasonable fallback 1715 to use if `actualDpi` returns 0. 1716 1717 [onDpiChanged] is changed when `actualDpi` has changed. 1718 +/ 1719 int actualDpi() { 1720 if(!actualDpiLoadAttempted) { 1721 // FIXME: do the actual monitor we are on 1722 // and on X this is a good chance to load the monitor map. 1723 version(Windows) { 1724 if(GetDpiForWindow) 1725 actualDpi_ = GetDpiForWindow(impl.hwnd); 1726 } else version(X11) { 1727 if(!xRandrInfoLoadAttemped) { 1728 xRandrInfoLoadAttemped = true; 1729 if(!XRandrLibrary.attempted) { 1730 XRandrLibrary.loadDynamicLibrary(); 1731 } 1732 1733 if(XRandrLibrary.loadSuccessful) { 1734 auto display = XDisplayConnection.get; 1735 int scratch; 1736 int major, minor; 1737 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 1738 goto fallback; 1739 1740 XRRQueryVersion(display, &major, &minor); 1741 if(major <= 1 && minor < 5) 1742 goto fallback; 1743 1744 int count; 1745 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 1746 if(monitors is null) 1747 goto fallback; 1748 scope(exit) XRRFreeMonitors(monitors); 1749 1750 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 1751 MonitorInfo.info.assumeSafeAppend(); 1752 foreach(monitor; monitors[0 .. count]) { 1753 if(monitor.mwidth == 0 || monitor.mheight == 0) 1754 // unknown physical size, just guess 96 to avoid divide by zero 1755 MonitorInfo.info ~= MonitorInfo( 1756 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1757 Size(monitor.mwidth, monitor.mheight), 1758 96 1759 ); 1760 else 1761 // and actual thing 1762 MonitorInfo.info ~= MonitorInfo( 1763 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1764 Size(monitor.mwidth, monitor.mheight), 1765 minInternal( 1766 // millimeter to int then rounding up. 1767 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 1768 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 1769 ) 1770 ); 1771 } 1772 //import std.stdio; writeln("Here", MonitorInfo.info); 1773 } 1774 } 1775 1776 if(XRandrLibrary.loadSuccessful) { 1777 updateActualDpi(true); 1778 //import std.stdio; writeln("updated"); 1779 1780 if(!requestedInput) { 1781 // this is what requests live updates should the configuration change 1782 // each time you select input, it sends an initial event, so very important 1783 // to not get into a loop of selecting input, getting event, updating data, 1784 // and reselecting input... 1785 requestedInput = true; 1786 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 1787 //import std.stdio; writeln("requested input"); 1788 } 1789 } else { 1790 fallback: 1791 // make sure we disable events that aren't coming 1792 xrrEventBase = -1; 1793 // best guess... 1794 actualDpi_ = cast(int) getDpi()[0]; 1795 } 1796 } 1797 actualDpiLoadAttempted = true; 1798 } 1799 return actualDpi_; 1800 } 1801 1802 private int actualDpi_; 1803 private bool actualDpiLoadAttempted; 1804 1805 version(X11) private { 1806 bool requestedInput; 1807 static bool xRandrInfoLoadAttemped; 1808 struct MonitorInfo { 1809 Rectangle position; 1810 Size size; 1811 int dpi; 1812 1813 static MonitorInfo[] info; 1814 } 1815 int screenPositionX; 1816 int screenPositionY; 1817 void updateActualDpi(bool loadingNow = false) { 1818 if(!loadingNow && !actualDpiLoadAttempted) 1819 actualDpi(); // just to make it do the load 1820 foreach(idx, m; MonitorInfo.info) { 1821 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 1822 bool changed = actualDpi_ && actualDpi_ != m.dpi; 1823 actualDpi_ = m.dpi; 1824 //import std.stdio; writeln("monitor ", idx); 1825 if(changed && onDpiChanged) 1826 onDpiChanged(); 1827 break; 1828 } 1829 } 1830 } 1831 } 1832 1833 /++ 1834 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 1835 or if the window is moved to a new remote connection or a monitor is hot-swapped. 1836 1837 History: 1838 Added November 26, 2021 (dub v10.4) 1839 1840 See_Also: 1841 [actualDpi] 1842 +/ 1843 void delegate() onDpiChanged; 1844 1845 version(X11) { 1846 void recreateAfterDisconnect() { 1847 if(!stateDiscarded) return; 1848 1849 if(_parent !is null && _parent.stateDiscarded) 1850 _parent.recreateAfterDisconnect(); 1851 1852 bool wasHidden = hidden; 1853 1854 activeScreenPainter = null; // should already be done but just to confirm 1855 1856 actualDpi_ = 0; 1857 actualDpiLoadAttempted = false; 1858 xRandrInfoLoadAttemped = false; 1859 1860 impl.createWindow(_width, _height, _title, openglMode, _parent); 1861 1862 if(auto dh = dropHandler) { 1863 dropHandler = null; 1864 enableDragAndDrop(this, dh); 1865 } 1866 1867 if(recreateAdditionalConnectionState) 1868 recreateAdditionalConnectionState(); 1869 1870 hidden = wasHidden; 1871 stateDiscarded = false; 1872 } 1873 1874 bool stateDiscarded; 1875 void discardConnectionState() { 1876 if(XDisplayConnection.display) 1877 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 1878 if(discardAdditionalConnectionState) 1879 discardAdditionalConnectionState(); 1880 stateDiscarded = true; 1881 } 1882 1883 void delegate() discardAdditionalConnectionState; 1884 void delegate() recreateAdditionalConnectionState; 1885 1886 } 1887 1888 private DropHandler dropHandler; 1889 1890 SimpleWindow _parent; 1891 bool beingOpenKeepsAppOpen = true; 1892 /++ 1893 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. 1894 1895 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 1896 1897 Params: 1898 1899 width = the width of the window's client area, in pixels 1900 height = the height of the window's client area, in pixels 1901 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 1902 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 1903 resizable = [Resizability] has three options: 1904 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 1905 $(P `fixedSize` will not allow the user to resize the window.) 1906 $(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.) 1907 windowType = The type of window you want to make. 1908 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. 1909 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". 1910 +/ 1911 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) { 1912 claimGuiThread(); 1913 version(sdpy_thread_checks) assert(thisIsGuiThread); 1914 this._width = width; 1915 this._height = height; 1916 this.openglMode = opengl; 1917 this.resizability = resizable; 1918 this.windowType = windowType; 1919 this.customizationFlags = customizationFlags; 1920 this._title = (title is null ? "D Application" : title); 1921 this._parent = parent; 1922 impl.createWindow(width, height, this._title, opengl, parent); 1923 1924 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 1925 beingOpenKeepsAppOpen = false; 1926 } 1927 1928 /// ditto 1929 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 1930 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 1931 } 1932 1933 /// Same as above, except using the `Size` struct instead of separate width and height. 1934 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 1935 this(size.width, size.height, title, opengl, resizable); 1936 } 1937 1938 /// ditto 1939 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 1940 this(size, title, opengl, resizable); 1941 } 1942 1943 1944 /++ 1945 Creates a window based on the given [Image]. It's client area 1946 width and height is equal to the image. (A window's client area 1947 is the drawable space inside; it excludes the title bar, etc.) 1948 1949 Windows based on images will not be resizable and do not use OpenGL. 1950 1951 It will draw the image in upon creation, but this will be overwritten 1952 upon any draws, including the initial window visible event. 1953 1954 You probably do not want to use this and it may be removed from 1955 the library eventually, or I might change it to be a "permanent" 1956 background image; one that is automatically drawn on it before any 1957 other drawing event. idk. 1958 +/ 1959 this(Image image, string title = null) { 1960 this(image.width, image.height, title); 1961 this.image = image; 1962 } 1963 1964 /++ 1965 Wraps a native window handle with very little additional processing - notably no destruction 1966 this is incomplete so don't use it for much right now. The purpose of this is to make native 1967 windows created through the low level API (so you can use platform-specific options and 1968 other details SimpleWindow does not expose) available to the event loop wrappers. 1969 +/ 1970 this(NativeWindowHandle nativeWindow) { 1971 version(Windows) 1972 impl.hwnd = nativeWindow; 1973 else version(X11) { 1974 impl.window = nativeWindow; 1975 if(nativeWindow) 1976 display = XDisplayConnection.get(); // get initial display to not segfault 1977 } else version(OSXCocoa) 1978 throw new NotYetImplementedException(); 1979 else featureNotImplemented(); 1980 // FIXME: set the size correctly 1981 _width = 1; 1982 _height = 1; 1983 if(nativeWindow) 1984 nativeMapping[nativeWindow] = this; 1985 1986 beingOpenKeepsAppOpen = false; 1987 1988 if(nativeWindow) 1989 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 1990 _suppressDestruction = true; // so it doesn't try to close 1991 } 1992 1993 /// Experimental, do not use yet 1994 /++ 1995 Grabs exclusive input from the user until you release it with 1996 [releaseInputGrab]. 1997 1998 1999 Note: it is extremely rude to do this without good reason. 2000 Reasons may include doing some kind of mouse drag operation 2001 or popping up a temporary menu that should get events and will 2002 be dismissed at ease by the user clicking away. 2003 2004 Params: 2005 keyboard = do you want to grab keyboard input? 2006 mouse = grab mouse input? 2007 confine = confine the mouse cursor to inside this window? 2008 2009 History: 2010 Prior to March 11, 2021, grabbing the keyboard would always also 2011 set the X input focus. Now, it only focuses if it is a non-transient 2012 window and otherwise manages the input direction internally. 2013 2014 This means spurious focus/blur events will no longer be sent and the 2015 application will not steal focus from other applications (which the 2016 window manager may have rejected anyway). 2017 +/ 2018 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2019 static if(UsingSimpledisplayX11) { 2020 XSync(XDisplayConnection.get, 0); 2021 if(keyboard) { 2022 if(isTransient && _parent) { 2023 /* 2024 FIXME: 2025 setting the keyboard focus is not actually that helpful, what I more likely want 2026 is the events from the parent window to be sent over here if we're transient. 2027 */ 2028 2029 _parent.inputProxy = this; 2030 } else { 2031 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 2032 } 2033 } 2034 if(mouse) { 2035 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2036 EventMask.PointerMotionMask // FIXME: not efficient 2037 | EventMask.ButtonPressMask 2038 | EventMask.ButtonReleaseMask 2039 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2040 ) 2041 { 2042 XSync(XDisplayConnection.get, 0); 2043 import core.stdc.stdio; 2044 printf("Grab input failed %d\n", res); 2045 //throw new Exception("Grab input failed"); 2046 } else { 2047 // cool 2048 } 2049 } 2050 2051 } else version(Windows) { 2052 // FIXME: keyboard? 2053 SetCapture(impl.hwnd); 2054 if(confine) { 2055 RECT rcClip; 2056 //RECT rcOldClip; 2057 //GetClipCursor(&rcOldClip); 2058 GetWindowRect(hwnd, &rcClip); 2059 ClipCursor(&rcClip); 2060 } 2061 } else version(OSXCocoa) { 2062 throw new NotYetImplementedException(); 2063 } else static assert(0); 2064 } 2065 2066 /++ 2067 Returns the native window. 2068 2069 History: 2070 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2071 to access it through the `impl` member (which is semi-supported 2072 but platform specific and here it is simple enough to offer an accessor). 2073 2074 Bugs: 2075 Not implemented outside Windows or X11. 2076 +/ 2077 NativeWindowHandle nativeWindowHandle() { 2078 version(X11) 2079 return impl.window; 2080 else version(Windows) 2081 return impl.hwnd; 2082 else 2083 throw new NotYetImplementedException(); 2084 } 2085 2086 private bool isTransient() { 2087 with(WindowTypes) 2088 final switch(windowType) { 2089 case normal, undecorated, eventOnly: 2090 case nestedChild: 2091 return (customizationFlags & WindowFlags.transient) ? true : false; 2092 case dropdownMenu, popupMenu, notification: 2093 return true; 2094 } 2095 } 2096 2097 private SimpleWindow inputProxy; 2098 2099 /++ 2100 Releases the grab acquired by [grabInput]. 2101 +/ 2102 void releaseInputGrab() { 2103 static if(UsingSimpledisplayX11) { 2104 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2105 if(_parent) 2106 _parent.inputProxy = null; 2107 } else version(Windows) { 2108 ReleaseCapture(); 2109 ClipCursor(null); 2110 } else version(OSXCocoa) { 2111 throw new NotYetImplementedException(); 2112 } else static assert(0); 2113 } 2114 2115 /++ 2116 Sets the input focus to this window. 2117 2118 You shouldn't call this very often - please let the user control the input focus. 2119 +/ 2120 void focus() { 2121 static if(UsingSimpledisplayX11) { 2122 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 2123 } else version(Windows) { 2124 SetFocus(this.impl.hwnd); 2125 } else version(OSXCocoa) { 2126 throw new NotYetImplementedException(); 2127 } else static assert(0); 2128 } 2129 2130 /++ 2131 Requests attention from the user for this window. 2132 2133 2134 The typical result of this function is to change the color 2135 of the taskbar icon, though it may be tweaked on specific 2136 platforms. 2137 2138 It is meant to unobtrusively tell the user that something 2139 relevant to them happened in the background and they should 2140 check the window when they get a chance. Upon receiving the 2141 keyboard focus, the window will automatically return to its 2142 natural state. 2143 2144 If the window already has the keyboard focus, this function 2145 may do nothing, because the user is presumed to already be 2146 giving the window attention. 2147 2148 Implementation_note: 2149 2150 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2151 atom on X11 and the FlashWindow function on Windows. 2152 +/ 2153 void requestAttention() { 2154 if(_focused) 2155 return; 2156 2157 version(Windows) { 2158 FLASHWINFO info; 2159 info.cbSize = info.sizeof; 2160 info.hwnd = impl.hwnd; 2161 info.dwFlags = FLASHW_TRAY; 2162 info.uCount = 1; 2163 2164 FlashWindowEx(&info); 2165 2166 } else version(X11) { 2167 demandingAttention = true; 2168 demandAttention(this, true); 2169 } else version(OSXCocoa) { 2170 throw new NotYetImplementedException(); 2171 } else static assert(0); 2172 } 2173 2174 private bool _focused; 2175 2176 version(X11) private bool demandingAttention; 2177 2178 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2179 /// You'll have to call `close()` manually if you set this delegate. 2180 void delegate () closeQuery; 2181 2182 /// This will be called when window visibility was changed. 2183 void delegate (bool becomesVisible) visibilityChanged; 2184 2185 /// This will be called when window becomes visible for the first time. 2186 /// You can do OpenGL initialization here. Note that in X11 you can't call 2187 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2188 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2189 private bool _visibleForTheFirstTimeCalled; 2190 void delegate () visibleForTheFirstTime; 2191 2192 /// Returns true if the window has been closed. 2193 final @property bool closed() { return _closed; } 2194 2195 /// Returns true if the window is focused. 2196 final @property bool focused() { return _focused; } 2197 2198 private bool _visible; 2199 /// Returns true if the window is visible (mapped). 2200 final @property bool visible() { return _visible; } 2201 2202 /// Closes the window. If there are no more open windows, the event loop will terminate. 2203 void close() { 2204 if (!_closed) { 2205 runInGuiThread( { 2206 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2207 if (onClosing !is null) onClosing(); 2208 impl.closeWindow(); 2209 _closed = true; 2210 } ); 2211 } 2212 } 2213 2214 /++ 2215 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2216 2217 History: 2218 Overload added on March 7, 2021. 2219 +/ 2220 void close() shared { 2221 (cast() this).close(); 2222 } 2223 2224 /++ 2225 2226 +/ 2227 void maximize() { 2228 version(Windows) 2229 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2230 else version(X11) { 2231 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2232 2233 // also note _NET_WM_STATE_FULLSCREEN 2234 } 2235 2236 } 2237 2238 private bool _fullscreen; 2239 2240 /// not fully implemented but planned for a future release 2241 void fullscreen(bool yes) { 2242 version(X11) 2243 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2244 2245 _fullscreen = yes; 2246 2247 } 2248 2249 bool fullscreen() { 2250 return _fullscreen; 2251 } 2252 2253 /++ 2254 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2255 2256 +/ 2257 void minimize() { 2258 version(Windows) 2259 ShowWindow(impl.hwnd, SW_MINIMIZE); 2260 //else version(X11) 2261 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2262 } 2263 2264 /// Alias for `hidden = false` 2265 void show() { 2266 hidden = false; 2267 } 2268 2269 /// Alias for `hidden = true` 2270 void hide() { 2271 hidden = true; 2272 } 2273 2274 /// Hide cursor when it enters the window. 2275 void hideCursor() { 2276 version(OSXCocoa) throw new NotYetImplementedException(); else 2277 if (!_closed) impl.hideCursor(); 2278 } 2279 2280 /// Don't hide cursor when it enters the window. 2281 void showCursor() { 2282 version(OSXCocoa) throw new NotYetImplementedException(); else 2283 if (!_closed) impl.showCursor(); 2284 } 2285 2286 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2287 * 2288 * Please remember that the cursor is a shared resource that should usually be left to the user's 2289 * control. Try to think for other approaches before using this function. 2290 * 2291 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2292 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2293 * receive "mouse moved here" event. 2294 */ 2295 bool warpMouse (int x, int y) { 2296 version(X11) { 2297 if (!_closed) { impl.warpMouse(x, y); return true; } 2298 } else version(Windows) { 2299 if (!_closed) { 2300 POINT point; 2301 point.x = x; 2302 point.y = y; 2303 if(ClientToScreen(impl.hwnd, &point)) { 2304 SetCursorPos(point.x, point.y); 2305 return true; 2306 } 2307 } 2308 } 2309 return false; 2310 } 2311 2312 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2313 void sendDummyEvent () { 2314 version(X11) { 2315 if (!_closed) { impl.sendDummyEvent(); } 2316 } 2317 } 2318 2319 /// Set window minimal size. 2320 void setMinSize (int minwidth, int minheight) { 2321 version(OSXCocoa) throw new NotYetImplementedException(); else 2322 if (!_closed) impl.setMinSize(minwidth, minheight); 2323 } 2324 2325 /// Set window maximal size. 2326 void setMaxSize (int maxwidth, int maxheight) { 2327 version(OSXCocoa) throw new NotYetImplementedException(); else 2328 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2329 } 2330 2331 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2332 /// Currently only supported on X11. 2333 void setResizeGranularity (int granx, int grany) { 2334 version(OSXCocoa) throw new NotYetImplementedException(); else 2335 if (!_closed) impl.setResizeGranularity(granx, grany); 2336 } 2337 2338 /// Move window. 2339 void move(int x, int y) { 2340 version(OSXCocoa) throw new NotYetImplementedException(); else 2341 if (!_closed) impl.move(x, y); 2342 } 2343 2344 /// ditto 2345 void move(Point p) { 2346 version(OSXCocoa) throw new NotYetImplementedException(); else 2347 if (!_closed) impl.move(p.x, p.y); 2348 } 2349 2350 /++ 2351 Resize window. 2352 2353 Note that the width and height of the window are NOT instantly 2354 updated - it waits for the window manager to approve the resize 2355 request, which means you must return to the event loop before the 2356 width and height are actually changed. 2357 +/ 2358 void resize(int w, int h) { 2359 if(!_closed && _fullscreen) fullscreen = false; 2360 version(OSXCocoa) throw new NotYetImplementedException(); else 2361 if (!_closed) impl.resize(w, h); 2362 } 2363 2364 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2365 void moveResize (int x, int y, int w, int h) { 2366 if(!_closed && _fullscreen) fullscreen = false; 2367 version(OSXCocoa) throw new NotYetImplementedException(); else 2368 if (!_closed) impl.moveResize(x, y, w, h); 2369 } 2370 2371 private bool _hidden; 2372 2373 /// Returns true if the window is hidden. 2374 final @property bool hidden() { 2375 return _hidden; 2376 } 2377 2378 /// Shows or hides the window based on the bool argument. 2379 final @property void hidden(bool b) { 2380 _hidden = b; 2381 version(Windows) { 2382 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2383 } else version(X11) { 2384 if(b) 2385 //XUnmapWindow(impl.display, impl.window); 2386 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2387 else 2388 XMapWindow(impl.display, impl.window); 2389 } else version(OSXCocoa) { 2390 throw new NotYetImplementedException(); 2391 } else static assert(0); 2392 } 2393 2394 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2395 void opacity(double opacity) @property 2396 in { 2397 assert(opacity >= 0 && opacity <= 1); 2398 } do { 2399 version (Windows) { 2400 impl.setOpacity(cast(ubyte)(255 * opacity)); 2401 } else version (X11) { 2402 impl.setOpacity(cast(uint)(uint.max * opacity)); 2403 } else throw new NotYetImplementedException(); 2404 } 2405 2406 /++ 2407 Sets your event handlers, without entering the event loop. Useful if you 2408 have multiple windows - set the handlers on each window, then only do eventLoop on your main window. 2409 +/ 2410 void setEventHandlers(T...)(T eventHandlers) { 2411 // FIXME: add more events 2412 foreach(handler; eventHandlers) { 2413 static if(__traits(compiles, handleKeyEvent = handler)) { 2414 handleKeyEvent = handler; 2415 } else static if(__traits(compiles, handleCharEvent = handler)) { 2416 handleCharEvent = handler; 2417 } else static if(__traits(compiles, handlePulse = handler)) { 2418 handlePulse = handler; 2419 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2420 handleMouseEvent = handler; 2421 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2422 } 2423 } 2424 2425 /// The event loop automatically returns when the window is closed 2426 /// pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2427 /// pulse timer is created. The event loop will block until an event 2428 /// arrives or the pulse timer goes off. 2429 final int eventLoop(T...)( 2430 long pulseTimeout, /// set to zero if you don't want a pulse. 2431 T eventHandlers) /// delegate list like std.concurrency.receive 2432 { 2433 setEventHandlers(eventHandlers); 2434 2435 version(with_eventloop) { 2436 // delegates event loop to my other module 2437 version(X11) 2438 XFlush(display); 2439 2440 import arsd.eventloop; 2441 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2442 scope(exit) clearInterval(handle); 2443 2444 loop(); 2445 return 0; 2446 } else version(OSXCocoa) { 2447 // FIXME 2448 if (handlePulse !is null && pulseTimeout != 0) { 2449 timer = scheduledTimer(pulseTimeout*1e-3, 2450 view, sel_registerName("simpledisplay_pulse"), 2451 null, true); 2452 } 2453 2454 setNeedsDisplay(view, true); 2455 run(NSApp); 2456 return 0; 2457 } else { 2458 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2459 return el.run(); 2460 } 2461 } 2462 2463 /++ 2464 This lets you draw on the window (or its backing buffer) using basic 2465 2D primitives. 2466 2467 Be sure to call this in a limited scope because your changes will not 2468 actually appear on the window until ScreenPainter's destructor runs. 2469 2470 Returns: an instance of [ScreenPainter], which has the drawing methods 2471 on it to draw on this window. 2472 +/ 2473 ScreenPainter draw() { 2474 return impl.getPainter(); 2475 } 2476 2477 // This is here to implement the interface we use for various native handlers. 2478 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2479 2480 // maps native window handles to SimpleWindow instances, if there are any 2481 // you shouldn't need this, but it is public in case you do in a native event handler or something 2482 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2483 2484 /// Width of the window's drawable client area, in pixels. 2485 @scriptable 2486 final @property int width() const pure nothrow @safe @nogc { return _width; } 2487 2488 /// Height of the window's drawable client area, in pixels. 2489 @scriptable 2490 final @property int height() const pure nothrow @safe @nogc { return _height; } 2491 2492 private int _width; 2493 private int _height; 2494 2495 // HACK: making the best of some copy constructor woes with refcounting 2496 private ScreenPainterImplementation* activeScreenPainter_; 2497 2498 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2499 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2500 2501 private OpenGlOptions openglMode; 2502 private Resizability resizability; 2503 private WindowTypes windowType; 2504 private int customizationFlags; 2505 2506 /// `true` if OpenGL was initialized for this window. 2507 @property bool isOpenGL () const pure nothrow @safe @nogc { 2508 version(without_opengl) 2509 return false; 2510 else 2511 return (openglMode == OpenGlOptions.yes); 2512 } 2513 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2514 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2515 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2516 2517 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2518 /// to call this, as it's not recommended to share window between threads. 2519 void mtLock () { 2520 version(X11) { 2521 XLockDisplay(this.display); 2522 } 2523 } 2524 2525 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2526 /// to call this, as it's not recommended to share window between threads. 2527 void mtUnlock () { 2528 version(X11) { 2529 XUnlockDisplay(this.display); 2530 } 2531 } 2532 2533 /// Emit a beep to get user's attention. 2534 void beep () { 2535 version(X11) { 2536 XBell(this.display, 100); 2537 } else version(Windows) { 2538 MessageBeep(0xFFFFFFFF); 2539 } 2540 } 2541 2542 2543 2544 version(without_opengl) {} else { 2545 2546 /// 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`. 2547 void delegate() redrawOpenGlScene; 2548 2549 /// This will allow you to change OpenGL vsync state. 2550 final @property void vsync (bool wait) { 2551 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2552 version(X11) { 2553 setAsCurrentOpenGlContext(); 2554 glxSetVSync(display, impl.window, wait); 2555 } else version(Windows) { 2556 setAsCurrentOpenGlContext(); 2557 wglSetVSync(wait); 2558 } 2559 } 2560 2561 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 2562 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 2563 /// enough without waiting 'em to finish their frame bussiness. 2564 bool useGLFinish = true; 2565 2566 // FIXME: it should schedule it for the end of the current iteration of the event loop... 2567 /// 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. 2568 void redrawOpenGlSceneNow() { 2569 version(X11) if (!this._visible) return; // no need to do this if window is invisible 2570 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2571 if(redrawOpenGlScene is null) 2572 return; 2573 2574 this.mtLock(); 2575 scope(exit) this.mtUnlock(); 2576 2577 this.setAsCurrentOpenGlContext(); 2578 2579 redrawOpenGlScene(); 2580 2581 this.swapOpenGlBuffers(); 2582 // 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. 2583 if (useGLFinish) glFinish(); 2584 } 2585 2586 private bool redrawOpenGlSceneSoonSet = false; 2587 private static class RedrawOpenGlSceneEvent { 2588 SimpleWindow w; 2589 this(SimpleWindow w) { this.w = w; } 2590 } 2591 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 2592 /++ 2593 Queues an opengl redraw as soon as the other pending events are cleared. 2594 +/ 2595 void redrawOpenGlSceneSoon() { 2596 if(!redrawOpenGlSceneSoonSet) { 2597 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 2598 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 2599 redrawOpenGlSceneSoonSet = true; 2600 } 2601 this.postEvent(redrawOpenGlSceneEvent, true); 2602 } 2603 2604 2605 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2606 void setAsCurrentOpenGlContext() { 2607 assert(openglMode == OpenGlOptions.yes); 2608 version(X11) { 2609 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 2610 throw new Exception("glXMakeCurrent"); 2611 } else version(Windows) { 2612 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2613 if (!wglMakeCurrent(ghDC, ghRC)) 2614 throw new Exception("wglMakeCurrent"); // let windows users suffer too 2615 } 2616 } 2617 2618 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2619 /// This doesn't throw, returning success flag instead. 2620 bool setAsCurrentOpenGlContextNT() nothrow { 2621 assert(openglMode == OpenGlOptions.yes); 2622 version(X11) { 2623 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 2624 } else version(Windows) { 2625 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2626 return wglMakeCurrent(ghDC, ghRC) ? true : false; 2627 } 2628 } 2629 2630 /// 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. 2631 /// This doesn't throw, returning success flag instead. 2632 bool releaseCurrentOpenGlContext() nothrow { 2633 assert(openglMode == OpenGlOptions.yes); 2634 version(X11) { 2635 return (glXMakeCurrent(display, 0, null) != 0); 2636 } else version(Windows) { 2637 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2638 return wglMakeCurrent(ghDC, null) ? true : false; 2639 } 2640 } 2641 2642 /++ 2643 simpledisplay always uses double buffering, usually automatically. This 2644 manually swaps the OpenGL buffers. 2645 2646 2647 You should not need to call this yourself because simpledisplay will do it 2648 for you after calling your `redrawOpenGlScene`. 2649 2650 Remember that this may throw an exception, which you can catch in a multithreaded 2651 application to keep your thread from dying from an unhandled exception. 2652 +/ 2653 void swapOpenGlBuffers() { 2654 assert(openglMode == OpenGlOptions.yes); 2655 version(X11) { 2656 if (!this._visible) return; // no need to do this if window is invisible 2657 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2658 glXSwapBuffers(display, impl.window); 2659 } else version(Windows) { 2660 SwapBuffers(ghDC); 2661 } 2662 } 2663 } 2664 2665 /++ 2666 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 2667 2668 2669 --- 2670 auto window = new SimpleWindow(100, 100, "First title"); 2671 window.title = "A new title"; 2672 --- 2673 2674 You may call this function at any time. 2675 +/ 2676 @property void title(string title) { 2677 _title = title; 2678 version(OSXCocoa) throw new NotYetImplementedException(); else 2679 impl.setTitle(title); 2680 } 2681 2682 private string _title; 2683 2684 /// Gets the title 2685 @property string title() { 2686 if(_title is null) 2687 _title = getRealTitle(); 2688 return _title; 2689 } 2690 2691 /++ 2692 Get the title as set by the window manager. 2693 May not match what you attempted to set. 2694 +/ 2695 string getRealTitle() { 2696 static if(is(typeof(impl.getTitle()))) 2697 return impl.getTitle(); 2698 else 2699 return null; 2700 } 2701 2702 // don't use this generally it is not yet really released 2703 version(X11) 2704 @property Image secret_icon() { 2705 return secret_icon_inner; 2706 } 2707 private Image secret_icon_inner; 2708 2709 2710 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. 2711 @property void icon(MemoryImage icon) { 2712 auto tci = icon.getAsTrueColorImage(); 2713 version(Windows) { 2714 winIcon = new WindowsIcon(icon); 2715 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 2716 } else version(X11) { 2717 secret_icon_inner = Image.fromMemoryImage(icon); 2718 // FIXME: ensure this is correct 2719 auto display = XDisplayConnection.get; 2720 arch_ulong[] buffer; 2721 buffer ~= icon.width; 2722 buffer ~= icon.height; 2723 foreach(c; tci.imageData.colors) { 2724 arch_ulong b; 2725 b |= c.a << 24; 2726 b |= c.r << 16; 2727 b |= c.g << 8; 2728 b |= c.b; 2729 buffer ~= b; 2730 } 2731 2732 XChangeProperty( 2733 display, 2734 impl.window, 2735 GetAtom!("_NET_WM_ICON", true)(display), 2736 GetAtom!"CARDINAL"(display), 2737 32 /* bits */, 2738 0 /*PropModeReplace*/, 2739 buffer.ptr, 2740 cast(int) buffer.length); 2741 } else version(OSXCocoa) { 2742 throw new NotYetImplementedException(); 2743 } else static assert(0); 2744 } 2745 2746 version(Windows) 2747 private WindowsIcon winIcon; 2748 2749 bool _suppressDestruction; 2750 2751 ~this() { 2752 if(_suppressDestruction) 2753 return; 2754 impl.dispose(); 2755 } 2756 2757 private bool _closed; 2758 2759 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 2760 /* 2761 ScreenPainter drawTransiently() { 2762 return impl.getPainter(); 2763 } 2764 */ 2765 2766 /// Draws an image on the window. This is meant to provide quick look 2767 /// of a static image generated elsewhere. 2768 @property void image(Image i) { 2769 /+ 2770 version(Windows) { 2771 BITMAP bm; 2772 HDC hdc = GetDC(hwnd); 2773 HDC hdcMem = CreateCompatibleDC(hdc); 2774 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 2775 2776 GetObject(i.handle, bm.sizeof, &bm); 2777 2778 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 2779 2780 SelectObject(hdcMem, hbmOld); 2781 DeleteDC(hdcMem); 2782 ReleaseDC(hwnd, hdc); 2783 2784 /* 2785 RECT r; 2786 r.right = i.width; 2787 r.bottom = i.height; 2788 InvalidateRect(hwnd, &r, false); 2789 */ 2790 } else 2791 version(X11) { 2792 if(!destroyed) { 2793 if(i.usingXshm) 2794 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 2795 else 2796 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 2797 } 2798 } else 2799 version(OSXCocoa) { 2800 draw().drawImage(Point(0, 0), i); 2801 setNeedsDisplay(view, true); 2802 } else static assert(0); 2803 +/ 2804 auto painter = this.draw; 2805 painter.drawImage(Point(0, 0), i); 2806 } 2807 2808 /++ 2809 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 2810 2811 --- 2812 window.cursor = GenericCursor.Help; 2813 // now the window mouse cursor is set to a generic help 2814 --- 2815 2816 +/ 2817 @property void cursor(MouseCursor cursor) { 2818 version(OSXCocoa) 2819 featureNotImplemented(); 2820 else 2821 if(this.impl.curHidden <= 0) { 2822 static if(UsingSimpledisplayX11) { 2823 auto ch = cursor.cursorHandle; 2824 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 2825 } else version(Windows) { 2826 auto ch = cursor.cursorHandle; 2827 impl.currentCursor = ch; 2828 SetCursor(ch); // redraw without waiting for mouse movement to update 2829 } else featureNotImplemented(); 2830 } 2831 2832 } 2833 2834 /// What follows are the event handlers. These are set automatically 2835 /// by the eventLoop function, but are still public so you can change 2836 /// them later. wasPressed == true means key down. false == key up. 2837 2838 /// Handles a low-level keyboard event. Settable through setEventHandlers. 2839 void delegate(KeyEvent ke) handleKeyEvent; 2840 2841 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 2842 void delegate(dchar c) handleCharEvent; 2843 2844 /// Handles a timer pulse. Settable through setEventHandlers. 2845 void delegate() handlePulse; 2846 2847 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 2848 void delegate(bool) onFocusChange; 2849 2850 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 2851 * Sometimes it is easier to setup the delegate instead of subclassing. */ 2852 void delegate() onClosing; 2853 2854 /** Called when we received destroy notification. At this stage we cannot do much with our window 2855 * (as it is already dead, and it's native handle cannot be used), but we still can do some 2856 * last minute cleanup. */ 2857 void delegate() onDestroyed; 2858 2859 static if (UsingSimpledisplayX11) 2860 /** Called when Expose event comes. See Xlib manual to understand the arguments. 2861 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 2862 * You will probably never need to setup this handler, it is for very low-level stuff. 2863 * 2864 * WARNING! Xlib is multithread-locked when this handles is called! */ 2865 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 2866 2867 //version(Windows) 2868 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 2869 2870 private { 2871 int lastMouseX = int.min; 2872 int lastMouseY = int.min; 2873 void mdx(ref MouseEvent ev) { 2874 if(lastMouseX == int.min || lastMouseY == int.min) { 2875 ev.dx = 0; 2876 ev.dy = 0; 2877 } else { 2878 ev.dx = ev.x - lastMouseX; 2879 ev.dy = ev.y - lastMouseY; 2880 } 2881 2882 lastMouseX = ev.x; 2883 lastMouseY = ev.y; 2884 } 2885 } 2886 2887 /// Mouse event handler. Settable through setEventHandlers. 2888 void delegate(MouseEvent) handleMouseEvent; 2889 2890 /// use to redraw child widgets if you use system apis to add stuff 2891 void delegate() paintingFinished; 2892 2893 void delegate() paintingFinishedDg() { 2894 return paintingFinished; 2895 } 2896 2897 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 2898 /// for this to ever happen. 2899 void delegate(int width, int height) windowResized; 2900 2901 /++ 2902 Platform specific - handle any native message this window gets. 2903 2904 Note: this is called *in addition to* other event handlers, unless you either: 2905 2906 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 2907 2908 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. 2909 2910 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 2911 2912 On X, it takes the form of `int delegate(XEvent)`. 2913 2914 History: 2915 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 2916 2917 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. 2918 +/ 2919 NativeEventHandler handleNativeEvent_; 2920 2921 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 2922 return handleNativeEvent_; 2923 } 2924 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 2925 handleNativeEvent_ = neh; 2926 } 2927 2928 version(Windows) 2929 // compatibility shim with the old deprecated way 2930 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 2931 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) { 2932 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 2933 auto ret = dg(h, m, w, l); 2934 if(ret == 0) 2935 r = 1; 2936 return ret; 2937 }; 2938 } 2939 2940 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 2941 /// If you used to use handleNativeEvent depending on it being static, just change it to use 2942 /// this instead and it will work the same way. 2943 __gshared NativeEventHandler handleNativeGlobalEvent; 2944 2945 // private: 2946 /// The native implementation is available, but you shouldn't use it unless you are 2947 /// familiar with the underlying operating system, don't mind depending on it, and 2948 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 2949 /// do what you need to do with handleNativeEvent instead. 2950 /// 2951 /// This is likely to eventually change to be just a struct holding platform-specific 2952 /// handles instead of a template mixin at some point because I'm not happy with the 2953 /// code duplication here (ironically). 2954 mixin NativeSimpleWindowImplementation!() impl; 2955 2956 /** 2957 This is in-process one-way (from anything to window) event sending mechanics. 2958 It is thread-safe, so it can be used in multi-threaded applications to send, 2959 for example, "wake up and repaint" events when thread completed some operation. 2960 This will allow to avoid using timer pulse to check events with synchronization, 2961 'cause event handler will be called in UI thread. You can stop guessing which 2962 pulse frequency will be enough for your app. 2963 Note that events handlers may be called in arbitrary order, i.e. last registered 2964 handler can be called first, and vice versa. 2965 */ 2966 public: 2967 /** Is our custom event queue empty? Can be used in simple cases to prevent 2968 * "spamming" window with events it can't cope with. 2969 * It is safe to call this from non-UI threads. 2970 */ 2971 @property bool eventQueueEmpty() () { 2972 synchronized(this) { 2973 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 2974 } 2975 return true; 2976 } 2977 2978 /** Does our custom event queue contains at least one with the given type? 2979 * Can be used in simple cases to prevent "spamming" window with events 2980 * it can't cope with. 2981 * It is safe to call this from non-UI threads. 2982 */ 2983 @property bool eventQueued(ET:Object) () { 2984 synchronized(this) { 2985 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 2986 if (!o.doProcess) { 2987 if (cast(ET)(o.evt)) return true; 2988 } 2989 } 2990 } 2991 return false; 2992 } 2993 2994 /++ 2995 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 2996 2997 History: 2998 Added May 12, 2021 2999 +/ 3000 void delegate(Exception e) nothrow eventUncaughtException; 3001 3002 /** Add listener for custom event. Can be used like this: 3003 * 3004 * --------------------- 3005 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3006 * ... 3007 * win.removeEventListener(eid); 3008 * --------------------- 3009 * 3010 * Returns: 0 on failure (should never happen, so ignore it) 3011 * 3012 * $(WARNING Don't use this method in object destructors!) 3013 * 3014 * $(WARNING It is better to register all event handlers and don't remove 'em, 3015 * 'cause if event handler id counter will overflow, you won't be able 3016 * to register any more events.) 3017 */ 3018 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3019 if (dg is null) return 0; // ignore empty handlers 3020 synchronized(this) { 3021 //FIXME: abort on overflow? 3022 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3023 EventHandlerEntry e; 3024 e.dg = delegate (Object o) { 3025 if (auto co = cast(ET)o) { 3026 try { 3027 dg(co); 3028 } catch (Exception e) { 3029 // sorry! 3030 if(eventUncaughtException) 3031 eventUncaughtException(e); 3032 } 3033 return true; 3034 } 3035 return false; 3036 }; 3037 e.id = lastUsedHandlerId; 3038 auto optr = eventHandlers.ptr; 3039 eventHandlers ~= e; 3040 if (eventHandlers.ptr !is optr) { 3041 import core.memory : GC; 3042 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3043 } 3044 return lastUsedHandlerId; 3045 } 3046 } 3047 3048 /// Remove event listener. It is safe to pass invalid event id here. 3049 /// $(WARNING Don't use this method in object destructors!) 3050 void removeEventListener() (uint id) { 3051 if (id == 0 || id > lastUsedHandlerId) return; 3052 synchronized(this) { 3053 foreach (immutable idx; 0..eventHandlers.length) { 3054 if (eventHandlers[idx].id == id) { 3055 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3056 eventHandlers[$-1].dg = null; 3057 eventHandlers.length -= 1; 3058 eventHandlers.assumeSafeAppend; 3059 return; 3060 } 3061 } 3062 } 3063 } 3064 3065 /// Post event to queue. It is safe to call this from non-UI threads. 3066 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3067 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3068 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3069 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3070 if (this.closed) return false; // closed windows can't handle events 3071 3072 // remove all events of type `ET` 3073 void removeAllET () { 3074 uint eidx = 0, ec = eventQueueUsed; 3075 auto eptr = eventQueue.ptr; 3076 while (eidx < ec) { 3077 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3078 if (cast(ET)eptr.evt !is null) { 3079 // i found her! 3080 if (inCustomEventProcessor) { 3081 // if we're in custom event processing loop, processor will clear it for us 3082 eptr.evt = null; 3083 ++eidx; 3084 ++eptr; 3085 } else { 3086 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3087 ec = --eventQueueUsed; 3088 // clear last event (it is already copied) 3089 eventQueue.ptr[ec].evt = null; 3090 } 3091 } else { 3092 ++eidx; 3093 ++eptr; 3094 } 3095 } 3096 } 3097 3098 if (evt is null) { 3099 if (replace) { synchronized(this) removeAllET(); } 3100 // ignore empty events, they can't be handled anyway 3101 return false; 3102 } 3103 3104 // add events even if no event FD/event object created yet 3105 synchronized(this) { 3106 if (replace) removeAllET(); 3107 if (eventQueueUsed == uint.max) return false; // just in case 3108 if (eventQueueUsed < eventQueue.length) { 3109 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3110 } else { 3111 if (eventQueue.capacity == eventQueue.length) { 3112 // need to reallocate; do a trick to ensure that old array is cleared 3113 auto oarr = eventQueue; 3114 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3115 // just in case, do yet another check 3116 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3117 import core.memory : GC; 3118 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3119 } else { 3120 auto optr = eventQueue.ptr; 3121 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3122 assert(eventQueue.ptr is optr); 3123 } 3124 ++eventQueueUsed; 3125 assert(eventQueueUsed == eventQueue.length); 3126 } 3127 if (!eventWakeUp()) { 3128 // can't wake up event processor, so there is no reason to keep the event 3129 assert(eventQueueUsed > 0); 3130 eventQueue[--eventQueueUsed].evt = null; 3131 return false; 3132 } 3133 return true; 3134 } 3135 } 3136 3137 /// Post event to queue. It is safe to call this from non-UI threads. 3138 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3139 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3140 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3141 return postTimeout!ET(evt, 0, replace); 3142 } 3143 3144 private: 3145 private import core.time : MonoTime; 3146 3147 version(Posix) { 3148 __gshared int customEventFDRead = -1; 3149 __gshared int customEventFDWrite = -1; 3150 __gshared int customSignalFD = -1; 3151 } else version(Windows) { 3152 __gshared HANDLE customEventH = null; 3153 } 3154 3155 // wake up event processor 3156 static bool eventWakeUp () { 3157 version(X11) { 3158 import core.sys.posix.unistd : write; 3159 ulong n = 1; 3160 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3161 return true; 3162 } else version(Windows) { 3163 if (customEventH !is null) SetEvent(customEventH); 3164 return true; 3165 } else { 3166 // not implemented for other OSes 3167 return false; 3168 } 3169 } 3170 3171 static struct QueuedEvent { 3172 Object evt; 3173 bool timed = false; 3174 MonoTime hittime = MonoTime.zero; 3175 bool doProcess = false; // process event at the current iteration (internal flag) 3176 3177 this (Object aevt, uint toutmsecs) { 3178 evt = aevt; 3179 if (toutmsecs > 0) { 3180 import core.time : msecs; 3181 timed = true; 3182 hittime = MonoTime.currTime+toutmsecs.msecs; 3183 } 3184 } 3185 } 3186 3187 alias CustomEventHandler = bool delegate (Object o) nothrow; 3188 static struct EventHandlerEntry { 3189 CustomEventHandler dg; 3190 uint id; 3191 } 3192 3193 uint lastUsedHandlerId; 3194 EventHandlerEntry[] eventHandlers; 3195 QueuedEvent[] eventQueue = null; 3196 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3197 bool inCustomEventProcessor = false; // required to properly remove events 3198 3199 // process queued events and call custom event handlers 3200 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3201 void processCustomEvents () { 3202 bool hasSomethingToDo = false; 3203 uint ecount; 3204 bool ocep; 3205 synchronized(this) { 3206 ocep = inCustomEventProcessor; 3207 inCustomEventProcessor = true; 3208 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3209 auto ctt = MonoTime.currTime; 3210 bool hasEmpty = false; 3211 // mark events to process (this is required for `eventQueued()`) 3212 foreach (ref qe; eventQueue[0..ecount]) { 3213 if (qe.evt is null) { hasEmpty = true; continue; } 3214 if (qe.timed) { 3215 qe.doProcess = (qe.hittime <= ctt); 3216 } else { 3217 qe.doProcess = true; 3218 } 3219 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3220 } 3221 if (!hasSomethingToDo) { 3222 // remove empty events 3223 if (hasEmpty) { 3224 uint eidx = 0, ec = eventQueueUsed; 3225 auto eptr = eventQueue.ptr; 3226 while (eidx < ec) { 3227 if (eptr.evt is null) { 3228 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3229 ec = --eventQueueUsed; 3230 eventQueue.ptr[ec].evt = null; // make GC life easier 3231 } else { 3232 ++eidx; 3233 ++eptr; 3234 } 3235 } 3236 } 3237 inCustomEventProcessor = ocep; 3238 return; 3239 } 3240 } 3241 // process marked events 3242 uint efree = 0; // non-processed events will be put at this index 3243 EventHandlerEntry[] eh; 3244 Object evt; 3245 foreach (immutable eidx; 0..ecount) { 3246 synchronized(this) { 3247 if (!eventQueue[eidx].doProcess) { 3248 // skip this event 3249 assert(efree <= eidx); 3250 if (efree != eidx) { 3251 // copy this event to queue start 3252 eventQueue[efree] = eventQueue[eidx]; 3253 eventQueue[eidx].evt = null; // just in case 3254 } 3255 ++efree; 3256 continue; 3257 } 3258 evt = eventQueue[eidx].evt; 3259 eventQueue[eidx].evt = null; // in case event handler will hit GC 3260 if (evt is null) continue; // just in case 3261 // try all handlers; this can be slow, but meh... 3262 eh = eventHandlers; 3263 } 3264 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3265 evt = null; 3266 eh = null; 3267 } 3268 synchronized(this) { 3269 // move all unprocessed events to queue top; efree holds first "free index" 3270 foreach (immutable eidx; ecount..eventQueueUsed) { 3271 assert(efree <= eidx); 3272 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3273 ++efree; 3274 } 3275 eventQueueUsed = efree; 3276 // wake up event processor on next event loop iteration if we have more queued events 3277 // also, remove empty events 3278 bool awaken = false; 3279 uint eidx = 0, ec = eventQueueUsed; 3280 auto eptr = eventQueue.ptr; 3281 while (eidx < ec) { 3282 if (eptr.evt is null) { 3283 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3284 ec = --eventQueueUsed; 3285 eventQueue.ptr[ec].evt = null; // make GC life easier 3286 } else { 3287 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3288 ++eidx; 3289 ++eptr; 3290 } 3291 } 3292 inCustomEventProcessor = ocep; 3293 } 3294 } 3295 3296 // for all windows in nativeMapping 3297 static void processAllCustomEvents () { 3298 3299 justCommunication.processCustomEvents(); 3300 3301 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3302 if (sw is null || sw.closed) continue; 3303 sw.processCustomEvents(); 3304 } 3305 3306 runPendingRunInGuiThreadDelegates(); 3307 } 3308 3309 // 0: infinite (i.e. no scheduled events in queue) 3310 uint eventQueueTimeoutMSecs () { 3311 synchronized(this) { 3312 if (eventQueueUsed == 0) return 0; 3313 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3314 uint res = int.max; 3315 auto ctt = MonoTime.currTime; 3316 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3317 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3318 if (qe.doProcess) continue; // just in case 3319 if (!qe.timed) return 1; // minimal 3320 if (qe.hittime <= ctt) return 1; // minimal 3321 auto tms = (qe.hittime-ctt).total!"msecs"; 3322 if (tms < 1) tms = 1; // safety net 3323 if (tms >= int.max) tms = int.max-1; // and another safety net 3324 if (res > tms) res = cast(uint)tms; 3325 } 3326 return (res >= int.max ? 0 : res); 3327 } 3328 } 3329 3330 // for all windows in nativeMapping 3331 static uint eventAllQueueTimeoutMSecs () { 3332 uint res = uint.max; 3333 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3334 if (sw is null || sw.closed) continue; 3335 uint to = sw.eventQueueTimeoutMSecs(); 3336 if (to && to < res) { 3337 res = to; 3338 if (to == 1) break; // can't have less than this 3339 } 3340 } 3341 return (res >= int.max ? 0 : res); 3342 } 3343 } 3344 3345 /++ 3346 Magic pseudo-window for just posting events to a global queue. 3347 3348 Not entirely supported, I might delete it at any time. 3349 3350 Added Nov 5, 2021. 3351 +/ 3352 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init); 3353 3354 /* Drag and drop support { */ 3355 version(X11) { 3356 3357 } else version(Windows) { 3358 import core.sys.windows.uuid; 3359 import core.sys.windows.ole2; 3360 import core.sys.windows.oleidl; 3361 import core.sys.windows.objidl; 3362 import core.sys.windows.wtypes; 3363 3364 pragma(lib, "ole32"); 3365 void initDnd() { 3366 auto err = OleInitialize(null); 3367 if(err != S_OK && err != S_FALSE) 3368 throw new Exception("init");//err); 3369 } 3370 } 3371 /* } End drag and drop support */ 3372 3373 3374 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 3375 /// See [GenericCursor]. 3376 class MouseCursor { 3377 int osId; 3378 bool isStockCursor; 3379 private this(int osId) { 3380 this.osId = osId; 3381 this.isStockCursor = true; 3382 } 3383 3384 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 3385 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 3386 3387 version(Windows) { 3388 HCURSOR cursor_; 3389 HCURSOR cursorHandle() { 3390 if(cursor_ is null) 3391 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 3392 return cursor_; 3393 } 3394 3395 } else static if(UsingSimpledisplayX11) { 3396 Cursor cursor_ = None; 3397 int xDisplaySequence; 3398 3399 Cursor cursorHandle() { 3400 if(this.osId == None) 3401 return None; 3402 3403 // we need to reload if we on a new X connection 3404 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 3405 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 3406 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 3407 } 3408 return cursor_; 3409 } 3410 } 3411 } 3412 3413 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 3414 // https://tronche.com/gui/x/xlib/appendix/b/ 3415 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 3416 /// 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. 3417 enum GenericCursorType { 3418 Default, /// The default arrow pointer. 3419 Wait, /// A cursor indicating something is loading and the user must wait. 3420 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3421 Help, /// A cursor indicating the user can get help about the pointer location. 3422 Cross, /// A crosshair. 3423 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3424 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 3425 UpArrow, /// An arrow pointing straight up. 3426 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3427 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3428 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 3429 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 3430 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 3431 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 3432 3433 } 3434 3435 /* 3436 X_plus == css cell == Windows ? 3437 */ 3438 3439 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3440 static struct GenericCursor { 3441 static: 3442 /// 3443 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3444 static MouseCursor mc; 3445 3446 auto type = __traits(getMember, GenericCursorType, str); 3447 3448 if(mc is null) { 3449 3450 version(Windows) { 3451 int osId; 3452 final switch(type) { 3453 case GenericCursorType.Default: osId = IDC_ARROW; break; 3454 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3455 case GenericCursorType.Hand: osId = IDC_HAND; break; 3456 case GenericCursorType.Help: osId = IDC_HELP; break; 3457 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3458 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3459 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3460 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3461 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3462 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3463 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3464 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3465 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3466 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3467 } 3468 } else static if(UsingSimpledisplayX11) { 3469 int osId; 3470 final switch(type) { 3471 case GenericCursorType.Default: osId = None; break; 3472 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3473 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3474 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3475 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3476 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3477 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3478 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3479 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3480 3481 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3482 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3483 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3484 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3485 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3486 } 3487 3488 } else featureNotImplemented(); 3489 3490 mc = new MouseCursor(osId); 3491 } 3492 return mc; 3493 } 3494 } 3495 3496 3497 /++ 3498 If you want to get more control over the event loop, you can use this. 3499 3500 Typically though, you can just call [SimpleWindow.eventLoop]. 3501 +/ 3502 struct EventLoop { 3503 @disable this(); 3504 3505 /// Gets a reference to an existing event loop 3506 static EventLoop get() { 3507 return EventLoop(0, null); 3508 } 3509 3510 __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 3511 3512 /// Construct an application-global event loop for yourself 3513 /// See_Also: [SimpleWindow.setEventHandlers] 3514 this(long pulseTimeout, void delegate() handlePulse) { 3515 synchronized(monitor) { 3516 if(impl is null) { 3517 claimGuiThread(); 3518 version(sdpy_thread_checks) assert(thisIsGuiThread); 3519 impl = new EventLoopImpl(pulseTimeout, handlePulse); 3520 } else { 3521 if(pulseTimeout) { 3522 impl.pulseTimeout = pulseTimeout; 3523 impl.handlePulse = handlePulse; 3524 } 3525 } 3526 impl.refcount++; 3527 } 3528 } 3529 3530 ~this() { 3531 if(impl is null) 3532 return; 3533 impl.refcount--; 3534 if(impl.refcount == 0) { 3535 impl.dispose(); 3536 if(thisIsGuiThread) 3537 guiThreadFinalize(); 3538 } 3539 3540 } 3541 3542 this(this) { 3543 if(impl is null) 3544 return; 3545 impl.refcount++; 3546 } 3547 3548 /// Runs the event loop until the whileCondition, if present, returns false 3549 int run(bool delegate() whileCondition = null) { 3550 assert(impl !is null); 3551 impl.notExited = true; 3552 return impl.run(whileCondition); 3553 } 3554 3555 /// Exits the event loop 3556 void exit() { 3557 assert(impl !is null); 3558 impl.notExited = false; 3559 } 3560 3561 version(linux) 3562 ref void delegate(int) signalHandler() { 3563 assert(impl !is null); 3564 return impl.signalHandler; 3565 } 3566 3567 __gshared static EventLoopImpl* impl; 3568 } 3569 3570 version(linux) 3571 void delegate(int, int) globalHupHandler; 3572 3573 version(Posix) 3574 void makeNonBlocking(int fd) { 3575 import fcntl = core.sys.posix.fcntl; 3576 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 3577 if(flags == -1) 3578 throw new Exception("fcntl get"); 3579 flags |= fcntl.O_NONBLOCK; 3580 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 3581 if(s == -1) 3582 throw new Exception("fcntl set"); 3583 } 3584 3585 struct EventLoopImpl { 3586 int refcount; 3587 3588 bool notExited = true; 3589 3590 version(linux) { 3591 static import ep = core.sys.linux.epoll; 3592 static import unix = core.sys.posix.unistd; 3593 static import err = core.stdc.errno; 3594 import core.sys.linux.timerfd; 3595 3596 void delegate(int) signalHandler; 3597 } 3598 3599 version(X11) { 3600 int pulseFd = -1; 3601 version(linux) ep.epoll_event[16] events = void; 3602 } else version(Windows) { 3603 Timer pulser; 3604 HANDLE[] handles; 3605 } 3606 3607 3608 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3609 /// to call this, as it's not recommended to share window between threads. 3610 void mtLock () { 3611 version(X11) { 3612 XLockDisplay(this.display); 3613 } 3614 } 3615 3616 version(X11) 3617 auto display() { return XDisplayConnection.get; } 3618 3619 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 3620 /// to call this, as it's not recommended to share window between threads. 3621 void mtUnlock () { 3622 version(X11) { 3623 XUnlockDisplay(this.display); 3624 } 3625 } 3626 3627 version(with_eventloop) 3628 void initialize(long pulseTimeout) {} 3629 else 3630 void initialize(long pulseTimeout) { 3631 version(Windows) { 3632 if(pulseTimeout && handlePulse !is null) 3633 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 3634 3635 if (customEventH is null) { 3636 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 3637 if (customEventH !is null) { 3638 handles ~= customEventH; 3639 } else { 3640 // this is something that should not be; better be safe than sorry 3641 throw new Exception("can't create eventfd for custom event processing"); 3642 } 3643 } 3644 3645 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 3646 } 3647 3648 version(linux) { 3649 prepareEventLoop(); 3650 { 3651 auto display = XDisplayConnection.get; 3652 // adding Xlib file 3653 ep.epoll_event ev = void; 3654 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3655 ev.events = ep.EPOLLIN; 3656 ev.data.fd = display.fd; 3657 //import std.conv; 3658 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 3659 throw new Exception("add x fd");// ~ to!string(epollFd)); 3660 displayFd = display.fd; 3661 } 3662 3663 if(pulseTimeout && handlePulse !is null) { 3664 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 3665 if(pulseFd == -1) 3666 throw new Exception("pulse timer create failed"); 3667 3668 itimerspec value; 3669 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 3670 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 3671 3672 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 3673 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 3674 3675 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 3676 throw new Exception("couldn't make pulse timer"); 3677 3678 ep.epoll_event ev = void; 3679 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3680 ev.events = ep.EPOLLIN; 3681 ev.data.fd = pulseFd; 3682 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 3683 } 3684 3685 // eventfd for custom events 3686 if (customEventFDWrite == -1) { 3687 customEventFDWrite = eventfd(0, 0); 3688 customEventFDRead = customEventFDWrite; 3689 if (customEventFDRead >= 0) { 3690 ep.epoll_event ev = void; 3691 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3692 ev.events = ep.EPOLLIN; 3693 ev.data.fd = customEventFDRead; 3694 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 3695 } else { 3696 // this is something that should not be; better be safe than sorry 3697 throw new Exception("can't create eventfd for custom event processing"); 3698 } 3699 } 3700 3701 if (customSignalFD == -1) { 3702 import core.sys.linux.sys.signalfd; 3703 3704 sigset_t sigset; 3705 auto err = sigemptyset(&sigset); 3706 assert(!err); 3707 err = sigaddset(&sigset, SIGINT); 3708 assert(!err); 3709 err = sigaddset(&sigset, SIGHUP); 3710 assert(!err); 3711 err = sigprocmask(SIG_BLOCK, &sigset, null); 3712 assert(!err); 3713 3714 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 3715 assert(customSignalFD != -1); 3716 3717 ep.epoll_event ev = void; 3718 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3719 ev.events = ep.EPOLLIN; 3720 ev.data.fd = customSignalFD; 3721 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 3722 } 3723 } else version(Posix) { 3724 prepareEventLoop(); 3725 if (customEventFDRead == -1) { 3726 int[2] bfr; 3727 import core.sys.posix.unistd; 3728 auto ret = pipe(bfr); 3729 if(ret == -1) throw new Exception("pipe"); 3730 customEventFDRead = bfr[0]; 3731 customEventFDWrite = bfr[1]; 3732 } 3733 3734 } 3735 3736 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 3737 3738 version(linux) { 3739 this.mtLock(); 3740 scope(exit) this.mtUnlock(); 3741 XPending(display); // no, really 3742 } 3743 3744 disposed = false; 3745 } 3746 3747 bool disposed = true; 3748 version(X11) 3749 int displayFd = -1; 3750 3751 version(with_eventloop) 3752 void dispose() {} 3753 else 3754 void dispose() { 3755 disposed = true; 3756 version(X11) { 3757 if(pulseFd != -1) { 3758 import unix = core.sys.posix.unistd; 3759 unix.close(pulseFd); 3760 pulseFd = -1; 3761 } 3762 3763 version(linux) 3764 if(displayFd != -1) { 3765 // 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 3766 ep.epoll_event ev = void; 3767 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3768 ev.events = ep.EPOLLIN; 3769 ev.data.fd = displayFd; 3770 //import std.conv; 3771 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 3772 displayFd = -1; 3773 } 3774 3775 } else version(Windows) { 3776 if(pulser !is null) { 3777 pulser.destroy(); 3778 pulser = null; 3779 } 3780 if (customEventH !is null) { 3781 CloseHandle(customEventH); 3782 customEventH = null; 3783 } 3784 } 3785 } 3786 3787 this(long pulseTimeout, void delegate() handlePulse) { 3788 this.pulseTimeout = pulseTimeout; 3789 this.handlePulse = handlePulse; 3790 initialize(pulseTimeout); 3791 } 3792 3793 private long pulseTimeout; 3794 void delegate() handlePulse; 3795 3796 ~this() { 3797 dispose(); 3798 } 3799 3800 version(Posix) 3801 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 3802 version(Posix) 3803 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 3804 version(linux) 3805 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 3806 version(Windows) 3807 ref auto customEventH() { return SimpleWindow.customEventH; } 3808 3809 version(with_eventloop) { 3810 int loopHelper(bool delegate() whileCondition) { 3811 // FIXME: whileCondition 3812 import arsd.eventloop; 3813 loop(); 3814 return 0; 3815 } 3816 } else 3817 int loopHelper(bool delegate() whileCondition) { 3818 version(X11) { 3819 bool done = false; 3820 3821 XFlush(display); 3822 insideXEventLoop = true; 3823 scope(exit) insideXEventLoop = false; 3824 3825 version(linux) { 3826 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 3827 bool forceXPending = false; 3828 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 3829 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 3830 { 3831 this.mtLock(); 3832 scope(exit) this.mtUnlock(); 3833 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 3834 } 3835 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 3836 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 3837 if(nfds == -1) { 3838 if(err.errno == err.EINTR) { 3839 //if(forceXPending) goto xpending; 3840 continue; // interrupted by signal, just try again 3841 } 3842 throw new Exception("epoll wait failure"); 3843 } 3844 3845 SimpleWindow.processAllCustomEvents(); // anyway 3846 //version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 3847 foreach(idx; 0 .. nfds) { 3848 if(done) break; 3849 auto fd = events[idx].data.fd; 3850 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 3851 auto flags = events[idx].events; 3852 if(flags & ep.EPOLLIN) { 3853 if (fd == customSignalFD) { 3854 version(linux) { 3855 import core.sys.linux.sys.signalfd; 3856 import core.sys.posix.unistd : read; 3857 signalfd_siginfo info; 3858 read(customSignalFD, &info, info.sizeof); 3859 3860 auto sig = info.ssi_signo; 3861 3862 if(EventLoop.get.signalHandler !is null) { 3863 EventLoop.get.signalHandler()(sig); 3864 } else { 3865 EventLoop.get.exit(); 3866 } 3867 } 3868 } else if(fd == display.fd) { 3869 version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); } 3870 this.mtLock(); 3871 scope(exit) this.mtUnlock(); 3872 while(!done && XPending(display)) { 3873 done = doXNextEvent(this.display); 3874 } 3875 forceXPending = false; 3876 } else if(fd == pulseFd) { 3877 long expirationCount; 3878 // 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... 3879 3880 handlePulse(); 3881 3882 // read just to clear the buffer so poll doesn't trigger again 3883 // BTW I read AFTER the pulse because if the pulse handler takes 3884 // a lot of time to execute, we don't want the app to get stuck 3885 // in a loop of timer hits without a chance to do anything else 3886 // 3887 // IOW handlePulse happens at most once per pulse interval. 3888 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 3889 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 3890 } else if (fd == customEventFDRead) { 3891 // we have some custom events; process 'em 3892 import core.sys.posix.unistd : read; 3893 ulong n; 3894 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 3895 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 3896 //SimpleWindow.processAllCustomEvents(); 3897 } else { 3898 // some other timer 3899 version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); } 3900 3901 if(Timer* t = fd in Timer.mapping) 3902 (*t).trigger(); 3903 3904 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 3905 (*pfr).ready(flags); 3906 3907 // or i might add support for other FDs too 3908 // but for now it is just timer 3909 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 3910 } 3911 } 3912 if(flags & ep.EPOLLHUP) { 3913 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 3914 (*pfr).hup(flags); 3915 if(globalHupHandler) 3916 globalHupHandler(fd, flags); 3917 } 3918 /+ 3919 } else { 3920 // not interested in OUT, we are just reading here. 3921 // 3922 // error or hup might also be reported 3923 // but it shouldn't here since we are only 3924 // using a few types of FD and Xlib will report 3925 // if it dies. 3926 // so instead of thoughtfully handling it, I'll 3927 // just throw. for now at least 3928 3929 throw new Exception("epoll did something else"); 3930 } 3931 +/ 3932 } 3933 // if we won't call `XPending()` here, libX may delay some internal event delivery. 3934 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 3935 xpending: 3936 if (!done && forceXPending) { 3937 this.mtLock(); 3938 scope(exit) this.mtUnlock(); 3939 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 3940 while(!done && XPending(display)) { 3941 done = doXNextEvent(this.display); 3942 } 3943 } 3944 } 3945 } else { 3946 // Generic fallback: yes to simple pulse support, 3947 // but NO timer support! 3948 3949 // FIXME: we could probably support the POSIX timer_create 3950 // signal-based option, but I'm in no rush to write it since 3951 // I prefer the fd-based functions. 3952 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 3953 3954 import core.sys.posix.poll; 3955 3956 pollfd[] pfds; 3957 pollfd[32] pfdsBuffer; 3958 auto len = PosixFdReader.mapping.length + 2; 3959 // FIXME: i should just reuse the buffer 3960 if(len < pfdsBuffer.length) 3961 pfds = pfdsBuffer[0 .. len]; 3962 else 3963 pfds = new pollfd[](len); 3964 3965 pfds[0].fd = display.fd; 3966 pfds[0].events = POLLIN; 3967 pfds[0].revents = 0; 3968 3969 int slot = 1; 3970 3971 if(customEventFDRead != -1) { 3972 pfds[slot].fd = customEventFDRead; 3973 pfds[slot].events = POLLIN; 3974 pfds[slot].revents = 0; 3975 3976 slot++; 3977 } 3978 3979 foreach(fd, obj; PosixFdReader.mapping) { 3980 if(!obj.enabled) continue; 3981 pfds[slot].fd = fd; 3982 pfds[slot].events = POLLIN; 3983 pfds[slot].revents = 0; 3984 3985 slot++; 3986 } 3987 3988 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 3989 if(ret == -1) throw new Exception("poll"); 3990 3991 if(ret == 0) { 3992 // FIXME it may not necessarily time out if events keep coming 3993 if(handlePulse !is null) 3994 handlePulse(); 3995 } else { 3996 foreach(s; 0 .. slot) { 3997 if(pfds[s].revents == 0) continue; 3998 3999 if(pfds[s].fd == display.fd) { 4000 while(!done && XPending(display)) { 4001 this.mtLock(); 4002 scope(exit) this.mtUnlock(); 4003 done = doXNextEvent(this.display); 4004 } 4005 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4006 4007 import core.sys.posix.unistd : read; 4008 ulong n; 4009 read(customEventFDRead, &n, n.sizeof); 4010 SimpleWindow.processAllCustomEvents(); 4011 } else { 4012 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4013 if(pfds[s].revents & POLLNVAL) { 4014 obj.dispose(); 4015 } else { 4016 obj.ready(pfds[s].revents); 4017 } 4018 } 4019 4020 ret--; 4021 if(ret == 0) break; 4022 } 4023 } 4024 } 4025 } 4026 } 4027 4028 version(Windows) { 4029 int ret = -1; 4030 MSG message; 4031 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4032 eventLoopRound++; 4033 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4034 auto waitResult = MsgWaitForMultipleObjectsEx( 4035 cast(int) handles.length, handles.ptr, 4036 (wto == 0 ? INFINITE : wto), /* timeout */ 4037 0x04FF, /* QS_ALLINPUT */ 4038 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4039 4040 SimpleWindow.processAllCustomEvents(); // anyway 4041 enum WAIT_OBJECT_0 = 0; 4042 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4043 auto h = handles[waitResult - WAIT_OBJECT_0]; 4044 if(auto e = h in WindowsHandleReader.mapping) { 4045 (*e).ready(); 4046 } 4047 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4048 // message ready 4049 int count; 4050 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 4051 ret = GetMessage(&message, null, 0, 0); 4052 if(ret == -1) 4053 throw new Exception("GetMessage failed"); 4054 TranslateMessage(&message); 4055 DispatchMessage(&message); 4056 4057 count++; 4058 if(count > 10) 4059 break; // take the opportunity to catch up on other events 4060 4061 if(ret == 0) // WM_QUIT 4062 break; 4063 } 4064 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4065 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4066 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4067 // timeout, should never happen since we aren't using it 4068 } else if(waitResult == 0xFFFFFFFF) { 4069 // failed 4070 throw new Exception("MsgWaitForMultipleObjectsEx failed"); 4071 } else { 4072 // idk.... 4073 } 4074 } 4075 4076 // return message.wParam; 4077 return 0; 4078 } else { 4079 return 0; 4080 } 4081 } 4082 4083 int run(bool delegate() whileCondition = null) { 4084 if(disposed) 4085 initialize(this.pulseTimeout); 4086 4087 version(X11) { 4088 try { 4089 return loopHelper(whileCondition); 4090 } catch(XDisconnectException e) { 4091 if(e.userRequested) { 4092 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4093 item.discardConnectionState(); 4094 XCloseDisplay(XDisplayConnection.display); 4095 } 4096 4097 XDisplayConnection.display = null; 4098 4099 this.dispose(); 4100 4101 throw e; 4102 } 4103 } else { 4104 return loopHelper(whileCondition); 4105 } 4106 } 4107 } 4108 4109 4110 /++ 4111 Provides an icon on the system notification area (also known as the system tray). 4112 4113 4114 If a notification area is not available with the NotificationIcon object is created, 4115 it will silently succeed and simply attempt to create one when an area becomes available. 4116 4117 4118 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. 4119 If this is wrong, pass -version=WindowsXP to dmd when compiling and it will 4120 use the older version. 4121 +/ 4122 version(OSXCocoa) {} else // NotYetImplementedException 4123 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4124 4125 version(X11) { 4126 void recreateAfterDisconnect() { 4127 stateDiscarded = false; 4128 clippixmap = None; 4129 throw new Exception("NOT IMPLEMENTED"); 4130 } 4131 4132 bool stateDiscarded; 4133 void discardConnectionState() { 4134 stateDiscarded = true; 4135 } 4136 } 4137 4138 4139 version(X11) { 4140 Image img; 4141 4142 NativeEventHandler getNativeEventHandler() { 4143 return delegate int(XEvent e) { 4144 switch(e.type) { 4145 case EventType.Expose: 4146 //case EventType.VisibilityNotify: 4147 redraw(); 4148 break; 4149 case EventType.ClientMessage: 4150 version(sddddd) { 4151 import std.stdio; 4152 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4153 writeln("\t", e.xclient.format); 4154 writeln("\t", e.xclient.data.l); 4155 } 4156 break; 4157 case EventType.ButtonPress: 4158 auto event = e.xbutton; 4159 if (onClick !is null || onClickEx !is null) { 4160 MouseButton mb = cast(MouseButton)0; 4161 switch (event.button) { 4162 case 1: mb = MouseButton.left; break; // left 4163 case 2: mb = MouseButton.middle; break; // middle 4164 case 3: mb = MouseButton.right; break; // right 4165 case 4: mb = MouseButton.wheelUp; break; // scroll up 4166 case 5: mb = MouseButton.wheelDown; break; // scroll down 4167 case 6: break; // idk 4168 case 7: break; // idk 4169 case 8: mb = MouseButton.backButton; break; 4170 case 9: mb = MouseButton.forwardButton; break; 4171 default: 4172 } 4173 if (mb) { 4174 try { onClick()(mb); } catch (Exception) {} 4175 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 4176 } 4177 } 4178 break; 4179 case EventType.EnterNotify: 4180 if (onEnter !is null) { 4181 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 4182 } 4183 break; 4184 case EventType.LeaveNotify: 4185 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 4186 break; 4187 case EventType.DestroyNotify: 4188 active = false; 4189 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 4190 break; 4191 case EventType.ConfigureNotify: 4192 auto event = e.xconfigure; 4193 this.width = event.width; 4194 this.height = event.height; 4195 //import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y); 4196 redraw(); 4197 break; 4198 default: return 1; 4199 } 4200 return 1; 4201 }; 4202 } 4203 4204 /* private */ void hideBalloon() { 4205 balloon.close(); 4206 version(with_timer) 4207 timer.destroy(); 4208 balloon = null; 4209 version(with_timer) 4210 timer = null; 4211 } 4212 4213 void redraw() { 4214 if (!active) return; 4215 4216 auto display = XDisplayConnection.get; 4217 auto gc = DefaultGC(display, DefaultScreen(display)); 4218 XClearWindow(display, nativeHandle); 4219 4220 XSetClipMask(display, gc, clippixmap); 4221 4222 XSetForeground(display, gc, 4223 cast(uint) 0 << 16 | 4224 cast(uint) 0 << 8 | 4225 cast(uint) 0); 4226 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 4227 4228 if (img is null) { 4229 XSetForeground(display, gc, 4230 cast(uint) 0 << 16 | 4231 cast(uint) 127 << 8 | 4232 cast(uint) 0); 4233 XFillArc(display, nativeHandle, 4234 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 4235 } else { 4236 int dx = 0; 4237 int dy = 0; 4238 if(width > img.width) 4239 dx = (width - img.width) / 2; 4240 if(height > img.height) 4241 dy = (height - img.height) / 2; 4242 XSetClipOrigin(display, gc, dx, dy); 4243 4244 if (img.usingXshm) 4245 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false); 4246 else 4247 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height); 4248 } 4249 XSetClipMask(display, gc, None); 4250 flushGui(); 4251 } 4252 4253 static Window getTrayOwner() { 4254 auto display = XDisplayConnection.get; 4255 auto i = cast(int) DefaultScreen(display); 4256 if(i < 10 && i >= 0) { 4257 static Atom atom; 4258 if(atom == None) 4259 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 4260 return XGetSelectionOwner(display, atom); 4261 } 4262 return None; 4263 } 4264 4265 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 4266 auto to = getTrayOwner(); 4267 auto display = XDisplayConnection.get; 4268 XEvent ev; 4269 ev.xclient.type = EventType.ClientMessage; 4270 ev.xclient.window = to; 4271 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 4272 ev.xclient.format = 32; 4273 ev.xclient.data.l[0] = CurrentTime; 4274 ev.xclient.data.l[1] = message; 4275 ev.xclient.data.l[2] = d1; 4276 ev.xclient.data.l[3] = d2; 4277 ev.xclient.data.l[4] = d3; 4278 4279 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 4280 } 4281 4282 private static NotificationAreaIcon[] activeIcons; 4283 4284 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 4285 private void newManager() { 4286 close(); 4287 createXWin(); 4288 4289 if(this.clippixmap) 4290 XFreePixmap(XDisplayConnection.get, clippixmap); 4291 if(this.originalMemoryImage) 4292 this.icon = this.originalMemoryImage; 4293 else if(this.img) 4294 this.icon = this.img; 4295 } 4296 4297 private void createXWin () { 4298 // create window 4299 auto display = XDisplayConnection.get; 4300 4301 // to check for MANAGER on root window to catch new/changed tray owners 4302 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 4303 // so if a thing does appear, we can handle it 4304 foreach(ai; activeIcons) 4305 if(ai is this) 4306 goto alreadythere; 4307 activeIcons ~= this; 4308 alreadythere: 4309 4310 // and check for an existing tray 4311 auto trayOwner = getTrayOwner(); 4312 if(trayOwner == None) 4313 return; 4314 //throw new Exception("No notification area found"); 4315 4316 Visual* v = cast(Visual*) CopyFromParent; 4317 /+ 4318 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 4319 if(visualProp !is null) { 4320 c_ulong[] info = cast(c_ulong[]) visualProp; 4321 if(info.length == 1) { 4322 auto vid = info[0]; 4323 int returned; 4324 XVisualInfo t; 4325 t.visualid = vid; 4326 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 4327 if(got !is null) { 4328 if(returned == 1) { 4329 v = got.visual; 4330 import std.stdio; 4331 writeln("using special visual ", *got); 4332 } 4333 XFree(got); 4334 } 4335 } 4336 } 4337 +/ 4338 4339 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null); 4340 assert(nativeWindow); 4341 4342 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4343 4344 nativeHandle = nativeWindow; 4345 4346 ///+ 4347 arch_ulong[2] info; 4348 info[0] = 0; 4349 info[1] = 1; 4350 4351 string title = this.name is null ? "simpledisplay.d program" : this.name; 4352 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4353 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4354 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4355 4356 XChangeProperty( 4357 display, 4358 nativeWindow, 4359 GetAtom!("_XEMBED_INFO", true)(display), 4360 GetAtom!("_XEMBED_INFO", true)(display), 4361 32 /* bits */, 4362 0 /*PropModeReplace*/, 4363 info.ptr, 4364 2); 4365 4366 import core.sys.posix.unistd; 4367 arch_ulong pid = getpid(); 4368 4369 XChangeProperty( 4370 display, 4371 nativeWindow, 4372 GetAtom!("_NET_WM_PID", true)(display), 4373 XA_CARDINAL, 4374 32 /* bits */, 4375 0 /*PropModeReplace*/, 4376 &pid, 4377 1); 4378 4379 updateNetWmIcon(); 4380 4381 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4382 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4383 XClassHint klass; 4384 XWMHints wh; 4385 XSizeHints size; 4386 klass.res_name = sdpyWindowClassStr; 4387 klass.res_class = sdpyWindowClassStr; 4388 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4389 } 4390 4391 // believe it or not, THIS is what xfce needed for the 9999 issue 4392 XSizeHints sh; 4393 c_long spr; 4394 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4395 sh.flags |= PMaxSize | PMinSize; 4396 // FIXME maybe nicer resizing 4397 sh.min_width = 16; 4398 sh.min_height = 16; 4399 sh.max_width = 16; 4400 sh.max_height = 16; 4401 XSetWMNormalHints(display, nativeWindow, &sh); 4402 4403 4404 //+/ 4405 4406 4407 XSelectInput(display, nativeWindow, 4408 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 4409 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 4410 4411 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 4412 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 4413 active = true; 4414 } 4415 4416 void updateNetWmIcon() { 4417 if(img is null) return; 4418 auto display = XDisplayConnection.get; 4419 // FIXME: ensure this is correct 4420 arch_ulong[] buffer; 4421 auto imgMi = img.toTrueColorImage; 4422 buffer ~= imgMi.width; 4423 buffer ~= imgMi.height; 4424 foreach(c; imgMi.imageData.colors) { 4425 arch_ulong b; 4426 b |= c.a << 24; 4427 b |= c.r << 16; 4428 b |= c.g << 8; 4429 b |= c.b; 4430 buffer ~= b; 4431 } 4432 4433 XChangeProperty( 4434 display, 4435 nativeHandle, 4436 GetAtom!"_NET_WM_ICON"(display), 4437 GetAtom!"CARDINAL"(display), 4438 32 /* bits */, 4439 0 /*PropModeReplace*/, 4440 buffer.ptr, 4441 cast(int) buffer.length); 4442 } 4443 4444 4445 4446 private SimpleWindow balloon; 4447 version(with_timer) 4448 private Timer timer; 4449 4450 private Window nativeHandle; 4451 private Pixmap clippixmap = None; 4452 private int width = 16; 4453 private int height = 16; 4454 private bool active = false; 4455 4456 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 4457 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 4458 void delegate () onLeave; /// X11 only. 4459 4460 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 4461 4462 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 4463 void getWindowRect (out int x, out int y, out int width, out int height) { 4464 if (!active) { width = 1; height = 1; return; } // 1: just in case 4465 Window dummyw; 4466 auto dpy = XDisplayConnection.get; 4467 //XWindowAttributes xwa; 4468 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 4469 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 4470 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 4471 width = this.width; 4472 height = this.height; 4473 } 4474 } 4475 4476 /+ 4477 What I actually want from this: 4478 4479 * set / change: icon, tooltip 4480 * handle: mouse click, right click 4481 * show: notification bubble. 4482 +/ 4483 4484 version(Windows) { 4485 WindowsIcon win32Icon; 4486 HWND hwnd; 4487 4488 NOTIFYICONDATAW data; 4489 4490 NativeEventHandler getNativeEventHandler() { 4491 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 4492 if(msg == WM_USER) { 4493 auto event = LOWORD(lParam); 4494 auto iconId = HIWORD(lParam); 4495 //auto x = GET_X_LPARAM(wParam); 4496 //auto y = GET_Y_LPARAM(wParam); 4497 switch(event) { 4498 case WM_LBUTTONDOWN: 4499 onClick()(MouseButton.left); 4500 break; 4501 case WM_RBUTTONDOWN: 4502 onClick()(MouseButton.right); 4503 break; 4504 case WM_MBUTTONDOWN: 4505 onClick()(MouseButton.middle); 4506 break; 4507 case WM_MOUSEMOVE: 4508 // sent, we could use it. 4509 break; 4510 case WM_MOUSEWHEEL: 4511 // NOT SENT 4512 break; 4513 //case NIN_KEYSELECT: 4514 //case NIN_SELECT: 4515 //break; 4516 default: {} 4517 } 4518 } 4519 return 0; 4520 }; 4521 } 4522 4523 enum NIF_SHOWTIP = 0x00000080; 4524 4525 private static struct NOTIFYICONDATAW { 4526 DWORD cbSize; 4527 HWND hWnd; 4528 UINT uID; 4529 UINT uFlags; 4530 UINT uCallbackMessage; 4531 HICON hIcon; 4532 WCHAR[128] szTip; 4533 DWORD dwState; 4534 DWORD dwStateMask; 4535 WCHAR[256] szInfo; 4536 union { 4537 UINT uTimeout; 4538 UINT uVersion; 4539 } 4540 WCHAR[64] szInfoTitle; 4541 DWORD dwInfoFlags; 4542 GUID guidItem; 4543 HICON hBalloonIcon; 4544 } 4545 4546 } 4547 4548 /++ 4549 Note that on Windows, only left, right, and middle buttons are sent. 4550 Mouse wheel buttons are NOT set, so don't rely on those events if your 4551 program is meant to be used on Windows too. 4552 +/ 4553 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 4554 // The canonical constructor for Windows needs the MemoryImage, so it is here, 4555 // but on X, we need an Image, so its canonical ctor is there. They should 4556 // forward to each other though. 4557 version(X11) { 4558 this.name = name; 4559 this.onClick = onClick; 4560 createXWin(); 4561 this.icon = icon; 4562 } else version(Windows) { 4563 this.onClick = onClick; 4564 this.win32Icon = new WindowsIcon(icon); 4565 4566 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 4567 4568 static bool registered = false; 4569 if(!registered) { 4570 WNDCLASSEX wc; 4571 wc.cbSize = wc.sizeof; 4572 wc.hInstance = hInstance; 4573 wc.lpfnWndProc = &WndProc; 4574 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 4575 if(!RegisterClassExW(&wc)) 4576 throw new WindowsApiException("RegisterClass"); 4577 registered = true; 4578 } 4579 4580 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 4581 if(hwnd is null) 4582 throw new Exception("CreateWindow"); 4583 4584 data.cbSize = data.sizeof; 4585 data.hWnd = hwnd; 4586 data.uID = cast(uint) cast(void*) this; 4587 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 4588 // NIF_INFO means show balloon 4589 data.uCallbackMessage = WM_USER; 4590 data.hIcon = this.win32Icon.hIcon; 4591 data.szTip = ""; // FIXME 4592 data.dwState = 0; // NIS_HIDDEN; // windows vista 4593 data.dwStateMask = NIS_HIDDEN; // windows vista 4594 4595 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 4596 4597 4598 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 4599 4600 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 4601 } else version(OSXCocoa) { 4602 throw new NotYetImplementedException(); 4603 } else static assert(0); 4604 } 4605 4606 /// ditto 4607 this(string name, Image icon, void delegate(MouseButton button) onClick) { 4608 version(X11) { 4609 this.onClick = onClick; 4610 this.name = name; 4611 createXWin(); 4612 this.icon = icon; 4613 } else version(Windows) { 4614 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 4615 } else version(OSXCocoa) { 4616 throw new NotYetImplementedException(); 4617 } else static assert(0); 4618 } 4619 4620 version(X11) { 4621 /++ 4622 X-specific extension (for now at least) 4623 +/ 4624 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 4625 this.onClickEx = onClickEx; 4626 createXWin(); 4627 if (icon !is null) this.icon = icon; 4628 } 4629 4630 /// ditto 4631 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 4632 this.onClickEx = onClickEx; 4633 createXWin(); 4634 this.icon = icon; 4635 } 4636 } 4637 4638 private void delegate (MouseButton button) onClick_; 4639 4640 /// 4641 @property final void delegate(MouseButton) onClick() { 4642 if(onClick_ is null) 4643 onClick_ = delegate void(MouseButton) {}; 4644 return onClick_; 4645 } 4646 4647 /// ditto 4648 @property final void onClick(void delegate(MouseButton) handler) { 4649 // I made this a property setter so we can wrap smaller arg 4650 // delegates and just forward all to onClickEx or something. 4651 onClick_ = handler; 4652 } 4653 4654 4655 string name_; 4656 @property void name(string n) { 4657 name_ = n; 4658 } 4659 4660 @property string name() { 4661 return name_; 4662 } 4663 4664 private MemoryImage originalMemoryImage; 4665 4666 /// 4667 @property void icon(MemoryImage i) { 4668 version(X11) { 4669 this.originalMemoryImage = i; 4670 if (!active) return; 4671 if (i !is null) { 4672 this.img = Image.fromMemoryImage(i); 4673 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 4674 //import std.stdio; writeln("using pixmap ", clippixmap); 4675 updateNetWmIcon(); 4676 redraw(); 4677 } else { 4678 if (this.img !is null) { 4679 this.img = null; 4680 redraw(); 4681 } 4682 } 4683 } else version(Windows) { 4684 this.win32Icon = new WindowsIcon(i); 4685 4686 data.uFlags = NIF_ICON; 4687 data.hIcon = this.win32Icon.hIcon; 4688 4689 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4690 } else version(OSXCocoa) { 4691 throw new NotYetImplementedException(); 4692 } else static assert(0); 4693 } 4694 4695 /// ditto 4696 @property void icon (Image i) { 4697 version(X11) { 4698 if (!active) return; 4699 if (i !is img) { 4700 originalMemoryImage = null; 4701 img = i; 4702 redraw(); 4703 } 4704 } else version(Windows) { 4705 this.icon(i is null ? null : i.toTrueColorImage()); 4706 } else version(OSXCocoa) { 4707 throw new NotYetImplementedException(); 4708 } else static assert(0); 4709 } 4710 4711 /++ 4712 Shows a balloon notification. You can only show one balloon at a time, if you call 4713 it twice while one is already up, the first balloon will be replaced. 4714 4715 4716 The user is free to block notifications and they will automatically disappear after 4717 a timeout period. 4718 4719 Params: 4720 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 4721 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 4722 icon = the icon to display with the notification. If null, it uses your existing icon. 4723 onclick = delegate called if the user clicks the balloon. (not yet implemented) 4724 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 4725 +/ 4726 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 4727 bool useCustom = true; 4728 version(libnotify) { 4729 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 4730 try { 4731 if(!active) return; 4732 4733 if(libnotify is null) { 4734 libnotify = new C_DynamicLibrary("libnotify.so"); 4735 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 4736 } 4737 4738 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 4739 4740 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 4741 4742 if(onclick) { 4743 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 4744 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); 4745 libnotify_action_delegates_count++; 4746 } 4747 4748 // FIXME icon 4749 4750 // set hint image-data 4751 // set default action for onclick 4752 4753 void* error; 4754 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 4755 4756 useCustom = false; 4757 } catch(Exception e) { 4758 4759 } 4760 } 4761 4762 version(X11) { 4763 if(useCustom) { 4764 if(!active) return; 4765 if(balloon) { 4766 hideBalloon(); 4767 } 4768 // I know there are two specs for this, but one is never 4769 // implemented by any window manager I have ever seen, and 4770 // the other is a bloated mess and too complicated for simpledisplay... 4771 // so doing my own little window instead. 4772 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 4773 4774 int x, y, width, height; 4775 getWindowRect(x, y, width, height); 4776 4777 int bx = x - balloon.width; 4778 int by = y - balloon.height; 4779 if(bx < 0) 4780 bx = x + width + balloon.width; 4781 if(by < 0) 4782 by = y + height; 4783 4784 // just in case, make sure it is actually on scren 4785 if(bx < 0) 4786 bx = 0; 4787 if(by < 0) 4788 by = 0; 4789 4790 balloon.move(bx, by); 4791 auto painter = balloon.draw(); 4792 painter.fillColor = Color(220, 220, 220); 4793 painter.outlineColor = Color.black; 4794 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 4795 auto iconWidth = icon is null ? 0 : icon.width; 4796 if(icon) 4797 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 4798 iconWidth += 6; // margin around the icon 4799 4800 // draw a close button 4801 painter.outlineColor = Color(44, 44, 44); 4802 painter.fillColor = Color(255, 255, 255); 4803 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 4804 painter.pen = Pen(Color.black, 3); 4805 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 4806 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 4807 painter.pen = Pen(Color.black, 1); 4808 painter.fillColor = Color(220, 220, 220); 4809 4810 // Draw the title and message 4811 painter.drawText(Point(4 + iconWidth, 4), title); 4812 painter.drawLine( 4813 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 4814 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 4815 ); 4816 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 4817 4818 balloon.setEventHandlers( 4819 (MouseEvent ev) { 4820 if(ev.type == MouseEventType.buttonPressed) { 4821 if(ev.x > balloon.width - 16 && ev.y < 16) 4822 hideBalloon(); 4823 else if(onclick) 4824 onclick(); 4825 } 4826 } 4827 ); 4828 balloon.show(); 4829 4830 version(with_timer) 4831 timer = new Timer(timeout, &hideBalloon); 4832 else {} // FIXME 4833 } 4834 } else version(Windows) { 4835 enum NIF_INFO = 0x00000010; 4836 4837 data.uFlags = NIF_INFO; 4838 4839 // FIXME: go back to the last valid unicode code point 4840 if(title.length > 40) 4841 title = title[0 .. 40]; 4842 if(message.length > 220) 4843 message = message[0 .. 220]; 4844 4845 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 4846 enum NIIF_LARGE_ICON = 0x00000020; 4847 enum NIIF_NOSOUND = 0x00000010; 4848 enum NIIF_USER = 0x00000004; 4849 enum NIIF_ERROR = 0x00000003; 4850 enum NIIF_WARNING = 0x00000002; 4851 enum NIIF_INFO = 0x00000001; 4852 enum NIIF_NONE = 0; 4853 4854 WCharzBuffer t = WCharzBuffer(title); 4855 WCharzBuffer m = WCharzBuffer(message); 4856 4857 t.copyInto(data.szInfoTitle); 4858 m.copyInto(data.szInfo); 4859 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 4860 4861 if(icon !is null) { 4862 auto i = new WindowsIcon(icon); 4863 data.hBalloonIcon = i.hIcon; 4864 data.dwInfoFlags |= NIIF_USER; 4865 } 4866 4867 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4868 } else version(OSXCocoa) { 4869 throw new NotYetImplementedException(); 4870 } else static assert(0); 4871 } 4872 4873 /// 4874 //version(Windows) 4875 void show() { 4876 version(X11) { 4877 if(!hidden) 4878 return; 4879 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 4880 hidden = false; 4881 } else version(Windows) { 4882 data.uFlags = NIF_STATE; 4883 data.dwState = 0; // NIS_HIDDEN; // windows vista 4884 data.dwStateMask = NIS_HIDDEN; // windows vista 4885 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4886 } else version(OSXCocoa) { 4887 throw new NotYetImplementedException(); 4888 } else static assert(0); 4889 } 4890 4891 version(X11) 4892 bool hidden = false; 4893 4894 /// 4895 //version(Windows) 4896 void hide() { 4897 version(X11) { 4898 if(hidden) 4899 return; 4900 hidden = true; 4901 XUnmapWindow(XDisplayConnection.get, nativeHandle); 4902 } else version(Windows) { 4903 data.uFlags = NIF_STATE; 4904 data.dwState = NIS_HIDDEN; // windows vista 4905 data.dwStateMask = NIS_HIDDEN; // windows vista 4906 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4907 } else version(OSXCocoa) { 4908 throw new NotYetImplementedException(); 4909 } else static assert(0); 4910 } 4911 4912 /// 4913 void close () { 4914 version(X11) { 4915 if (active) { 4916 active = false; // event handler will set this too, but meh 4917 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 4918 XDestroyWindow(XDisplayConnection.get, nativeHandle); 4919 flushGui(); 4920 } 4921 } else version(Windows) { 4922 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 4923 } else version(OSXCocoa) { 4924 throw new NotYetImplementedException(); 4925 } else static assert(0); 4926 } 4927 4928 ~this() { 4929 version(X11) 4930 if(clippixmap != None) 4931 XFreePixmap(XDisplayConnection.get, clippixmap); 4932 close(); 4933 } 4934 } 4935 4936 version(X11) 4937 /// Call `XFreePixmap` on the return value. 4938 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 4939 char[] data = new char[](i.width * i.height / 8 + 2); 4940 data[] = 0; 4941 4942 int bitOffset = 0; 4943 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 4944 ubyte v = c.a > 128 ? 1 : 0; 4945 data[bitOffset / 8] |= v << (bitOffset%8); 4946 bitOffset++; 4947 } 4948 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 4949 return handle; 4950 } 4951 4952 4953 // basic functions to make timers 4954 /** 4955 A timer that will trigger your function on a given interval. 4956 4957 4958 You create a timer with an interval and a callback. It will continue 4959 to fire on the interval until it is destroyed. 4960 4961 There are currently no one-off timers (instead, just create one and 4962 destroy it when it is triggered) nor are there pause/resume functions - 4963 the timer must again be destroyed and recreated if you want to pause it. 4964 4965 auto timer = new Timer(50, { it happened!; }); 4966 timer.destroy(); 4967 4968 Timers can only be expected to fire when the event loop is running and only 4969 once per iteration through the event loop. 4970 4971 History: 4972 Prior to December 9, 2020, a timer pulse set too high with a handler too 4973 slow could lock up the event loop. It now guarantees other things will 4974 get a chance to run between timer calls, even if that means not keeping up 4975 with the requested interval. 4976 */ 4977 version(with_timer) { 4978 class Timer { 4979 // FIXME: needs pause and unpause 4980 // FIXME: I might add overloads for ones that take a count of 4981 // how many elapsed since last time (on Windows, it will divide 4982 // the ticks thing given, on Linux it is just available) and 4983 // maybe one that takes an instance of the Timer itself too 4984 /// Create a timer with a callback when it triggers. 4985 this(int intervalInMilliseconds, void delegate() onPulse) { 4986 assert(onPulse !is null); 4987 4988 this.intervalInMilliseconds = intervalInMilliseconds; 4989 this.onPulse = onPulse; 4990 4991 version(Windows) { 4992 /* 4993 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 4994 if(handle == 0) 4995 throw new Exception("SetTimer fail"); 4996 */ 4997 4998 // thanks to Archival 998 for the WaitableTimer blocks 4999 handle = CreateWaitableTimer(null, false, null); 5000 long initialTime = -intervalInMilliseconds; 5001 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5002 throw new Exception("SetWaitableTimer Failed"); 5003 5004 mapping[handle] = this; 5005 5006 } else version(linux) { 5007 static import ep = core.sys.linux.epoll; 5008 5009 import core.sys.linux.timerfd; 5010 5011 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5012 if(fd == -1) 5013 throw new Exception("timer create failed"); 5014 5015 mapping[fd] = this; 5016 5017 itimerspec value; 5018 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5019 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5020 5021 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5022 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5023 5024 if(timerfd_settime(fd, 0, &value, null) == -1) 5025 throw new Exception("couldn't make pulse timer"); 5026 5027 version(with_eventloop) { 5028 import arsd.eventloop; 5029 addFileEventListeners(fd, &trigger, null, null); 5030 } else { 5031 prepareEventLoop(); 5032 5033 ep.epoll_event ev = void; 5034 ev.events = ep.EPOLLIN; 5035 ev.data.fd = fd; 5036 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5037 } 5038 } else featureNotImplemented(); 5039 } 5040 5041 private int intervalInMilliseconds; 5042 5043 /// Stop and destroy the timer object. 5044 void destroy() { 5045 version(Windows) { 5046 if(handle) { 5047 // KillTimer(null, handle); 5048 CancelWaitableTimer(cast(void*)handle); 5049 mapping.remove(handle); 5050 CloseHandle(handle); 5051 handle = null; 5052 } 5053 } else version(linux) { 5054 if(fd != -1) { 5055 import unix = core.sys.posix.unistd; 5056 static import ep = core.sys.linux.epoll; 5057 5058 version(with_eventloop) { 5059 import arsd.eventloop; 5060 removeFileEventListeners(fd); 5061 } else { 5062 ep.epoll_event ev = void; 5063 ev.events = ep.EPOLLIN; 5064 ev.data.fd = fd; 5065 5066 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5067 } 5068 unix.close(fd); 5069 mapping.remove(fd); 5070 fd = -1; 5071 } 5072 } else featureNotImplemented(); 5073 } 5074 5075 ~this() { 5076 destroy(); 5077 } 5078 5079 5080 void changeTime(int intervalInMilliseconds) 5081 { 5082 this.intervalInMilliseconds = intervalInMilliseconds; 5083 version(Windows) 5084 { 5085 if(handle) 5086 { 5087 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5088 long initialTime = -intervalInMilliseconds; 5089 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5090 throw new Exception("couldn't change pulse timer"); 5091 } 5092 } 5093 } 5094 5095 5096 private: 5097 5098 void delegate() onPulse; 5099 5100 int lastEventLoopRoundTriggered; 5101 5102 void trigger() { 5103 version(linux) { 5104 import unix = core.sys.posix.unistd; 5105 long val; 5106 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 5107 } else version(Windows) { 5108 if(this.lastEventLoopRoundTriggered == eventLoopRound) 5109 return; // never try to actually run faster than the event loop 5110 lastEventLoopRoundTriggered = eventLoopRound; 5111 } else featureNotImplemented(); 5112 5113 onPulse(); 5114 } 5115 5116 version(Windows) 5117 void rearm() { 5118 5119 } 5120 5121 version(Windows) 5122 extern(Windows) 5123 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 5124 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 5125 if(Timer* t = timer in mapping) { 5126 try 5127 (*t).trigger(); 5128 catch(Exception e) { sdpy_abort(e); assert(0); } 5129 } 5130 } 5131 5132 version(Windows) { 5133 //UINT_PTR handle; 5134 //static Timer[UINT_PTR] mapping; 5135 HANDLE handle; 5136 __gshared Timer[HANDLE] mapping; 5137 } else version(linux) { 5138 int fd = -1; 5139 __gshared Timer[int] mapping; 5140 } else static assert(0, "timer not supported"); 5141 } 5142 } 5143 5144 version(Windows) 5145 private int eventLoopRound; 5146 5147 version(Windows) 5148 /// 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 5149 class WindowsHandleReader { 5150 /// 5151 this(void delegate() onReady, HANDLE handle) { 5152 this.onReady = onReady; 5153 this.handle = handle; 5154 5155 mapping[handle] = this; 5156 5157 enable(); 5158 } 5159 5160 /// 5161 void enable() { 5162 auto el = EventLoop.get().impl; 5163 el.handles ~= handle; 5164 } 5165 5166 /// 5167 void disable() { 5168 auto el = EventLoop.get().impl; 5169 for(int i = 0; i < el.handles.length; i++) { 5170 if(el.handles[i] is handle) { 5171 el.handles[i] = el.handles[$-1]; 5172 el.handles = el.handles[0 .. $-1]; 5173 return; 5174 } 5175 } 5176 } 5177 5178 void dispose() { 5179 disable(); 5180 if(handle) 5181 mapping.remove(handle); 5182 handle = null; 5183 } 5184 5185 void ready() { 5186 if(onReady) 5187 onReady(); 5188 } 5189 5190 HANDLE handle; 5191 void delegate() onReady; 5192 5193 __gshared WindowsHandleReader[HANDLE] mapping; 5194 } 5195 5196 version(Posix) 5197 /// Lets you add files to the event loop for reading. Use at your own risk. 5198 class PosixFdReader { 5199 /// 5200 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5201 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 5202 } 5203 5204 /// 5205 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5206 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 5207 } 5208 5209 /// 5210 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5211 this.onReady = onReady; 5212 this.fd = fd; 5213 this.captureWrites = captureWrites; 5214 this.captureReads = captureReads; 5215 5216 mapping[fd] = this; 5217 5218 version(with_eventloop) { 5219 import arsd.eventloop; 5220 addFileEventListeners(fd, &readyel); 5221 } else { 5222 enable(); 5223 } 5224 } 5225 5226 bool captureReads; 5227 bool captureWrites; 5228 5229 version(with_eventloop) {} else 5230 /// 5231 void enable() { 5232 prepareEventLoop(); 5233 5234 enabled = true; 5235 5236 version(linux) { 5237 static import ep = core.sys.linux.epoll; 5238 ep.epoll_event ev = void; 5239 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5240 //import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites); 5241 ev.data.fd = fd; 5242 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5243 } else { 5244 5245 } 5246 } 5247 5248 version(with_eventloop) {} else 5249 /// 5250 void disable() { 5251 prepareEventLoop(); 5252 5253 enabled = false; 5254 5255 version(linux) { 5256 static import ep = core.sys.linux.epoll; 5257 ep.epoll_event ev = void; 5258 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5259 //import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites); 5260 ev.data.fd = fd; 5261 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5262 } 5263 } 5264 5265 version(with_eventloop) {} else 5266 /// 5267 void dispose() { 5268 if(enabled) 5269 disable(); 5270 if(fd != -1) 5271 mapping.remove(fd); 5272 fd = -1; 5273 } 5274 5275 void delegate(int, bool, bool) onReady; 5276 5277 version(with_eventloop) 5278 void readyel() { 5279 onReady(fd, true, true); 5280 } 5281 5282 void ready(uint flags) { 5283 version(linux) { 5284 static import ep = core.sys.linux.epoll; 5285 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 5286 } else { 5287 import core.sys.posix.poll; 5288 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 5289 } 5290 } 5291 5292 void hup(uint flags) { 5293 if(onHup) 5294 onHup(); 5295 } 5296 5297 void delegate() onHup; 5298 5299 int fd = -1; 5300 private bool enabled; 5301 __gshared PosixFdReader[int] mapping; 5302 } 5303 5304 // basic functions to access the clipboard 5305 /+ 5306 5307 5308 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 5309 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 5310 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5311 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 5312 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 5313 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5314 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 5315 5316 +/ 5317 5318 /++ 5319 this does a delegate because it is actually an async call on X... 5320 the receiver may never be called if the clipboard is empty or unavailable 5321 gets plain text from the clipboard. 5322 +/ 5323 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 5324 version(Windows) { 5325 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5326 if(OpenClipboard(hwndOwner) == 0) 5327 throw new Exception("OpenClipboard"); 5328 scope(exit) 5329 CloseClipboard(); 5330 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 5331 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 5332 5333 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 5334 scope(exit) 5335 GlobalUnlock(dataHandle); 5336 5337 // FIXME: CR/LF conversions 5338 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 5339 int len = 0; 5340 auto d = data; 5341 while(*d) { 5342 d++; 5343 len++; 5344 } 5345 string s; 5346 s.reserve(len); 5347 foreach(dchar ch; data[0 .. len]) { 5348 s ~= ch; 5349 } 5350 receiver(s); 5351 } 5352 } 5353 } else version(X11) { 5354 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5355 } else version(OSXCocoa) { 5356 throw new NotYetImplementedException(); 5357 } else static assert(0); 5358 } 5359 5360 // FIXME: a clipboard listener might be cool btw 5361 5362 /++ 5363 this does a delegate because it is actually an async call on X... 5364 the receiver may never be called if the clipboard is empty or unavailable 5365 gets image from the clipboard. 5366 5367 templated because it introduces an optional dependency on arsd.bmp 5368 +/ 5369 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 5370 version(Windows) { 5371 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5372 if(OpenClipboard(hwndOwner) == 0) 5373 throw new Exception("OpenClipboard"); 5374 scope(exit) 5375 CloseClipboard(); 5376 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 5377 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 5378 scope(exit) 5379 GlobalUnlock(dataHandle); 5380 5381 auto len = GlobalSize(dataHandle); 5382 5383 import arsd.bmp; 5384 auto img = readBmp(data[0 .. len], false); 5385 receiver(img); 5386 } 5387 } 5388 } else version(X11) { 5389 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5390 } else version(OSXCocoa) { 5391 throw new NotYetImplementedException(); 5392 } else static assert(0); 5393 } 5394 5395 version(Windows) 5396 struct WCharzBuffer { 5397 wchar[] buffer; 5398 wchar[256] staticBuffer = void; 5399 5400 size_t length() { 5401 return buffer.length; 5402 } 5403 5404 wchar* ptr() { 5405 return buffer.ptr; 5406 } 5407 5408 wchar[] slice() { 5409 return buffer; 5410 } 5411 5412 void copyInto(R)(ref R r) { 5413 static if(is(R == wchar[N], size_t N)) { 5414 r[0 .. this.length] = slice[]; 5415 r[this.length] = 0; 5416 } else static assert(0, "can only copy into wchar[n], not " ~ R.stringof); 5417 } 5418 5419 /++ 5420 conversionFlags = [WindowsStringConversionFlags] 5421 +/ 5422 this(in char[] data, int conversionFlags = 0) { 5423 conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name 5424 auto sz = sizeOfConvertedWstring(data, conversionFlags); 5425 if(sz > staticBuffer.length) 5426 buffer = new wchar[](sz); 5427 else 5428 buffer = staticBuffer[]; 5429 5430 buffer = makeWindowsString(data, buffer, conversionFlags); 5431 } 5432 } 5433 5434 version(Windows) 5435 int sizeOfConvertedWstring(in char[] s, int conversionFlags) { 5436 int size = 0; 5437 5438 if(conversionFlags & WindowsStringConversionFlags.convertNewLines) { 5439 // need to convert line endings, which means the length will get bigger. 5440 5441 // BTW I betcha this could be faster with some simd stuff. 5442 char last; 5443 foreach(char ch; s) { 5444 if(ch == 10 && last != 13) 5445 size++; // will add a 13 before it... 5446 size++; 5447 last = ch; 5448 } 5449 } else { 5450 // no conversion necessary, just estimate based on length 5451 /* 5452 I don't think there's any string with a longer length 5453 in code units when encoded in UTF-16 than it has in UTF-8. 5454 This will probably over allocate, but that's OK. 5455 */ 5456 size = cast(int) s.length; 5457 } 5458 5459 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) 5460 size++; 5461 5462 return size; 5463 } 5464 5465 version(Windows) 5466 enum WindowsStringConversionFlags : int { 5467 zeroTerminate = 1, 5468 convertNewLines = 2, 5469 } 5470 5471 version(Windows) 5472 class WindowsApiException : Exception { 5473 char[256] buffer; 5474 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 5475 assert(msg.length < 100); 5476 5477 auto error = GetLastError(); 5478 buffer[0 .. msg.length] = msg; 5479 buffer[msg.length] = ' '; 5480 5481 int pos = cast(int) msg.length + 1; 5482 5483 if(error == 0) 5484 buffer[pos++] = '0'; 5485 else { 5486 5487 auto ec = error; 5488 auto init = pos; 5489 while(ec) { 5490 buffer[pos++] = (ec % 10) + '0'; 5491 ec /= 10; 5492 } 5493 5494 buffer[pos++] = ' '; 5495 5496 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); 5497 5498 pos += size; 5499 } 5500 5501 5502 super(cast(string) buffer[0 .. pos], file, line, next); 5503 } 5504 } 5505 5506 class ErrnoApiException : Exception { 5507 char[256] buffer; 5508 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 5509 assert(msg.length < 100); 5510 5511 import core.stdc.errno; 5512 auto error = errno; 5513 buffer[0 .. msg.length] = msg; 5514 buffer[msg.length] = ' '; 5515 5516 int pos = cast(int) msg.length + 1; 5517 5518 if(error == 0) 5519 buffer[pos++] = '0'; 5520 else { 5521 auto init = pos; 5522 while(error) { 5523 buffer[pos++] = (error % 10) + '0'; 5524 error /= 10; 5525 } 5526 for(int i = 0; i < (pos - init) / 2; i++) { 5527 char c = buffer[i + init]; 5528 buffer[i + init] = buffer[pos - (i + init) - 1]; 5529 buffer[pos - (i + init) - 1] = c; 5530 } 5531 } 5532 5533 5534 super(cast(string) buffer[0 .. pos], file, line, next); 5535 } 5536 5537 } 5538 5539 version(Windows) 5540 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) { 5541 if(str.length == 0) 5542 return null; 5543 5544 int pos = 0; 5545 dchar last; 5546 foreach(dchar c; str) { 5547 if(c <= 0xFFFF) { 5548 if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13) 5549 buffer[pos++] = 13; 5550 buffer[pos++] = cast(wchar) c; 5551 } else if(c <= 0x10FFFF) { 5552 buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 5553 buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); 5554 } 5555 5556 last = c; 5557 } 5558 5559 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) { 5560 buffer[pos] = 0; 5561 } 5562 5563 return buffer[0 .. pos]; 5564 } 5565 5566 version(Windows) 5567 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) { 5568 if(str.length == 0) 5569 return null; 5570 5571 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null); 5572 if(got == 0) { 5573 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 5574 throw new Exception("not enough buffer"); 5575 else 5576 throw new Exception("conversion"); // FIXME: GetLastError 5577 } 5578 return buffer[0 .. got]; 5579 } 5580 5581 version(Windows) 5582 string makeUtf8StringFromWindowsString(in wchar[] str) { 5583 char[] buffer; 5584 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null); 5585 buffer.length = got; 5586 5587 // it is unique because we just allocated it above! 5588 return cast(string) makeUtf8StringFromWindowsString(str, buffer); 5589 } 5590 5591 version(Windows) 5592 string makeUtf8StringFromWindowsString(wchar* str) { 5593 char[] buffer; 5594 auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null); 5595 buffer.length = got; 5596 5597 got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null); 5598 if(got == 0) { 5599 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 5600 throw new Exception("not enough buffer"); 5601 else 5602 throw new Exception("conversion"); // FIXME: GetLastError 5603 } 5604 return cast(string) buffer[0 .. got]; 5605 } 5606 5607 int findIndexOfZero(in wchar[] str) { 5608 foreach(idx, wchar ch; str) 5609 if(ch == 0) 5610 return cast(int) idx; 5611 return cast(int) str.length; 5612 } 5613 int findIndexOfZero(in char[] str) { 5614 foreach(idx, char ch; str) 5615 if(ch == 0) 5616 return cast(int) idx; 5617 return cast(int) str.length; 5618 } 5619 5620 /// Copies some text to the clipboard. 5621 void setClipboardText(SimpleWindow clipboardOwner, string text) { 5622 assert(clipboardOwner !is null); 5623 version(Windows) { 5624 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5625 throw new Exception("OpenClipboard"); 5626 scope(exit) 5627 CloseClipboard(); 5628 EmptyClipboard(); 5629 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5630 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 5631 if(handle is null) throw new Exception("GlobalAlloc"); 5632 if(auto data = cast(wchar*) GlobalLock(handle)) { 5633 auto slice = data[0 .. sz]; 5634 scope(failure) 5635 GlobalUnlock(handle); 5636 5637 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5638 5639 GlobalUnlock(handle); 5640 SetClipboardData(CF_UNICODETEXT, handle); 5641 } 5642 } else version(X11) { 5643 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 5644 } else version(OSXCocoa) { 5645 throw new NotYetImplementedException(); 5646 } else static assert(0); 5647 } 5648 5649 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 5650 assert(clipboardOwner !is null); 5651 version(Windows) { 5652 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5653 throw new Exception("OpenClipboard"); 5654 scope(exit) 5655 CloseClipboard(); 5656 EmptyClipboard(); 5657 5658 5659 import arsd.bmp; 5660 ubyte[] mdata; 5661 mdata.reserve(img.width * img.height); 5662 void sink(ubyte b) { 5663 mdata ~= b; 5664 } 5665 writeBmpIndirect(img, &sink, false); 5666 5667 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 5668 if(handle is null) throw new Exception("GlobalAlloc"); 5669 if(auto data = cast(ubyte*) GlobalLock(handle)) { 5670 auto slice = data[0 .. mdata.length]; 5671 scope(failure) 5672 GlobalUnlock(handle); 5673 5674 slice[] = mdata[]; 5675 5676 GlobalUnlock(handle); 5677 SetClipboardData(CF_DIB, handle); 5678 } 5679 } else version(X11) { 5680 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 5681 mixin X11SetSelectionHandler_Basics; 5682 private const(ubyte)[] mdata; 5683 private const(ubyte)[] mdata_original; 5684 this(MemoryImage img) { 5685 import arsd.bmp; 5686 5687 mdata.reserve(img.width * img.height); 5688 void sink(ubyte b) { 5689 mdata ~= b; 5690 } 5691 writeBmpIndirect(img, &sink, true); 5692 5693 mdata_original = mdata; 5694 } 5695 5696 Atom[] availableFormats() { 5697 auto display = XDisplayConnection.get; 5698 return [ 5699 GetAtom!"image/bmp"(display), 5700 GetAtom!"TARGETS"(display) 5701 ]; 5702 } 5703 5704 ubyte[] getData(Atom format, return scope ubyte[] data) { 5705 if(mdata.length < data.length) { 5706 data[0 .. mdata.length] = mdata[]; 5707 auto ret = data[0 .. mdata.length]; 5708 mdata = mdata[$..$]; 5709 return ret; 5710 } else { 5711 data[] = mdata[0 .. data.length]; 5712 mdata = mdata[data.length .. $]; 5713 return data[]; 5714 } 5715 } 5716 5717 void done() { 5718 mdata = mdata_original; 5719 } 5720 } 5721 5722 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 5723 } else version(OSXCocoa) { 5724 throw new NotYetImplementedException(); 5725 } else static assert(0); 5726 } 5727 5728 5729 version(X11) { 5730 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 5731 5732 private Atom*[] interredAtoms; // for discardAndRecreate 5733 5734 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 5735 /// Platform-specific for X11. 5736 /// History: On February 21, 2021, I changed the default value of `create` to be true. 5737 @property Atom GetAtom(string name, bool create = true)(Display* display) { 5738 static Atom a; 5739 if(!a) { 5740 a = XInternAtom(display, name, !create); 5741 interredAtoms ~= &a; 5742 } 5743 if(a == None) 5744 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 5745 return a; 5746 } 5747 5748 /// Platform-specific for X11 - gets atom names as a string. 5749 string getAtomName(Atom atom, Display* display) { 5750 auto got = XGetAtomName(display, atom); 5751 scope(exit) XFree(got); 5752 import core.stdc.string; 5753 string s = got[0 .. strlen(got)].idup; 5754 return s; 5755 } 5756 5757 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 5758 void setPrimarySelection(SimpleWindow window, string text) { 5759 setX11Selection!"PRIMARY"(window, text); 5760 } 5761 5762 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 5763 void setSecondarySelection(SimpleWindow window, string text) { 5764 setX11Selection!"SECONDARY"(window, text); 5765 } 5766 5767 interface X11SetSelectionHandler { 5768 // should include TARGETS right now 5769 Atom[] availableFormats(); 5770 // Return the slice of data you filled, empty slice if done. 5771 // this is to support the incremental thing 5772 ubyte[] getData(Atom format, return scope ubyte[] data); 5773 5774 void done(); 5775 5776 void handleRequest(XEvent); 5777 5778 bool matchesIncr(Window, Atom); 5779 void sendMoreIncr(XPropertyEvent*); 5780 } 5781 5782 mixin template X11SetSelectionHandler_Basics() { 5783 Window incrWindow; 5784 Atom incrAtom; 5785 Atom selectionAtom; 5786 Atom formatAtom; 5787 ubyte[] toSend; 5788 bool matchesIncr(Window w, Atom a) { 5789 return incrAtom && incrAtom == a && w == incrWindow; 5790 } 5791 void sendMoreIncr(XPropertyEvent* event) { 5792 auto display = XDisplayConnection.get; 5793 5794 XChangeProperty (display, 5795 incrWindow, 5796 incrAtom, 5797 formatAtom, 5798 8 /* bits */, PropModeReplace, 5799 toSend.ptr, cast(int) toSend.length); 5800 5801 if(toSend.length != 0) { 5802 toSend = this.getData(formatAtom, toSend[]); 5803 } else { 5804 this.done(); 5805 incrWindow = None; 5806 incrAtom = None; 5807 selectionAtom = None; 5808 formatAtom = None; 5809 toSend = null; 5810 } 5811 } 5812 void handleRequest(XEvent ev) { 5813 5814 auto display = XDisplayConnection.get; 5815 5816 XSelectionRequestEvent* event = &ev.xselectionrequest; 5817 XSelectionEvent selectionEvent; 5818 selectionEvent.type = EventType.SelectionNotify; 5819 selectionEvent.display = event.display; 5820 selectionEvent.requestor = event.requestor; 5821 selectionEvent.selection = event.selection; 5822 selectionEvent.time = event.time; 5823 selectionEvent.target = event.target; 5824 5825 bool supportedType() { 5826 foreach(t; this.availableFormats()) 5827 if(t == event.target) 5828 return true; 5829 return false; 5830 } 5831 5832 if(event.property == None) { 5833 selectionEvent.property = event.target; 5834 5835 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 5836 XFlush(display); 5837 } if(event.target == GetAtom!"TARGETS"(display)) { 5838 /* respond with the supported types */ 5839 auto tlist = this.availableFormats(); 5840 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 5841 selectionEvent.property = event.property; 5842 5843 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 5844 XFlush(display); 5845 } else if(supportedType()) { 5846 auto buffer = new ubyte[](1024 * 64); 5847 auto toSend = this.getData(event.target, buffer[]); 5848 5849 if(toSend.length < 32 * 1024) { 5850 // small enough to send directly... 5851 selectionEvent.property = event.property; 5852 XChangeProperty (display, 5853 selectionEvent.requestor, 5854 selectionEvent.property, 5855 event.target, 5856 8 /* bits */, 0 /* PropModeReplace */, 5857 toSend.ptr, cast(int) toSend.length); 5858 5859 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 5860 XFlush(display); 5861 } else { 5862 // large, let's send incrementally 5863 arch_ulong l = toSend.length; 5864 5865 // if I wanted other events from this window don't want to clear that out.... 5866 XWindowAttributes xwa; 5867 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 5868 5869 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 5870 5871 incrWindow = event.requestor; 5872 incrAtom = event.property; 5873 formatAtom = event.target; 5874 selectionAtom = event.selection; 5875 this.toSend = toSend; 5876 5877 selectionEvent.property = event.property; 5878 XChangeProperty (display, 5879 selectionEvent.requestor, 5880 selectionEvent.property, 5881 GetAtom!"INCR"(display), 5882 32 /* bits */, PropModeReplace, 5883 &l, 1); 5884 5885 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 5886 XFlush(display); 5887 } 5888 //if(after) 5889 //after(); 5890 } else { 5891 debug(sdpy_clip) { 5892 import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display)); 5893 } 5894 selectionEvent.property = None; // I don't know how to handle this type... 5895 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 5896 XFlush(display); 5897 } 5898 } 5899 } 5900 5901 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 5902 mixin X11SetSelectionHandler_Basics; 5903 private const(ubyte)[] text; 5904 private const(ubyte)[] text_original; 5905 this(string text) { 5906 this.text = cast(const ubyte[]) text; 5907 this.text_original = this.text; 5908 } 5909 Atom[] availableFormats() { 5910 auto display = XDisplayConnection.get; 5911 return [ 5912 GetAtom!"UTF8_STRING"(display), 5913 GetAtom!"text/plain"(display), 5914 XA_STRING, 5915 GetAtom!"TARGETS"(display) 5916 ]; 5917 } 5918 5919 ubyte[] getData(Atom format, return scope ubyte[] data) { 5920 if(text.length < data.length) { 5921 data[0 .. text.length] = text[]; 5922 return data[0 .. text.length]; 5923 } else { 5924 data[] = text[0 .. data.length]; 5925 text = text[data.length .. $]; 5926 return data[]; 5927 } 5928 } 5929 5930 void done() { 5931 text = text_original; 5932 } 5933 } 5934 5935 /// 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?!) 5936 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 5937 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 5938 } 5939 5940 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 5941 assert(window !is null); 5942 5943 auto display = XDisplayConnection.get(); 5944 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 5945 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 5946 else Atom a = GetAtom!atomName(display); 5947 5948 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 5949 5950 window.impl.setSelectionHandlers[a] = data; 5951 } 5952 5953 /// 5954 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 5955 getX11Selection!"PRIMARY"(window, handler); 5956 } 5957 5958 // added July 28, 2020 5959 // undocumented as experimental tho 5960 interface X11GetSelectionHandler { 5961 void handleData(Atom target, in ubyte[] data); 5962 Atom findBestFormat(Atom[] answer); 5963 5964 void prepareIncremental(Window, Atom); 5965 bool matchesIncr(Window, Atom); 5966 void handleIncrData(Atom, in ubyte[] data); 5967 } 5968 5969 mixin template X11GetSelectionHandler_Basics() { 5970 Window incrWindow; 5971 Atom incrAtom; 5972 5973 void prepareIncremental(Window w, Atom a) { 5974 incrWindow = w; 5975 incrAtom = a; 5976 } 5977 bool matchesIncr(Window w, Atom a) { 5978 return incrWindow == w && incrAtom == a; 5979 } 5980 5981 Atom incrFormatAtom; 5982 ubyte[] incrData; 5983 void handleIncrData(Atom format, in ubyte[] data) { 5984 incrFormatAtom = format; 5985 5986 if(data.length) 5987 incrData ~= data; 5988 else 5989 handleData(incrFormatAtom, incrData); 5990 5991 } 5992 } 5993 5994 /// 5995 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 5996 assert(window !is null); 5997 5998 auto display = XDisplayConnection.get(); 5999 auto atom = GetAtom!atomName(display); 6000 6001 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6002 this(void delegate(in char[]) handler) { 6003 this.handler = handler; 6004 } 6005 6006 mixin X11GetSelectionHandler_Basics; 6007 6008 void delegate(in char[]) handler; 6009 6010 void handleData(Atom target, in ubyte[] data) { 6011 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6012 handler(cast(const char[]) data); 6013 } 6014 6015 Atom findBestFormat(Atom[] answer) { 6016 Atom best = None; 6017 foreach(option; answer) { 6018 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6019 best = option; 6020 break; 6021 } else if(option == XA_STRING) { 6022 best = option; 6023 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6024 best = option; 6025 } 6026 } 6027 return best; 6028 } 6029 } 6030 6031 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6032 6033 auto target = GetAtom!"TARGETS"(display); 6034 6035 // SDD_DATA is "simpledisplay.d data" 6036 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6037 } 6038 6039 /// Gets the image on the clipboard, if there is one. Added July 2020. 6040 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6041 assert(window !is null); 6042 6043 auto display = XDisplayConnection.get(); 6044 auto atom = GetAtom!atomName(display); 6045 6046 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6047 this(void delegate(MemoryImage) handler) { 6048 this.handler = handler; 6049 } 6050 6051 mixin X11GetSelectionHandler_Basics; 6052 6053 void delegate(MemoryImage) handler; 6054 6055 void handleData(Atom target, in ubyte[] data) { 6056 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6057 import arsd.bmp; 6058 handler(readBmp(data)); 6059 } 6060 } 6061 6062 Atom findBestFormat(Atom[] answer) { 6063 Atom best = None; 6064 foreach(option; answer) { 6065 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6066 best = option; 6067 } 6068 } 6069 return best; 6070 } 6071 6072 } 6073 6074 6075 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6076 6077 auto target = GetAtom!"TARGETS"(display); 6078 6079 // SDD_DATA is "simpledisplay.d data" 6080 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6081 } 6082 6083 6084 /// 6085 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6086 Atom actualType; 6087 int actualFormat; 6088 arch_ulong actualItems; 6089 arch_ulong bytesRemaining; 6090 void* data; 6091 6092 auto display = XDisplayConnection.get(); 6093 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6094 if(actualFormat == 0) 6095 return null; 6096 else { 6097 int byteLength; 6098 if(actualFormat == 32) { 6099 // 32 means it is a C long... which is variable length 6100 actualFormat = cast(int) arch_long.sizeof * 8; 6101 } 6102 6103 // then it is just a bit count 6104 byteLength = cast(int) (actualItems * actualFormat / 8); 6105 6106 auto d = new ubyte[](byteLength); 6107 d[] = cast(ubyte[]) data[0 .. byteLength]; 6108 XFree(data); 6109 return d; 6110 } 6111 } 6112 return null; 6113 } 6114 6115 /* defined in the systray spec */ 6116 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6117 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6118 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6119 6120 6121 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6122 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6123 public class GlobalHotkey { 6124 KeyEvent key; 6125 void delegate () handler; 6126 6127 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6128 6129 /// Create from initialzed KeyEvent object 6130 this (KeyEvent akey, void delegate () ahandler=null) { 6131 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6132 key = akey; 6133 handler = ahandler; 6134 } 6135 6136 /// Create from emacs-like key name ("C-M-Y", etc.) 6137 this (const(char)[] akey, void delegate () ahandler=null) { 6138 key = KeyEvent.parse(akey); 6139 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6140 handler = ahandler; 6141 } 6142 6143 } 6144 6145 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6146 //conwriteln("failed to grab key"); 6147 GlobalHotkeyManager.ghfailed = true; 6148 return 0; 6149 } 6150 6151 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6152 Image.impl.xshmfailed = true; 6153 return 0; 6154 } 6155 6156 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6157 import core.stdc.stdio; 6158 char[265] buffer; 6159 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6160 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); 6161 return 0; 6162 } 6163 6164 /++ 6165 Global hotkey manager. It contains static methods to manage global hotkeys. 6166 6167 --- 6168 try { 6169 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6170 } catch (Exception e) { 6171 conwriteln("ERROR registering hotkey!"); 6172 } 6173 --- 6174 6175 The key strings are based on Emacs. In practical terms, 6176 `M` means `alt` and `H` means the Windows logo key. `C` 6177 is `ctrl`. 6178 6179 $(WARNING 6180 This is X-specific right now. If you are on 6181 Windows, try [registerHotKey] instead. 6182 6183 We will probably merge these into a single 6184 interface later. 6185 ) 6186 +/ 6187 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6188 version(X11) { 6189 void recreateAfterDisconnect() { 6190 throw new Exception("NOT IMPLEMENTED"); 6191 } 6192 void discardConnectionState() { 6193 throw new Exception("NOT IMPLEMENTED"); 6194 } 6195 } 6196 6197 private static immutable uint[8] masklist = [ 0, 6198 KeyOrButtonMask.LockMask, 6199 KeyOrButtonMask.Mod2Mask, 6200 KeyOrButtonMask.Mod3Mask, 6201 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6202 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6203 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6204 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6205 ]; 6206 private __gshared GlobalHotkeyManager ghmanager; 6207 private __gshared bool ghfailed = false; 6208 6209 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6210 if (modmask == 0) return false; 6211 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6212 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6213 return true; 6214 } 6215 6216 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6217 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6218 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6219 return modmask; 6220 } 6221 6222 private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) { 6223 uint keycode = cast(uint)ke.key; 6224 auto dpy = XDisplayConnection.get; 6225 return XKeysymToKeycode(dpy, keycode); 6226 } 6227 6228 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6229 6230 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6231 6232 NativeEventHandler getNativeEventHandler () { 6233 return delegate int (XEvent e) { 6234 if (e.type != EventType.KeyPress) return 1; 6235 auto kev = cast(const(XKeyEvent)*)&e; 6236 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6237 if (auto ghkp = hash in globalHotkeyList) { 6238 try { 6239 ghkp.doHandle(); 6240 } catch (Exception e) { 6241 import core.stdc.stdio : stderr, fprintf; 6242 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6243 } 6244 } 6245 return 1; 6246 }; 6247 } 6248 6249 private this () { 6250 auto dpy = XDisplayConnection.get; 6251 auto root = RootWindow(dpy, DefaultScreen(dpy)); 6252 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 6253 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 6254 } 6255 6256 /// Register new global hotkey with initialized `GlobalHotkey` object. 6257 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 6258 static void register (GlobalHotkey gh) { 6259 if (gh is null) return; 6260 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 6261 6262 auto dpy = XDisplayConnection.get; 6263 immutable keycode = keyEvent2KeyCode(gh.key); 6264 6265 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 6266 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 6267 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 6268 XSync(dpy, 0/*False*/); 6269 6270 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6271 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6272 ghfailed = false; 6273 foreach (immutable uint ormask; masklist[]) { 6274 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 6275 } 6276 XSync(dpy, 0/*False*/); 6277 XSetErrorHandler(savedErrorHandler); 6278 6279 if (ghfailed) { 6280 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6281 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 6282 XSync(dpy, 0/*False*/); 6283 XSetErrorHandler(savedErrorHandler); 6284 throw new Exception("cannot register global hotkey"); 6285 } 6286 6287 globalHotkeyList[hash] = gh; 6288 } 6289 6290 /// Ditto 6291 static void register (const(char)[] akey, void delegate () ahandler) { 6292 register(new GlobalHotkey(akey, ahandler)); 6293 } 6294 6295 private static void removeByHash (ulong hash) { 6296 if (auto ghp = hash in globalHotkeyList) { 6297 auto dpy = XDisplayConnection.get; 6298 immutable keycode = keyEvent2KeyCode(ghp.key); 6299 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6300 XSync(dpy, 0/*False*/); 6301 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6302 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 6303 XSync(dpy, 0/*False*/); 6304 XSetErrorHandler(savedErrorHandler); 6305 globalHotkeyList.remove(hash); 6306 } 6307 } 6308 6309 /// Register new global hotkey with previously used `GlobalHotkey` object. 6310 /// It is safe to unregister unknown or invalid hotkey. 6311 static void unregister (GlobalHotkey gh) { 6312 //TODO: add second AA for faster search? prolly doesn't worth it. 6313 if (gh is null) return; 6314 foreach (const ref kv; globalHotkeyList.byKeyValue) { 6315 if (kv.value is gh) { 6316 removeByHash(kv.key); 6317 return; 6318 } 6319 } 6320 } 6321 6322 /// Ditto. 6323 static void unregister (const(char)[] key) { 6324 auto kev = KeyEvent.parse(key); 6325 immutable keycode = keyEvent2KeyCode(kev); 6326 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 6327 } 6328 } 6329 } 6330 6331 version(Windows) { 6332 /// Platform-specific for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application). 6333 void sendSyntheticInput(wstring s) { 6334 INPUT[] inputs; 6335 inputs.reserve(s.length * 2); 6336 6337 foreach(wchar c; s) { 6338 INPUT input; 6339 input.type = INPUT_KEYBOARD; 6340 input.ki.wScan = c; 6341 input.ki.dwFlags = KEYEVENTF_UNICODE; 6342 inputs ~= input; 6343 6344 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6345 inputs ~= input; 6346 } 6347 6348 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6349 throw new Exception("SendInput failed"); 6350 } 6351 } 6352 6353 6354 // global hotkey helper function 6355 6356 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. 6357 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6358 __gshared int hotkeyId = 0; 6359 int id = ++hotkeyId; 6360 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6361 throw new Exception("RegisterHotKey failed"); 6362 6363 __gshared void delegate()[WPARAM][HWND] handlers; 6364 6365 handlers[window.impl.hwnd][id] = handler; 6366 6367 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 6368 6369 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 6370 switch(msg) { 6371 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6372 case WM_HOTKEY: 6373 if(auto list = hwnd in handlers) { 6374 if(auto h = wParam in *list) { 6375 (*h)(); 6376 return 0; 6377 } 6378 } 6379 goto default; 6380 default: 6381 } 6382 if(oldHandler) 6383 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 6384 return 1; // pass it on 6385 }; 6386 6387 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6388 oldHandler = window.handleNativeEvent; 6389 window.handleNativeEvent = nativeEventHandler; 6390 } 6391 6392 return id; 6393 } 6394 6395 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 6396 void unregisterHotKey(SimpleWindow window, int id) { 6397 if(!UnregisterHotKey(window.impl.hwnd, id)) 6398 throw new Exception("UnregisterHotKey"); 6399 } 6400 } 6401 6402 version (X11) { 6403 pragma(lib, "dl"); 6404 import core.sys.posix.dlfcn; 6405 6406 /++ 6407 Allows for sending synthetic input to the X server via the Xtst 6408 extension. 6409 6410 Please remember user input is meant to be user - don't use this 6411 if you have some other alternative! 6412 6413 If you need this on Windows btw, the top-level [sendSyntheticInput] shows 6414 the Win32 api to start it, but I only did basics there, PR welcome if you like, 6415 it is an easy enough function to use. 6416 6417 History: Added May 17, 2020. 6418 +/ 6419 struct SyntheticInput { 6420 @disable this(); 6421 6422 private void* lib; 6423 private int* refcount; 6424 6425 private extern(C) { 6426 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6427 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6428 } 6429 6430 /// The dummy param must be 0. 6431 this(int dummy) { 6432 lib = dlopen("libXtst.so", RTLD_NOW); 6433 if(lib is null) 6434 throw new Exception("cannot load xtest lib extension"); 6435 scope(failure) 6436 dlclose(lib); 6437 6438 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6439 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6440 6441 if(XTestFakeKeyEvent is null) 6442 throw new Exception("No XTestFakeKeyEvent"); 6443 if(XTestFakeButtonEvent is null) 6444 throw new Exception("No XTestFakeButtonEvent"); 6445 6446 refcount = new int; 6447 *refcount = 1; 6448 } 6449 6450 this(this) { 6451 if(refcount) 6452 *refcount += 1; 6453 } 6454 6455 ~this() { 6456 if(refcount) { 6457 *refcount -= 1; 6458 if(*refcount == 0) 6459 // I commented this because if I close the lib before 6460 // XCloseDisplay, it is liable to segfault... so just 6461 // gonna keep it loaded if it is loaded, no big deal 6462 // anyway. 6463 {} // dlclose(lib); 6464 } 6465 } 6466 6467 /// This ONLY works with basic ascii! 6468 void sendSyntheticInput(string s) { 6469 int delay = 0; 6470 foreach(ch; s) { 6471 pressKey(cast(Key) ch, true, delay); 6472 pressKey(cast(Key) ch, false, delay); 6473 delay += 5; 6474 } 6475 } 6476 6477 /++ 6478 Sends a fake press key event. 6479 6480 Please note you need to call [flushGui] or return to the event loop for this to actually be sent. 6481 +/ 6482 void pressKey(Key key, bool pressed, int delay = 0) { 6483 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6484 } 6485 6486 /// 6487 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6488 int btn; 6489 6490 switch(button) { 6491 case MouseButton.left: btn = 1; break; 6492 case MouseButton.middle: btn = 2; break; 6493 case MouseButton.right: btn = 3; break; 6494 case MouseButton.wheelUp: btn = 4; break; 6495 case MouseButton.wheelDown: btn = 5; break; 6496 case MouseButton.backButton: btn = 8; break; 6497 case MouseButton.forwardButton: btn = 9; break; 6498 default: 6499 } 6500 6501 assert(btn); 6502 6503 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 6504 } 6505 6506 /// 6507 static void moveMouseArrowBy(int dx, int dy) { 6508 auto disp = XDisplayConnection.get(); 6509 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 6510 XFlush(disp); 6511 } 6512 6513 /// 6514 static void moveMouseArrowTo(int x, int y) { 6515 auto disp = XDisplayConnection.get(); 6516 auto root = RootWindow(disp, DefaultScreen(disp)); 6517 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 6518 XFlush(disp); 6519 } 6520 } 6521 } 6522 6523 6524 6525 /++ 6526 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 6527 6528 See_Also: 6529 $(LIST 6530 *[ScreenPainter] 6531 *[ScreenPainter.rasterOp] 6532 ) 6533 +/ 6534 enum RasterOp { 6535 normal, /// Replaces the pixel. 6536 xor, /// Uses bitwise xor to draw. 6537 } 6538 6539 // being phobos-free keeps the size WAY down 6540 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 6541 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 6542 package(arsd) const(wchar)* toWStringz(string s) { 6543 wstring r; 6544 foreach(dchar c; s) 6545 r ~= c; 6546 r ~= '\0'; 6547 return r.ptr; 6548 } 6549 private string[] split(in void[] a, char c) { 6550 string[] ret; 6551 size_t previous = 0; 6552 foreach(i, char ch; cast(ubyte[]) a) { 6553 if(ch == c) { 6554 ret ~= cast(string) a[previous .. i]; 6555 previous = i + 1; 6556 } 6557 } 6558 if(previous != a.length) 6559 ret ~= cast(string) a[previous .. $]; 6560 return ret; 6561 } 6562 6563 version(without_opengl) { 6564 enum OpenGlOptions { 6565 no, 6566 } 6567 } else { 6568 /++ 6569 Determines if you want an OpenGL context created on the new window. 6570 6571 6572 See more: [#topics-3d|in the 3d topic]. 6573 6574 --- 6575 import arsd.simpledisplay; 6576 void main() { 6577 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 6578 6579 // Set up the matrix 6580 window.setAsCurrentOpenGlContext(); // make this window active 6581 6582 // This is called on each frame, we will draw our scene 6583 window.redrawOpenGlScene = delegate() { 6584 6585 }; 6586 6587 window.eventLoop(0); 6588 } 6589 --- 6590 +/ 6591 enum OpenGlOptions { 6592 no, /// No OpenGL context is created 6593 yes, /// Yes, create an OpenGL context 6594 } 6595 6596 version(X11) { 6597 static if (!SdpyIsUsingIVGLBinds) { 6598 6599 6600 struct __GLXFBConfigRec {} 6601 alias GLXFBConfig = __GLXFBConfigRec*; 6602 6603 //pragma(lib, "GL"); 6604 //pragma(lib, "GLU"); 6605 interface GLX { 6606 extern(C) nothrow @nogc { 6607 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 6608 const int *attrib_list); 6609 6610 void glXCopyContext(Display *dpy, GLXContext src, 6611 GLXContext dst, arch_ulong mask); 6612 6613 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 6614 GLXContext share_list, Bool direct); 6615 6616 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 6617 Pixmap pixmap); 6618 6619 void glXDestroyContext(Display *dpy, GLXContext ctx); 6620 6621 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 6622 6623 int glXGetConfig(Display *dpy, XVisualInfo *vis, 6624 int attrib, int *value); 6625 6626 GLXContext glXGetCurrentContext(); 6627 6628 GLXDrawable glXGetCurrentDrawable(); 6629 6630 Bool glXIsDirect(Display *dpy, GLXContext ctx); 6631 6632 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 6633 GLXContext ctx); 6634 6635 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 6636 6637 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 6638 6639 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 6640 6641 void glXUseXFont(Font font, int first, int count, int list_base); 6642 6643 void glXWaitGL(); 6644 6645 void glXWaitX(); 6646 6647 6648 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 6649 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 6650 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 6651 6652 char* glXQueryExtensionsString (Display*, int); 6653 void* glXGetProcAddress (const(char)*); 6654 6655 } 6656 } 6657 6658 version(OSX) 6659 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 6660 else 6661 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 6662 shared static this() { 6663 glx.loadDynamicLibrary(); 6664 } 6665 6666 alias glbindGetProcAddress = glXGetProcAddress; 6667 } 6668 } else version(Windows) { 6669 /* it is done below by interface GL */ 6670 } else 6671 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."); 6672 } 6673 6674 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 6675 alias Resizablity = Resizability; 6676 6677 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 6678 enum Resizability { 6679 fixedSize, /// the window cannot be resized 6680 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. 6681 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. 6682 6683 // FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events 6684 } 6685 6686 6687 /++ 6688 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 6689 +/ 6690 enum TextAlignment : uint { 6691 Left = 0, /// 6692 Center = 1, /// 6693 Right = 2, /// 6694 6695 VerticalTop = 0, /// 6696 VerticalCenter = 4, /// 6697 VerticalBottom = 8, /// 6698 } 6699 6700 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 6701 alias Rectangle = arsd.color.Rectangle; 6702 6703 6704 /++ 6705 Keyboard press and release events. 6706 +/ 6707 struct KeyEvent { 6708 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 6709 Key key; 6710 ubyte hardwareCode; /// A platform and hardware specific code for the key 6711 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 6712 6713 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 6714 6715 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 6716 6717 SimpleWindow window; /// associated Window 6718 6719 /++ 6720 A view into the upcoming buffer holding coming character events that are sent if and only if neither 6721 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 6722 to predict if char events are actually coming.. 6723 6724 Only available on X systems since this information is not given ahead of time elsewhere. 6725 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 6726 6727 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 6728 and potential quirks I'd recommend avoiding it. 6729 6730 History: 6731 Added April 26, 2021 (dub v9.5) 6732 +/ 6733 version(X11) 6734 dchar[] charsPossible; 6735 6736 // convert key event to simplified string representation a-la emacs 6737 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 6738 uint dpos = 0; 6739 void put (const(char)[] s...) nothrow @trusted { 6740 static if (growdest) { 6741 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 6742 } else { 6743 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 6744 } 6745 } 6746 6747 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 6748 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 6749 } 6750 6751 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 6752 6753 // put modifiers 6754 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 6755 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 6756 putMod(ModifierState.alt, Key.Alt, "Alt+"); 6757 putMod(ModifierState.windows, Key.Shift, "Windows+"); 6758 putMod(ModifierState.shift, Key.Shift, "Shift+"); 6759 6760 if (this.key) { 6761 foreach (string kn; __traits(allMembers, Key)) { 6762 if (this.key == __traits(getMember, Key, kn)) { 6763 // HACK! 6764 static if (kn == "N0") put("0"); 6765 else static if (kn == "N1") put("1"); 6766 else static if (kn == "N2") put("2"); 6767 else static if (kn == "N3") put("3"); 6768 else static if (kn == "N4") put("4"); 6769 else static if (kn == "N5") put("5"); 6770 else static if (kn == "N6") put("6"); 6771 else static if (kn == "N7") put("7"); 6772 else static if (kn == "N8") put("8"); 6773 else static if (kn == "N9") put("9"); 6774 else put(kn); 6775 return dest[0..dpos]; 6776 } 6777 } 6778 put("Unknown"); 6779 } else { 6780 if (dpos && dest[dpos-1] == '+') --dpos; 6781 } 6782 return dest[0..dpos]; 6783 } 6784 6785 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 6786 6787 /** Parse string into key name with modifiers. It accepts things like: 6788 * 6789 * C-H-1 -- emacs style (ctrl, and windows, and 1) 6790 * 6791 * Ctrl+Win+1 -- windows style 6792 * 6793 * Ctrl-Win-1 -- '-' is a valid delimiter too 6794 * 6795 * Ctrl Win 1 -- and space 6796 * 6797 * and even "Win + 1 + Ctrl". 6798 */ 6799 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 6800 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 6801 6802 // remove trailing spaces 6803 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 6804 6805 // tokens delimited by blank, '+', or '-' 6806 // null on eol 6807 const(char)[] getToken () nothrow @trusted @nogc { 6808 // remove leading spaces and delimiters 6809 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 6810 if (name.length == 0) return null; // oops, no more tokens 6811 // get token 6812 size_t epos = 0; 6813 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 6814 assert(epos > 0 && epos <= name.length); 6815 auto res = name[0..epos]; 6816 name = name[epos..$]; 6817 return res; 6818 } 6819 6820 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 6821 if (s0.length != s1.length) return false; 6822 foreach (immutable ci, char c0; s0) { 6823 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 6824 char c1 = s1[ci]; 6825 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 6826 if (c0 != c1) return false; 6827 } 6828 return true; 6829 } 6830 6831 if (ignoreModsOut !is null) *ignoreModsOut = false; 6832 if (updown !is null) *updown = -1; 6833 KeyEvent res; 6834 res.key = cast(Key)0; // just in case 6835 const(char)[] tk, tkn; // last token 6836 bool allowEmascStyle = true; 6837 bool ignoreModifiers = false; 6838 tokenloop: for (;;) { 6839 tk = tkn; 6840 tkn = getToken(); 6841 //k8: yay, i took "Bloody Mess" trait from Fallout! 6842 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 6843 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 6844 if (allowEmascStyle && tkn.length != 0) { 6845 if (tk.length == 1) { 6846 char mdc = tk[0]; 6847 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 6848 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 6849 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 6850 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 6851 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 6852 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 6853 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 6854 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 6855 } 6856 } 6857 allowEmascStyle = false; 6858 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 6859 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 6860 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 6861 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 6862 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 6863 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 6864 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 6865 if (tk.length == 0) continue; 6866 // try key name 6867 if (res.key == 0) { 6868 // little hack 6869 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 6870 final switch (tk[0]) { 6871 case '0': tk = "N0"; break; 6872 case '1': tk = "N1"; break; 6873 case '2': tk = "N2"; break; 6874 case '3': tk = "N3"; break; 6875 case '4': tk = "N4"; break; 6876 case '5': tk = "N5"; break; 6877 case '6': tk = "N6"; break; 6878 case '7': tk = "N7"; break; 6879 case '8': tk = "N8"; break; 6880 case '9': tk = "N9"; break; 6881 } 6882 } 6883 foreach (string kn; __traits(allMembers, Key)) { 6884 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 6885 } 6886 } 6887 // unknown or duplicate key name, get out of here 6888 break; 6889 } 6890 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 6891 return res; // something 6892 } 6893 6894 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 6895 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 6896 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 6897 if (kk == k) { mask |= mst; kk = cast(Key)0; } 6898 } 6899 bool ignoreMods; 6900 int updown; 6901 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 6902 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 6903 if (this.key != ke.key) { 6904 // things like "ctrl+alt" are complicated 6905 uint tkm = this.modifierState&modmask; 6906 uint kkm = ke.modifierState&modmask; 6907 Key tk = this.key; 6908 // ke 6909 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 6910 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 6911 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 6912 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 6913 // this 6914 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 6915 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 6916 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 6917 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 6918 return (tk == ke.key && tkm == kkm); 6919 } 6920 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 6921 } 6922 } 6923 6924 /// Sets the application name. 6925 @property string ApplicationName(string name) { 6926 return _applicationName = name; 6927 } 6928 6929 string _applicationName; 6930 6931 /// ditto 6932 @property string ApplicationName() { 6933 if(_applicationName is null) { 6934 import core.runtime; 6935 return Runtime.args[0]; 6936 } 6937 return _applicationName; 6938 } 6939 6940 6941 /// Type of a [MouseEvent]. 6942 enum MouseEventType : int { 6943 motion = 0, /// The mouse moved inside the window 6944 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 6945 buttonReleased = 2, /// A mouse button was released 6946 } 6947 6948 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 6949 /++ 6950 Listen for this on your event listeners if you are interested in mouse action. 6951 6952 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. 6953 6954 Examples: 6955 6956 This will draw boxes on the window with the mouse as you hold the left button. 6957 --- 6958 import arsd.simpledisplay; 6959 6960 void main() { 6961 auto window = new SimpleWindow(); 6962 6963 window.eventLoop(0, 6964 (MouseEvent ev) { 6965 if(ev.modifierState & ModifierState.leftButtonDown) { 6966 auto painter = window.draw(); 6967 painter.fillColor = Color.red; 6968 painter.outlineColor = Color.black; 6969 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 6970 } 6971 } 6972 ); 6973 } 6974 --- 6975 +/ 6976 struct MouseEvent { 6977 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 6978 6979 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. 6980 int y; /// Current Y position of the cursor when the event fired. 6981 6982 int dx; /// Change in X position since last report 6983 int dy; /// Change in Y position since last report 6984 6985 MouseButton button; /// See [MouseButton] 6986 int modifierState; /// See [ModifierState] 6987 6988 version(X11) 6989 private Time timestamp; 6990 6991 /// Returns a linear representation of mouse button, 6992 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 6993 /// 6994 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 6995 @property ubyte buttonLinear() const { 6996 import core.bitop; 6997 if(button == 0) 6998 return 0; 6999 return (bsf(button) + 1) & 0b1111; 7000 } 7001 7002 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7003 7004 SimpleWindow window; /// The window in which the event happened. 7005 7006 Point globalCoordinates() { 7007 Point p; 7008 if(window is null) 7009 throw new Exception("wtf"); 7010 static if(UsingSimpledisplayX11) { 7011 Window child; 7012 XTranslateCoordinates( 7013 XDisplayConnection.get, 7014 window.impl.window, 7015 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7016 x, y, &p.x, &p.y, &child); 7017 return p; 7018 } else version(Windows) { 7019 POINT[1] points; 7020 points[0].x = x; 7021 points[0].y = y; 7022 MapWindowPoints( 7023 window.impl.hwnd, 7024 null, 7025 points.ptr, 7026 points.length 7027 ); 7028 p.x = points[0].x; 7029 p.y = points[0].y; 7030 7031 return p; 7032 } else version(OSXCocoa) { 7033 throw new NotYetImplementedException(); 7034 } else static assert(0); 7035 } 7036 7037 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7038 7039 /** 7040 can contain emacs-like modifier prefix 7041 case-insensitive names: 7042 lmbX/leftX 7043 rmbX/rightX 7044 mmbX/middleX 7045 wheelX 7046 motion (no prefix allowed) 7047 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7048 */ 7049 static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7050 if (str.length == 0) return false; // just in case 7051 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7052 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7053 auto anchor = str; 7054 uint mods = 0; // uint.max == any 7055 // interesting bits in kmod 7056 uint kmodmask = 7057 ModifierState.shift| 7058 ModifierState.ctrl| 7059 ModifierState.alt| 7060 ModifierState.windows| 7061 ModifierState.leftButtonDown| 7062 ModifierState.middleButtonDown| 7063 ModifierState.rightButtonDown| 7064 0; 7065 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7066 bool wasButtons = false; 7067 while (str.length) { 7068 if (str.ptr[0] <= ' ') { 7069 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7070 continue; 7071 } 7072 // one-letter modifier? 7073 if (str.length >= 2 && str.ptr[1] == '-') { 7074 switch (str.ptr[0]) { 7075 case '*': // "any" modifier (cannot be undone) 7076 mods = mods.max; 7077 break; 7078 case 'C': case 'c': // emacs "ctrl" 7079 if (mods != mods.max) mods |= ModifierState.ctrl; 7080 break; 7081 case 'M': case 'm': // emacs "meta" 7082 if (mods != mods.max) mods |= ModifierState.alt; 7083 break; 7084 case 'S': case 's': // emacs "shift" 7085 if (mods != mods.max) mods |= ModifierState.shift; 7086 break; 7087 case 'H': case 'h': // emacs "hyper" (aka winkey) 7088 if (mods != mods.max) mods |= ModifierState.windows; 7089 break; 7090 default: 7091 return false; // unknown modifier 7092 } 7093 str = str[2..$]; 7094 continue; 7095 } 7096 // word 7097 char[16] buf = void; // locased 7098 auto wep = 0; 7099 while (str.length) { 7100 immutable char ch = str.ptr[0]; 7101 if (ch <= ' ' || ch == '-') break; 7102 str = str[1..$]; 7103 if (wep > buf.length) return false; // too long 7104 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7105 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7106 else return false; // invalid char 7107 } 7108 if (wep == 0) return false; // just in case 7109 uint bnum; 7110 enum UpDown { None = -1, Up, Down, Any } 7111 auto updown = UpDown.None; // 0: up; 1: down 7112 switch (buf[0..wep]) { 7113 // left button 7114 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 7115 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 7116 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 7117 case "lmb": case "left": bnum = 0; break; 7118 // middle button 7119 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 7120 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 7121 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 7122 case "mmb": case "middle": bnum = 1; break; 7123 // right button 7124 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 7125 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 7126 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 7127 case "rmb": case "right": bnum = 2; break; 7128 // wheel 7129 case "wheelup": updown = UpDown.Up; goto case "wheel"; 7130 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 7131 case "wheelany": updown = UpDown.Any; goto case "wheel"; 7132 case "wheel": bnum = 3; break; 7133 // motion 7134 case "motion": bnum = 7; break; 7135 // unknown 7136 default: return false; 7137 } 7138 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7139 // parse possible "-up" or "-down" 7140 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 7141 wep = 0; 7142 foreach (immutable idx, immutable char ch; str[1..$]) { 7143 if (ch <= ' ' || ch == '-') break; 7144 assert(idx == wep); // for now; trick 7145 if (wep > buf.length) { wep = 0; break; } // too long 7146 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7147 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7148 else { wep = 0; break; } // invalid char 7149 } 7150 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 7151 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 7152 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 7153 // remove parsed part 7154 if (updown != UpDown.None) str = str[wep+1..$]; 7155 } 7156 if (updown == UpDown.None) { 7157 updown = UpDown.Down; 7158 } 7159 wasButtons = wasButtons || (bnum <= 2); 7160 //assert(updown != UpDown.None); 7161 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7162 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 7163 if (lastButt != lastButt.max) { 7164 if ((lastButt&0xff) >= 3) return false; // wheel or motion 7165 if (mods != mods.max) { 7166 uint butbit = 0; 7167 final switch (lastButt&0x03) { 7168 case 0: butbit = ModifierState.leftButtonDown; break; 7169 case 1: butbit = ModifierState.middleButtonDown; break; 7170 case 2: butbit = ModifierState.rightButtonDown; break; 7171 } 7172 if (lastButt&Flag.Down) mods |= butbit; 7173 else if (lastButt&Flag.Up) mods &= ~butbit; 7174 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 7175 } 7176 } 7177 // remember last button 7178 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 7179 } 7180 // no button -- nothing to do 7181 if (lastButt == lastButt.max) return false; 7182 // done parsing, check if something's left 7183 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 7184 // remove action button from mask 7185 if ((lastButt&0xff) < 3) { 7186 final switch (lastButt&0x03) { 7187 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 7188 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 7189 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 7190 } 7191 } 7192 // special case: "Motion" means "ignore buttons" 7193 if ((lastButt&0xff) == 7 && !wasButtons) { 7194 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 7195 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 7196 } 7197 uint kmod = event.modifierState&kmodmask; 7198 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 7199 // check modifier state 7200 if (mods != mods.max) { 7201 if (kmod != mods) return false; 7202 } 7203 // now check type 7204 if ((lastButt&0xff) == 7) { 7205 // motion 7206 if (event.type != MouseEventType.motion) return false; 7207 } else if ((lastButt&0xff) == 3) { 7208 // wheel 7209 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 7210 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 7211 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 7212 return false; 7213 } else { 7214 // buttons 7215 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 7216 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 7217 { 7218 return false; 7219 } 7220 // button number 7221 switch (lastButt&0x03) { 7222 case 0: if (event.button != MouseButton.left) return false; break; 7223 case 1: if (event.button != MouseButton.middle) return false; break; 7224 case 2: if (event.button != MouseButton.right) return false; break; 7225 default: return false; 7226 } 7227 } 7228 return true; 7229 } 7230 } 7231 7232 version(arsd_mevent_strcmp_test) unittest { 7233 MouseEvent event; 7234 event.type = MouseEventType.buttonPressed; 7235 event.button = MouseButton.left; 7236 event.modifierState = ModifierState.ctrl; 7237 assert(event == "C-LMB"); 7238 assert(event != "C-LMBUP"); 7239 assert(event != "C-LMB-UP"); 7240 assert(event != "C-S-LMB"); 7241 assert(event == "*-LMB"); 7242 assert(event != "*-LMB-UP"); 7243 7244 event.type = MouseEventType.buttonReleased; 7245 assert(event != "C-LMB"); 7246 assert(event == "C-LMBUP"); 7247 assert(event == "C-LMB-UP"); 7248 assert(event != "C-S-LMB"); 7249 assert(event != "*-LMB"); 7250 assert(event == "*-LMB-UP"); 7251 7252 event.button = MouseButton.right; 7253 event.modifierState |= ModifierState.shift; 7254 event.type = MouseEventType.buttonPressed; 7255 assert(event != "C-LMB"); 7256 assert(event != "C-LMBUP"); 7257 assert(event != "C-LMB-UP"); 7258 assert(event != "C-S-LMB"); 7259 assert(event != "*-LMB"); 7260 assert(event != "*-LMB-UP"); 7261 7262 assert(event != "C-RMB"); 7263 assert(event != "C-RMBUP"); 7264 assert(event != "C-RMB-UP"); 7265 assert(event == "C-S-RMB"); 7266 assert(event == "*-RMB"); 7267 assert(event != "*-RMB-UP"); 7268 } 7269 7270 /// This gives a few more options to drawing lines and such 7271 struct Pen { 7272 Color color; /// the foreground color 7273 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. 7274 Style style; /// See [Style] 7275 /+ 7276 // From X.h 7277 7278 #define LineSolid 0 7279 #define LineOnOffDash 1 7280 #define LineDoubleDash 2 7281 LineDou- The full path of the line is drawn, but the 7282 bleDash even dashes are filled differently from the 7283 odd dashes (see fill-style) with CapButt 7284 style used where even and odd dashes meet. 7285 7286 7287 7288 /* capStyle */ 7289 7290 #define CapNotLast 0 7291 #define CapButt 1 7292 #define CapRound 2 7293 #define CapProjecting 3 7294 7295 /* joinStyle */ 7296 7297 #define JoinMiter 0 7298 #define JoinRound 1 7299 #define JoinBevel 2 7300 7301 /* fillStyle */ 7302 7303 #define FillSolid 0 7304 #define FillTiled 1 7305 #define FillStippled 2 7306 #define FillOpaqueStippled 3 7307 7308 7309 +/ 7310 /// Style of lines drawn 7311 enum Style { 7312 Solid, /// a solid line 7313 Dashed, /// a dashed line 7314 Dotted, /// a dotted line 7315 } 7316 } 7317 7318 7319 /++ 7320 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 7321 7322 7323 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 7324 7325 $(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.) 7326 7327 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. 7328 7329 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 7330 7331 $(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. 7332 7333 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! 7334 7335 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!) 7336 7337 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 7338 7339 --- 7340 auto image = new Image(256, 256); 7341 scope(exit) destroy(image); 7342 --- 7343 7344 As long as you don't hold on to it outside the scope. 7345 7346 I might change it to be an owned pointer at some point in the future. 7347 7348 ) 7349 7350 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7351 you can also often get a fair amount of speedup by getting the raw data format and 7352 writing some custom code. 7353 7354 FIXME INSERT EXAMPLES HERE 7355 7356 7357 +/ 7358 final class Image { 7359 /// 7360 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7361 this.width = width; 7362 this.height = height; 7363 this.enableAlpha = enableAlpha; 7364 7365 impl.createImage(width, height, forcexshm, enableAlpha); 7366 } 7367 7368 /// 7369 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7370 this(size.width, size.height, forcexshm, enableAlpha); 7371 } 7372 7373 private bool suppressDestruction; 7374 7375 version(X11) 7376 this(XImage* handle) { 7377 this.handle = handle; 7378 this.rawData = cast(ubyte*) handle.data; 7379 this.width = handle.width; 7380 this.height = handle.height; 7381 this.enableAlpha = handle.depth == 32; 7382 suppressDestruction = true; 7383 } 7384 7385 ~this() { 7386 if(suppressDestruction) return; 7387 impl.dispose(); 7388 } 7389 7390 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7391 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7392 pure const @system nothrow { 7393 /* 7394 To use these to draw a blue rectangle with size WxH at position X,Y... 7395 7396 // make certain that it will fit before we proceed 7397 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! 7398 7399 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7400 // (though calculating them isn't really that expensive). 7401 auto nextLineAdjustment = img.adjustmentForNextLine(); 7402 auto offR = img.redByteOffset(); 7403 auto offB = img.blueByteOffset(); 7404 auto offG = img.greenByteOffset(); 7405 auto bpp = img.bytesPerPixel(); 7406 7407 auto data = img.getDataPointer(); 7408 7409 // figure out the starting byte offset 7410 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7411 7412 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7413 7414 // and now our drawing loop for the rectangle 7415 foreach(y; 0 .. H) { 7416 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7417 foreach(x; 0 .. W) { 7418 // write our color 7419 data[offR] = 0; 7420 data[offG] = 0; 7421 data[offB] = 255; 7422 7423 data += bpp; // moving to the next pixel is just an addition... 7424 } 7425 startOfLine += nextLineAdjustment; 7426 } 7427 7428 7429 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7430 7431 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7432 can be made into a bitmask or something so we can write them as *uint... 7433 */ 7434 7435 /// 7436 int offsetForTopLeftPixel() { 7437 version(X11) { 7438 return 0; 7439 } else version(Windows) { 7440 if(enableAlpha) { 7441 return (width * 4) * (height - 1); 7442 } else { 7443 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7444 } 7445 } else version(OSXCocoa) { 7446 return 0 ; //throw new NotYetImplementedException(); 7447 } else static assert(0, "fill in this info for other OSes"); 7448 } 7449 7450 /// 7451 int offsetForPixel(int x, int y) { 7452 version(X11) { 7453 auto offset = (y * width + x) * 4; 7454 return offset; 7455 } else version(Windows) { 7456 if(enableAlpha) { 7457 auto itemsPerLine = width * 4; 7458 // remember, bmps are upside down 7459 auto offset = itemsPerLine * (height - y - 1) + x * 4; 7460 return offset; 7461 } else { 7462 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 7463 // remember, bmps are upside down 7464 auto offset = itemsPerLine * (height - y - 1) + x * 3; 7465 return offset; 7466 } 7467 } else version(OSXCocoa) { 7468 return 0 ; //throw new NotYetImplementedException(); 7469 } else static assert(0, "fill in this info for other OSes"); 7470 } 7471 7472 /// 7473 int adjustmentForNextLine() { 7474 version(X11) { 7475 return width * 4; 7476 } else version(Windows) { 7477 // windows bmps are upside down, so the adjustment is actually negative 7478 if(enableAlpha) 7479 return - (cast(int) width * 4); 7480 else 7481 return -((cast(int) width * 3 + 3) / 4) * 4; 7482 } else version(OSXCocoa) { 7483 return 0 ; //throw new NotYetImplementedException(); 7484 } else static assert(0, "fill in this info for other OSes"); 7485 } 7486 7487 /// once you have the position of a pixel, use these to get to the proper color 7488 int redByteOffset() { 7489 version(X11) { 7490 return 2; 7491 } else version(Windows) { 7492 return 2; 7493 } else version(OSXCocoa) { 7494 return 0 ; //throw new NotYetImplementedException(); 7495 } else static assert(0, "fill in this info for other OSes"); 7496 } 7497 7498 /// 7499 int greenByteOffset() { 7500 version(X11) { 7501 return 1; 7502 } else version(Windows) { 7503 return 1; 7504 } else version(OSXCocoa) { 7505 return 0 ; //throw new NotYetImplementedException(); 7506 } else static assert(0, "fill in this info for other OSes"); 7507 } 7508 7509 /// 7510 int blueByteOffset() { 7511 version(X11) { 7512 return 0; 7513 } else version(Windows) { 7514 return 0; 7515 } else version(OSXCocoa) { 7516 return 0 ; //throw new NotYetImplementedException(); 7517 } else static assert(0, "fill in this info for other OSes"); 7518 } 7519 7520 /// Only valid if [enableAlpha] is true 7521 int alphaByteOffset() { 7522 version(X11) { 7523 return 3; 7524 } else version(Windows) { 7525 return 3; 7526 } else version(OSXCocoa) { 7527 return 3; //throw new NotYetImplementedException(); 7528 } else static assert(0, "fill in this info for other OSes"); 7529 } 7530 } 7531 7532 /// 7533 final void putPixel(int x, int y, Color c) { 7534 if(x < 0 || x >= width) 7535 return; 7536 if(y < 0 || y >= height) 7537 return; 7538 7539 impl.setPixel(x, y, c); 7540 } 7541 7542 /// 7543 final Color getPixel(int x, int y) { 7544 if(x < 0 || x >= width) 7545 return Color.transparent; 7546 if(y < 0 || y >= height) 7547 return Color.transparent; 7548 7549 version(OSXCocoa) throw new NotYetImplementedException(); else 7550 return impl.getPixel(x, y); 7551 } 7552 7553 /// 7554 final void opIndexAssign(Color c, int x, int y) { 7555 putPixel(x, y, c); 7556 } 7557 7558 /// 7559 TrueColorImage toTrueColorImage() { 7560 auto tci = new TrueColorImage(width, height); 7561 convertToRgbaBytes(tci.imageData.bytes); 7562 return tci; 7563 } 7564 7565 /// 7566 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) { 7567 auto tci = i.getAsTrueColorImage(); 7568 auto img = new Image(tci.width, tci.height, false, enableAlpha); 7569 img.setRgbaBytes(tci.imageData.bytes); 7570 return img; 7571 } 7572 7573 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 7574 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 7575 /// if you pass null, it will allocate a new one. 7576 ubyte[] getRgbaBytes(ubyte[] where = null) { 7577 if(where is null) 7578 where = new ubyte[this.width*this.height*4]; 7579 convertToRgbaBytes(where); 7580 return where; 7581 } 7582 7583 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 7584 void setRgbaBytes(in ubyte[] from ) { 7585 assert(from.length == this.width * this.height * 4); 7586 setFromRgbaBytes(from); 7587 } 7588 7589 // FIXME: make properly cross platform by getting rgba right 7590 7591 /// warning: this is not portable across platforms because the data format can change 7592 ubyte* getDataPointer() { 7593 return impl.rawData; 7594 } 7595 7596 /// for use with getDataPointer 7597 final int bytesPerLine() const pure @safe nothrow { 7598 version(Windows) 7599 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 7600 else version(X11) 7601 return 4 * width; 7602 else version(OSXCocoa) 7603 return 4 * width; 7604 else static assert(0); 7605 } 7606 7607 /// for use with getDataPointer 7608 final int bytesPerPixel() const pure @safe nothrow { 7609 version(Windows) 7610 return enableAlpha ? 4 : 3; 7611 else version(X11) 7612 return 4; 7613 else version(OSXCocoa) 7614 return 4; 7615 else static assert(0); 7616 } 7617 7618 /// 7619 immutable int width; 7620 7621 /// 7622 immutable int height; 7623 7624 /// 7625 immutable bool enableAlpha; 7626 //private: 7627 mixin NativeImageImplementation!() impl; 7628 } 7629 7630 /// A convenience function to pop up a window displaying the image. 7631 /// If you pass a win, it will draw the image in it. Otherwise, it will 7632 /// create a window with the size of the image and run its event loop, closing 7633 /// when a key is pressed. 7634 void displayImage(Image image, SimpleWindow win = null) { 7635 if(win is null) { 7636 win = new SimpleWindow(image); 7637 { 7638 auto p = win.draw; 7639 p.drawImage(Point(0, 0), image); 7640 } 7641 win.eventLoop(0, 7642 (KeyEvent ev) { 7643 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 7644 } ); 7645 } else { 7646 win.image = image; 7647 } 7648 } 7649 7650 enum FontWeight : int { 7651 dontcare = 0, 7652 thin = 100, 7653 extralight = 200, 7654 light = 300, 7655 regular = 400, 7656 medium = 500, 7657 semibold = 600, 7658 bold = 700, 7659 extrabold = 800, 7660 heavy = 900 7661 } 7662 7663 // FIXME: i need a font cache and it needs to handle disconnects. 7664 7665 /++ 7666 Represents a font loaded off the operating system or the X server. 7667 7668 7669 While the api here is unified cross platform, the fonts are not necessarily 7670 available, even across machines of the same platform, so be sure to always check 7671 for null (using [isNull]) and have a fallback plan. 7672 7673 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 7674 7675 Worst case, a null font will automatically fall back to the default font loaded 7676 for your system. 7677 +/ 7678 class OperatingSystemFont { 7679 // FIXME: when the X Connection is lost, these need to be invalidated! 7680 // that means I need to store the original stuff again to reconstruct it too. 7681 7682 version(X11) { 7683 XFontStruct* font; 7684 XFontSet fontset; 7685 7686 version(with_xft) { 7687 XftFont* xftFont; 7688 bool isXft; 7689 } 7690 } else version(Windows) { 7691 HFONT font; 7692 int width_; 7693 int height_; 7694 } else version(OSXCocoa) { 7695 // FIXME 7696 } else static assert(0); 7697 7698 /++ 7699 Constructs the class and immediately calls [load]. 7700 +/ 7701 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 7702 load(name, size, weight, italic); 7703 } 7704 7705 /++ 7706 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 7707 7708 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 7709 7710 History: 7711 Added January 24, 2021. 7712 +/ 7713 this() { 7714 // this space intentionally left blank 7715 } 7716 7717 /++ 7718 Loads specifically with the Xft library - a freetype font from a fontconfig string. 7719 7720 History: 7721 Added November 13, 2020. 7722 +/ 7723 version(with_xft) 7724 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 7725 unload(); 7726 7727 if(!XftLibrary.attempted) { 7728 XftLibrary.loadDynamicLibrary(); 7729 } 7730 7731 if(!XftLibrary.loadSuccessful) 7732 return false; 7733 7734 auto display = XDisplayConnection.get; 7735 7736 char[256] nameBuffer = void; 7737 int nbp = 0; 7738 7739 void add(in char[] a) { 7740 nameBuffer[nbp .. nbp + a.length] = a[]; 7741 nbp += a.length; 7742 } 7743 add(name); 7744 7745 if(size) { 7746 add(":size="); 7747 add(toInternal!string(size)); 7748 } 7749 if(weight != FontWeight.dontcare) { 7750 add(":weight="); 7751 add(weightToString(weight)); 7752 } 7753 if(italic) 7754 add(":slant=100"); 7755 7756 nameBuffer[nbp] = 0; 7757 7758 this.xftFont = XftFontOpenName( 7759 display, 7760 DefaultScreen(display), 7761 nameBuffer.ptr 7762 ); 7763 7764 this.isXft = true; 7765 7766 if(xftFont !is null) { 7767 isMonospace_ = stringWidth("x") == stringWidth("M"); 7768 ascent_ = xftFont.ascent; 7769 descent_ = xftFont.descent; 7770 } 7771 7772 return !isNull(); 7773 } 7774 7775 /++ 7776 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 7777 7778 7779 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. 7780 7781 If `pattern` is null, it returns all available font families. 7782 7783 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. 7784 7785 The format of the pattern is platform-specific. 7786 7787 History: 7788 Added May 1, 2021 (dub v9.5) 7789 +/ 7790 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 7791 version(Windows) { 7792 auto hdc = GetDC(null); 7793 scope(exit) ReleaseDC(null, hdc); 7794 LOGFONT logfont; 7795 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 7796 auto localHandler = *(cast(typeof(handler)*) p); 7797 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 7798 } 7799 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 7800 } else version(X11) { 7801 //import core.stdc.stdio; 7802 bool done = false; 7803 version(with_xft) { 7804 if(!XftLibrary.attempted) { 7805 XftLibrary.loadDynamicLibrary(); 7806 } 7807 7808 if(!XftLibrary.loadSuccessful) 7809 goto skipXft; 7810 7811 if(!FontConfigLibrary.attempted) 7812 FontConfigLibrary.loadDynamicLibrary(); 7813 if(!FontConfigLibrary.loadSuccessful) 7814 goto skipXft; 7815 7816 { 7817 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 7818 if(got is null) 7819 goto skipXft; 7820 scope(exit) FcFontSetDestroy(got); 7821 7822 auto fontPatterns = got.fonts[0 .. got.nfont]; 7823 foreach(candidate; fontPatterns) { 7824 char* where, whereStyle; 7825 7826 char* pmg = FcNameUnparse(candidate); 7827 7828 //FcPatternGetString(candidate, "family", 0, &where); 7829 //FcPatternGetString(candidate, "style", 0, &whereStyle); 7830 //if(where && whereStyle) { 7831 if(pmg) { 7832 if(!handler(pmg.sliceCString)) 7833 return; 7834 //printf("%s || %s %s\n", pmg, where, whereStyle); 7835 } 7836 } 7837 } 7838 } 7839 7840 skipXft: 7841 7842 if(pattern is null) 7843 pattern = "*"; 7844 7845 int count; 7846 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 7847 scope(exit) XFreeFontNames(coreFontsRaw); 7848 7849 auto coreFonts = coreFontsRaw[0 .. count]; 7850 7851 foreach(font; coreFonts) { 7852 char[128] tmp; 7853 tmp[0 ..5] = "core:"; 7854 auto cf = font.sliceCString; 7855 if(5 + cf.length > tmp.length) 7856 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 7857 tmp[5 .. 5 + cf.length] = cf; 7858 if(!handler(tmp[0 .. 5 + cf.length])) 7859 return; 7860 } 7861 } 7862 } 7863 7864 /++ 7865 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 7866 to look up fonts that you then pass to things like [arsd.game.OpenGlLimitedFont] or [arsd.nanovega]. 7867 7868 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 7869 underlying system doesn't support returning the raw bytes. 7870 7871 History: 7872 Added September 10, 2021 (dub v10.3) 7873 +/ 7874 ubyte[] getTtfBytes() { 7875 if(isNull) 7876 return null; 7877 7878 version(Windows) { 7879 auto dc = GetDC(null); 7880 auto orig = SelectObject(dc, font); 7881 7882 scope(exit) { 7883 SelectObject(dc, orig); 7884 ReleaseDC(null, dc); 7885 } 7886 7887 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 7888 if(res == GDI_ERROR) 7889 return null; 7890 7891 ubyte[] buffer = new ubyte[](res); 7892 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 7893 if(res == GDI_ERROR) 7894 return null; // wtf really tbh 7895 7896 return buffer; 7897 } else version(with_xft) { 7898 if(isXft && xftFont) { 7899 if(!FontConfigLibrary.attempted) 7900 FontConfigLibrary.loadDynamicLibrary(); 7901 if(!FontConfigLibrary.loadSuccessful) 7902 return null; 7903 7904 char* file; 7905 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 7906 if (file !is null && file[0]) { 7907 import core.stdc.stdio; 7908 auto fp = fopen(file, "rb"); 7909 if(fp is null) 7910 return null; 7911 scope(exit) 7912 fclose(fp); 7913 fseek(fp, 0, SEEK_END); 7914 ubyte[] buffer = new ubyte[](ftell(fp)); 7915 fseek(fp, 0, SEEK_SET); 7916 7917 auto got = fread(buffer.ptr, 1, buffer.length, fp); 7918 if(got != buffer.length) 7919 return null; 7920 7921 return buffer; 7922 } 7923 } 7924 } 7925 return null; 7926 } 7927 } 7928 7929 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 7930 7931 private string weightToString(FontWeight weight) { 7932 with(FontWeight) 7933 final switch(weight) { 7934 case dontcare: return "*"; 7935 case thin: return "extralight"; 7936 case extralight: return "extralight"; 7937 case light: return "light"; 7938 case regular: return "regular"; 7939 case medium: return "medium"; 7940 case semibold: return "demibold"; 7941 case bold: return "bold"; 7942 case extrabold: return "demibold"; 7943 case heavy: return "black"; 7944 } 7945 } 7946 7947 /++ 7948 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 7949 7950 History: 7951 Added November 13, 2020. Before then, this code was integrated in the [load] function. 7952 +/ 7953 version(X11) 7954 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 7955 unload(); 7956 7957 string xfontstr; 7958 7959 if(name.length > 3 && name[0 .. 3] == "-*-") { 7960 // this is kinda a disgusting hack but if the user sends an exact 7961 // string I'd like to honor it... 7962 xfontstr = name; 7963 } else { 7964 string weightstr = weightToString(weight); 7965 string sizestr; 7966 if(size == 0) 7967 sizestr = "*"; 7968 else 7969 sizestr = toInternal!string(size); 7970 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 7971 } 7972 7973 //import std.stdio; writeln(xfontstr); 7974 7975 auto display = XDisplayConnection.get; 7976 7977 font = XLoadQueryFont(display, xfontstr.ptr); 7978 if(font is null) 7979 return false; 7980 7981 char** lol; 7982 int lol2; 7983 char* lol3; 7984 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 7985 7986 prepareFontInfo(); 7987 7988 return !isNull(); 7989 } 7990 7991 version(X11) 7992 private void prepareFontInfo() { 7993 if(font !is null) { 7994 isMonospace_ = stringWidth("l") == stringWidth("M"); 7995 ascent_ = font.max_bounds.ascent; 7996 descent_ = font.max_bounds.descent; 7997 } 7998 } 7999 8000 /++ 8001 Loads a Windows font. You probably want to use [load] instead to be more generic. 8002 8003 History: 8004 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8005 +/ 8006 version(Windows) 8007 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8008 unload(); 8009 8010 WCharzBuffer buffer = WCharzBuffer(name); 8011 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8012 8013 prepareFontInfo(hdc); 8014 8015 return !isNull(); 8016 } 8017 8018 version(Windows) 8019 void prepareFontInfo(HDC hdc = null) { 8020 if(font is null) 8021 return; 8022 8023 TEXTMETRIC tm; 8024 auto dc = hdc ? hdc : GetDC(null); 8025 auto orig = SelectObject(dc, font); 8026 GetTextMetrics(dc, &tm); 8027 SelectObject(dc, orig); 8028 if(hdc is null) 8029 ReleaseDC(null, dc); 8030 8031 width_ = tm.tmAveCharWidth; 8032 height_ = tm.tmHeight; 8033 ascent_ = tm.tmAscent; 8034 descent_ = tm.tmDescent; 8035 // 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. 8036 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 8037 } 8038 8039 8040 /++ 8041 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 8042 8043 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 8044 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 8045 8046 On Windows, it forwards directly to [loadWin32]. 8047 8048 Params: 8049 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 8050 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. 8051 weight = approximate boldness, results may vary. 8052 italic = try to get a slanted version of the given font. 8053 8054 History: 8055 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. 8056 +/ 8057 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8058 version(X11) { 8059 version(with_xft) { 8060 if(name.length > 5 && name[0 .. 5] == "core:") { 8061 goto core; 8062 } 8063 8064 if(loadXft(name, size, weight, italic)) 8065 return true; 8066 // if xft fails, fallback to core to avoid breaking 8067 // code that already depended on this. 8068 } 8069 8070 core: 8071 8072 if(name.length > 5 && name[0 .. 5] == "core:") { 8073 name = name[5 .. $]; 8074 } 8075 8076 return loadCoreX(name, size, weight, italic); 8077 } else version(Windows) { 8078 return loadWin32(name, size, weight, italic); 8079 } else version(OSXCocoa) { 8080 // FIXME 8081 return false; 8082 } else static assert(0); 8083 } 8084 8085 /// 8086 void unload() { 8087 if(isNull()) 8088 return; 8089 8090 version(X11) { 8091 auto display = XDisplayConnection.display; 8092 8093 if(display is null) 8094 return; 8095 8096 version(with_xft) { 8097 if(isXft) { 8098 if(xftFont) 8099 XftFontClose(display, xftFont); 8100 isXft = false; 8101 xftFont = null; 8102 return; 8103 } 8104 } 8105 8106 if(font && font !is ScreenPainterImplementation.defaultfont) 8107 XFreeFont(display, font); 8108 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 8109 XFreeFontSet(display, fontset); 8110 8111 font = null; 8112 fontset = null; 8113 } else version(Windows) { 8114 DeleteObject(font); 8115 font = null; 8116 } else version(OSXCocoa) { 8117 // FIXME 8118 } else static assert(0); 8119 } 8120 8121 private bool isMonospace_; 8122 8123 /++ 8124 History: 8125 Added January 16, 2021 8126 +/ 8127 bool isMonospace() { 8128 return isMonospace_; 8129 } 8130 8131 /++ 8132 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 8133 8134 History: 8135 Added March 26, 2020 8136 Documented January 16, 2021 8137 +/ 8138 int averageWidth() { 8139 version(X11) { 8140 return stringWidth("x"); 8141 } else version(Windows) 8142 return width_; 8143 else assert(0); 8144 } 8145 8146 /++ 8147 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 8148 8149 History: 8150 Added January 16, 2021 8151 +/ 8152 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 8153 // FIXME: what about tab? 8154 if(isNull) 8155 return 0; 8156 8157 version(X11) { 8158 version(with_xft) 8159 if(isXft && xftFont !is null) { 8160 //return xftFont.max_advance_width; 8161 XGlyphInfo extents; 8162 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 8163 //import std.stdio; writeln(extents); 8164 return extents.xOff; 8165 } 8166 if(font is null) 8167 return 0; 8168 else if(fontset) { 8169 XRectangle rect; 8170 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 8171 8172 return rect.width; 8173 } else { 8174 return XTextWidth(font, s.ptr, cast(int) s.length); 8175 } 8176 } else version(Windows) { 8177 WCharzBuffer buffer = WCharzBuffer(s); 8178 8179 return stringWidth(buffer.slice, window); 8180 } 8181 else assert(0); 8182 } 8183 8184 version(Windows) 8185 /// ditto 8186 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 8187 if(isNull) 8188 return 0; 8189 version(Windows) { 8190 SIZE size; 8191 8192 prepareContext(window); 8193 scope(exit) releaseContext(); 8194 8195 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 8196 8197 return size.cx; 8198 } else { 8199 // std.conv can do this easily but it is slow to import and i don't think it is worth it 8200 static assert(0, "not implemented yet"); 8201 //return stringWidth(s, window); 8202 } 8203 } 8204 8205 private { 8206 int prepRefcount; 8207 8208 version(Windows) { 8209 HDC dc; 8210 HANDLE orig; 8211 HWND hwnd; 8212 } 8213 } 8214 /++ 8215 [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. 8216 8217 History: 8218 Added January 23, 2021 8219 +/ 8220 void prepareContext(SimpleWindow window = null) { 8221 prepRefcount++; 8222 if(prepRefcount == 1) { 8223 version(Windows) { 8224 hwnd = window is null ? null : window.impl.hwnd; 8225 dc = GetDC(hwnd); 8226 orig = SelectObject(dc, font); 8227 } 8228 } 8229 } 8230 /// ditto 8231 void releaseContext() { 8232 prepRefcount--; 8233 if(prepRefcount == 0) { 8234 version(Windows) { 8235 SelectObject(dc, orig); 8236 ReleaseDC(hwnd, dc); 8237 hwnd = null; 8238 dc = null; 8239 orig = null; 8240 } 8241 } 8242 } 8243 8244 /+ 8245 FIXME: I think I need advance and kerning pair 8246 8247 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 8248 +/ 8249 8250 /++ 8251 Returns the height of the font. 8252 8253 History: 8254 Added March 26, 2020 8255 Documented January 16, 2021 8256 +/ 8257 int height() { 8258 version(X11) { 8259 version(with_xft) 8260 if(isXft && xftFont !is null) { 8261 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 8262 } 8263 if(font is null) 8264 return 0; 8265 return font.max_bounds.ascent + font.max_bounds.descent; 8266 } else version(Windows) 8267 return height_; 8268 else assert(0); 8269 } 8270 8271 private int ascent_; 8272 private int descent_; 8273 8274 /++ 8275 Max ascent above the baseline. 8276 8277 History: 8278 Added January 22, 2021 8279 +/ 8280 int ascent() { 8281 return ascent_; 8282 } 8283 8284 /++ 8285 Max descent below the baseline. 8286 8287 History: 8288 Added January 22, 2021 8289 +/ 8290 int descent() { 8291 return descent_; 8292 } 8293 8294 /++ 8295 Loads the default font used by [ScreenPainter] if none others are loaded. 8296 8297 Returns: 8298 This method mutates the `this` object, but then returns `this` for 8299 easy chaining like: 8300 8301 --- 8302 auto font = foo.isNull ? foo : foo.loadDefault 8303 --- 8304 8305 History: 8306 Added previously, but left unimplemented until January 24, 2021. 8307 +/ 8308 OperatingSystemFont loadDefault() { 8309 unload(); 8310 8311 version(X11) { 8312 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 8313 // but meh since sdpy does its own thing, this should be ok too 8314 8315 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8316 this.font = ScreenPainterImplementation.defaultfont; 8317 this.fontset = ScreenPainterImplementation.defaultfontset; 8318 8319 prepareFontInfo(); 8320 } else version(Windows) { 8321 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8322 this.font = ScreenPainterImplementation.defaultGuiFont; 8323 8324 prepareFontInfo(); 8325 } else throw new NotYetImplementedException(); 8326 8327 return this; 8328 } 8329 8330 /// 8331 bool isNull() { 8332 version(OSXCocoa) throw new NotYetImplementedException(); else { 8333 version(with_xft) 8334 if(isXft) 8335 return xftFont is null; 8336 return font is null; 8337 } 8338 } 8339 8340 /* Metrics */ 8341 /+ 8342 GetABCWidth 8343 GetKerningPairs 8344 8345 if I do it right, I can size it all here, and match 8346 what happens when I draw the full string with the OS functions. 8347 8348 subclasses might do the same thing while getting the glyphs on images 8349 struct GlyphInfo { 8350 int glyph; 8351 8352 size_t stringIdxStart; 8353 size_t stringIdxEnd; 8354 8355 Rectangle boundingBox; 8356 } 8357 GlyphInfo[] getCharBoxes() { 8358 // XftTextExtentsUtf8 8359 return null; 8360 8361 } 8362 +/ 8363 8364 ~this() { 8365 unload(); 8366 } 8367 } 8368 8369 version(Windows) 8370 private string sliceCString(const(wchar)[] w) { 8371 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 8372 } 8373 8374 private inout(char)[] sliceCString(inout(char)* s) { 8375 import core.stdc.string; 8376 auto len = strlen(s); 8377 return s[0 .. len]; 8378 } 8379 8380 /** 8381 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 8382 than constructing it directly. Then, it is reference counted so you can pass it 8383 at around and when the last ref goes out of scope, the buffered drawing activities 8384 are all carried out. 8385 8386 8387 Most functions use the outlineColor instead of taking a color themselves. 8388 ScreenPainter is reference counted and draws its buffer to the screen when its 8389 final reference goes out of scope. 8390 */ 8391 struct ScreenPainter { 8392 CapableOfBeingDrawnUpon window; 8393 this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle) { 8394 this.window = window; 8395 if(window.closed) 8396 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 8397 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 8398 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 8399 if(window.activeScreenPainter !is null) { 8400 impl = window.activeScreenPainter; 8401 if(impl.referenceCount == 0) { 8402 impl.window = window; 8403 impl.create(handle); 8404 } 8405 impl.referenceCount++; 8406 // writeln("refcount ++ ", impl.referenceCount); 8407 } else { 8408 impl = new ScreenPainterImplementation; 8409 impl.window = window; 8410 impl.create(handle); 8411 impl.referenceCount = 1; 8412 window.activeScreenPainter = impl; 8413 //import std.stdio; writeln("constructed"); 8414 } 8415 8416 copyActiveOriginals(); 8417 } 8418 8419 private Pen originalPen; 8420 private Color originalFillColor; 8421 private arsd.color.Rectangle originalClipRectangle; 8422 void copyActiveOriginals() { 8423 if(impl is null) return; 8424 originalPen = impl._activePen; 8425 originalFillColor = impl._fillColor; 8426 originalClipRectangle = impl._clipRectangle; 8427 } 8428 8429 ~this() { 8430 if(impl is null) return; 8431 impl.referenceCount--; 8432 //writeln("refcount -- ", impl.referenceCount); 8433 if(impl.referenceCount == 0) { 8434 //import std.stdio; writeln("destructed"); 8435 impl.dispose(); 8436 *window.activeScreenPainter = ScreenPainterImplementation.init; 8437 //import std.stdio; writeln("paint finished"); 8438 } else { 8439 // there is still an active reference, reset stuff so the 8440 // next user doesn't get weirdness via the reference 8441 this.rasterOp = RasterOp.normal; 8442 pen = originalPen; 8443 fillColor = originalFillColor; 8444 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 8445 } 8446 } 8447 8448 this(this) { 8449 if(impl is null) return; 8450 impl.referenceCount++; 8451 //writeln("refcount ++ ", impl.referenceCount); 8452 8453 copyActiveOriginals(); 8454 } 8455 8456 private int _originX; 8457 private int _originY; 8458 @property int originX() { return _originX; } 8459 @property int originY() { return _originY; } 8460 @property int originX(int a) { 8461 _originX = a; 8462 return _originX; 8463 } 8464 @property int originY(int a) { 8465 _originY = a; 8466 return _originY; 8467 } 8468 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 8469 private void transform(ref Point p) { 8470 if(impl is null) return; 8471 p.x += _originX; 8472 p.y += _originY; 8473 } 8474 8475 // this needs to be checked BEFORE the originX/Y transformation 8476 private bool isClipped(Point p) { 8477 return !currentClipRectangle.contains(p); 8478 } 8479 private bool isClipped(Point p, int width, int height) { 8480 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 8481 } 8482 private bool isClipped(Point p, Size s) { 8483 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 8484 } 8485 private bool isClipped(Point p, Point p2) { 8486 // need to ensure the end points are actually included inside, so the +1 does that 8487 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 8488 } 8489 8490 8491 /++ 8492 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 8493 8494 Returns: 8495 The old clip rectangle. 8496 8497 History: 8498 Return value was `void` prior to May 10, 2021. 8499 8500 +/ 8501 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 8502 if(impl is null) return currentClipRectangle; 8503 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 8504 return currentClipRectangle; // no need to do anything 8505 auto old = currentClipRectangle; 8506 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 8507 transform(pt); 8508 8509 impl.setClipRectangle(pt.x, pt.y, width, height); 8510 8511 return old; 8512 } 8513 8514 /// ditto 8515 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 8516 if(impl is null) return currentClipRectangle; 8517 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 8518 } 8519 8520 /// 8521 void setFont(OperatingSystemFont font) { 8522 if(impl is null) return; 8523 impl.setFont(font); 8524 } 8525 8526 /// 8527 int fontHeight() { 8528 if(impl is null) return 0; 8529 return impl.fontHeight(); 8530 } 8531 8532 private Pen activePen; 8533 8534 /// 8535 @property void pen(Pen p) { 8536 if(impl is null) return; 8537 activePen = p; 8538 impl.pen(p); 8539 } 8540 8541 /// 8542 @scriptable 8543 @property void outlineColor(Color c) { 8544 if(impl is null) return; 8545 if(activePen.color == c) 8546 return; 8547 activePen.color = c; 8548 impl.pen(activePen); 8549 } 8550 8551 /// 8552 @scriptable 8553 @property void fillColor(Color c) { 8554 if(impl is null) return; 8555 impl.fillColor(c); 8556 } 8557 8558 /// 8559 @property void rasterOp(RasterOp op) { 8560 if(impl is null) return; 8561 impl.rasterOp(op); 8562 } 8563 8564 8565 void updateDisplay() { 8566 // FIXME this should do what the dtor does 8567 } 8568 8569 /// 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) 8570 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 8571 if(impl is null) return; 8572 if(isClipped(upperLeft, width, height)) return; 8573 transform(upperLeft); 8574 version(Windows) { 8575 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 8576 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 8577 RECT clip = scroll; 8578 RECT uncovered; 8579 HRGN hrgn; 8580 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 8581 throw new Exception("ScrollDC"); 8582 8583 } else version(X11) { 8584 // FIXME: clip stuff outside this rectangle 8585 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 8586 } else version(OSXCocoa) { 8587 throw new NotYetImplementedException(); 8588 } else static assert(0); 8589 } 8590 8591 /// 8592 void clear(Color color = Color.white()) { 8593 if(impl is null) return; 8594 fillColor = color; 8595 outlineColor = color; 8596 drawRectangle(Point(0, 0), window.width, window.height); 8597 } 8598 8599 /++ 8600 Draws a pixmap (represented by the [Sprite] class) on the drawable. 8601 8602 Params: 8603 upperLeft = point on the window where the upper left corner of the image will be drawn 8604 imageUpperLeft = point on the image to start the slice to draw 8605 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. 8606 History: 8607 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 8608 +/ 8609 version(OSXCocoa) {} else // NotYetImplementedException 8610 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 8611 if(impl is null) return; 8612 if(isClipped(upperLeft, s.width, s.height)) return; 8613 transform(upperLeft); 8614 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 8615 } 8616 8617 /// 8618 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 8619 if(impl is null) return; 8620 //if(isClipped(upperLeft, w, h)) return; // FIXME 8621 transform(upperLeft); 8622 if(w == 0 || w > i.width) 8623 w = i.width; 8624 if(h == 0 || h > i.height) 8625 h = i.height; 8626 if(upperLeftOfImage.x < 0) 8627 upperLeftOfImage.x = 0; 8628 if(upperLeftOfImage.y < 0) 8629 upperLeftOfImage.y = 0; 8630 8631 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 8632 } 8633 8634 /// 8635 Size textSize(in char[] text) { 8636 if(impl is null) return Size(0, 0); 8637 return impl.textSize(text); 8638 } 8639 8640 /++ 8641 Draws a string in the window with the set font (see [setFont] to change it). 8642 8643 Params: 8644 upperLeft = the upper left point of the bounding box of the text 8645 text = the string to draw 8646 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 8647 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 8648 +/ 8649 @scriptable 8650 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 8651 if(impl is null) return; 8652 if(lowerRight.x != 0 || lowerRight.y != 0) { 8653 if(isClipped(upperLeft, lowerRight)) return; 8654 transform(lowerRight); 8655 } else { 8656 if(isClipped(upperLeft, textSize(text))) return; 8657 } 8658 transform(upperLeft); 8659 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 8660 } 8661 8662 /++ 8663 Draws text using a custom font. 8664 8665 This is still MAJOR work in progress. 8666 8667 Creating a [DrawableFont] can be tricky and require additional dependencies. 8668 +/ 8669 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 8670 if(impl is null) return; 8671 if(isClipped(upperLeft, Point(int.max, int.max))) return; 8672 transform(upperLeft); 8673 font.drawString(this, upperLeft, text); 8674 } 8675 8676 version(Windows) 8677 void drawText(Point upperLeft, scope const(wchar)[] text) { 8678 if(impl is null) return; 8679 if(isClipped(upperLeft, Point(int.max, int.max))) return; 8680 transform(upperLeft); 8681 8682 if(text.length && text[$-1] == '\n') 8683 text = text[0 .. $-1]; // tailing newlines are weird on windows... 8684 8685 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 8686 } 8687 8688 static struct TextDrawingContext { 8689 Point boundingBoxUpperLeft; 8690 Point boundingBoxLowerRight; 8691 8692 Point currentLocation; 8693 8694 Point lastDrewUpperLeft; 8695 Point lastDrewLowerRight; 8696 8697 // how do i do right aligned rich text? 8698 // i kinda want to do a pre-made drawing then right align 8699 // draw the whole block. 8700 // 8701 // That's exactly the diff: inline vs block stuff. 8702 8703 // I need to get coordinates of an inline section out too, 8704 // not just a bounding box, but a series of bounding boxes 8705 // should be ok. Consider what's needed to detect a click 8706 // on a link in the middle of a paragraph breaking a line. 8707 // 8708 // Generally, we should be able to get the rectangles of 8709 // any portion we draw. 8710 // 8711 // It also needs to tell what text is left if it overflows 8712 // out of the box, so we can do stuff like float images around 8713 // it. It should not attempt to draw a letter that would be 8714 // clipped. 8715 // 8716 // I might also turn off word wrap stuff. 8717 } 8718 8719 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 8720 if(impl is null) return; 8721 // FIXME 8722 } 8723 8724 /// Drawing an individual pixel is slow. Avoid it if possible. 8725 void drawPixel(Point where) { 8726 if(impl is null) return; 8727 if(isClipped(where)) return; 8728 transform(where); 8729 impl.drawPixel(where.x, where.y); 8730 } 8731 8732 8733 /// Draws a pen using the current pen / outlineColor 8734 @scriptable 8735 void drawLine(Point starting, Point ending) { 8736 if(impl is null) return; 8737 if(isClipped(starting, ending)) return; 8738 transform(starting); 8739 transform(ending); 8740 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 8741 } 8742 8743 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 8744 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 8745 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 8746 @scriptable 8747 void drawRectangle(Point upperLeft, int width, int height) { 8748 if(impl is null) return; 8749 if(isClipped(upperLeft, width, height)) return; 8750 transform(upperLeft); 8751 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 8752 } 8753 8754 /// ditto 8755 void drawRectangle(Point upperLeft, Size size) { 8756 if(impl is null) return; 8757 if(isClipped(upperLeft, size.width, size.height)) return; 8758 transform(upperLeft); 8759 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 8760 } 8761 8762 /// ditto 8763 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 8764 if(impl is null) return; 8765 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 8766 transform(upperLeft); 8767 transform(lowerRightInclusive); 8768 impl.drawRectangle(upperLeft.x, upperLeft.y, 8769 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 8770 } 8771 8772 // overload added on May 12, 2021 8773 /// ditto 8774 void drawRectangle(Rectangle rect) { 8775 drawRectangle(rect.upperLeft, rect.size); 8776 } 8777 8778 /// Arguments are the points of the bounding rectangle 8779 void drawEllipse(Point upperLeft, Point lowerRight) { 8780 if(impl is null) return; 8781 if(isClipped(upperLeft, lowerRight)) return; 8782 transform(upperLeft); 8783 transform(lowerRight); 8784 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 8785 } 8786 8787 /++ 8788 start and finish are units of degrees * 64 8789 8790 History: 8791 The Windows implementation didn't match the Linux implementation until September 24, 2021. 8792 8793 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 8794 +/ 8795 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 8796 if(impl is null) return; 8797 // FIXME: not actually implemented 8798 if(isClipped(upperLeft, width, height)) return; 8799 transform(upperLeft); 8800 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 8801 } 8802 8803 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 8804 void drawCircle(Point upperLeft, int diameter) { 8805 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 8806 } 8807 8808 /// . 8809 void drawPolygon(Point[] vertexes) { 8810 if(impl is null) return; 8811 assert(vertexes.length); 8812 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 8813 foreach(ref vertex; vertexes) { 8814 if(vertex.x < minX) 8815 minX = vertex.x; 8816 if(vertex.y < minY) 8817 minY = vertex.y; 8818 if(vertex.x > maxX) 8819 maxX = vertex.x; 8820 if(vertex.y > maxY) 8821 maxY = vertex.y; 8822 transform(vertex); 8823 } 8824 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 8825 impl.drawPolygon(vertexes); 8826 } 8827 8828 /// ditto 8829 void drawPolygon(Point[] vertexes...) { 8830 if(impl is null) return; 8831 drawPolygon(vertexes); 8832 } 8833 8834 8835 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 8836 8837 //mixin NativeScreenPainterImplementation!() impl; 8838 8839 8840 // HACK: if I mixin the impl directly, it won't let me override the copy 8841 // constructor! The linker complains about there being multiple definitions. 8842 // I'll make the best of it and reference count it though. 8843 ScreenPainterImplementation* impl; 8844 } 8845 8846 // HACK: I need a pointer to the implementation so it's separate 8847 struct ScreenPainterImplementation { 8848 CapableOfBeingDrawnUpon window; 8849 int referenceCount; 8850 mixin NativeScreenPainterImplementation!(); 8851 } 8852 8853 // FIXME: i haven't actually tested the sprite class on MS Windows 8854 8855 /** 8856 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 8857 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 8858 8859 8860 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 8861 though I'm not sure that's ideal and the implementation might change. 8862 8863 You create one by giving a window and an image. It optimizes for that window, 8864 and copies the image into it to use as the initial picture. Creating a sprite 8865 can be quite slow (especially over a network connection) so you should do it 8866 as little as possible and just hold on to your sprite handles after making them. 8867 simpledisplay does try to do its best though, using the XSHM extension if available, 8868 but you should still write your code as if it will always be slow. 8869 8870 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 8871 a fast operation - much faster than drawing the Image itself every time. 8872 8873 `Sprite` represents a scarce resource which should be freed when you 8874 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 8875 after it has been disposed. If you are unsure about this, don't take chances, 8876 just let the garbage collector do it for you. But ideally, you can manage its 8877 lifetime more efficiently. 8878 8879 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 8880 support alpha blending in its drawing at this time. That might change in the 8881 future, but if you need alpha blending right now, use OpenGL instead. See 8882 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 8883 8884 Update: on April 23, 2021, I finally added alpha blending support. You must opt 8885 in by setting the enableAlpha = true in the constructor. 8886 */ 8887 version(OSXCocoa) {} else // NotYetImplementedException 8888 class Sprite : CapableOfBeingDrawnUpon { 8889 8890 /// 8891 ScreenPainter draw() { 8892 return ScreenPainter(this, handle); 8893 } 8894 8895 /++ 8896 Copies the sprite's current state into a [TrueColorImage]. 8897 8898 Be warned: this can be a very slow operation 8899 8900 History: 8901 Actually implemented on March 14, 2021 8902 +/ 8903 TrueColorImage takeScreenshot() { 8904 return trueColorImageFromNativeHandle(handle, width, height); 8905 } 8906 8907 void delegate() paintingFinishedDg() { return null; } 8908 bool closed() { return false; } 8909 ScreenPainterImplementation* activeScreenPainter_; 8910 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 8911 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 8912 8913 version(Windows) 8914 private ubyte* rawData; 8915 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 8916 // ditto on the XPicture stuff 8917 8918 version(X11) { 8919 private static XRenderPictFormat* RGB24; 8920 private static XRenderPictFormat* ARGB32; 8921 8922 private Picture xrenderPicture; 8923 } 8924 8925 version(X11) 8926 private static void requireXRender() { 8927 if(!XRenderLibrary.loadAttempted) { 8928 XRenderLibrary.loadDynamicLibrary(); 8929 } 8930 8931 if(!XRenderLibrary.loadSuccessful) 8932 throw new Exception("XRender library load failure"); 8933 8934 auto display = XDisplayConnection.get; 8935 8936 // FIXME: if we migrate X displays, these need to be changed 8937 if(RGB24 is null) 8938 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 8939 if(ARGB32 is null) 8940 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 8941 } 8942 8943 protected this() {} 8944 8945 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 8946 this._width = width; 8947 this._height = height; 8948 this.enableAlpha = enableAlpha; 8949 8950 version(X11) { 8951 auto display = XDisplayConnection.get(); 8952 8953 if(enableAlpha) { 8954 requireXRender(); 8955 } 8956 8957 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 8958 8959 if(enableAlpha) { 8960 XRenderPictureAttributes attrs; 8961 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 8962 } 8963 } else version(Windows) { 8964 version(CRuntime_DigitalMars) { 8965 //if(enableAlpha) 8966 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 8967 } 8968 8969 BITMAPINFO infoheader; 8970 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 8971 infoheader.bmiHeader.biWidth = width; 8972 infoheader.bmiHeader.biHeight = height; 8973 infoheader.bmiHeader.biPlanes = 1; 8974 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 8975 infoheader.bmiHeader.biCompression = BI_RGB; 8976 8977 // FIXME: this should prolly be a device dependent bitmap... 8978 handle = CreateDIBSection( 8979 null, 8980 &infoheader, 8981 DIB_RGB_COLORS, 8982 cast(void**) &rawData, 8983 null, 8984 0); 8985 8986 if(handle is null) 8987 throw new Exception("couldn't create pixmap"); 8988 } 8989 } 8990 8991 /// Makes a sprite based on the image with the initial contents from the Image 8992 this(SimpleWindow win, Image i) { 8993 this(win, i.width, i.height, i.enableAlpha); 8994 8995 version(X11) { 8996 auto display = XDisplayConnection.get(); 8997 auto gc = XCreateGC(display, this.handle, 0, null); 8998 scope(exit) XFreeGC(display, gc); 8999 if(i.usingXshm) 9000 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 9001 else 9002 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 9003 } else version(Windows) { 9004 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 9005 auto arrLength = itemsPerLine * height; 9006 rawData[0..arrLength] = i.rawData[0..arrLength]; 9007 } else version(OSXCocoa) { 9008 // FIXME: I have no idea if this is even any good 9009 ubyte* rawData; 9010 9011 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 9012 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 9013 colorSpace, 9014 kCGImageAlphaPremultipliedLast 9015 |kCGBitmapByteOrder32Big); 9016 CGColorSpaceRelease(colorSpace); 9017 rawData = CGBitmapContextGetData(context); 9018 9019 auto rdl = (width * height * 4); 9020 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 9021 } else static assert(0); 9022 } 9023 9024 /++ 9025 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 9026 9027 Params: 9028 where = point on the window where the upper left corner of the image will be drawn 9029 imageUpperLeft = point on the image to start the slice to draw 9030 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. 9031 History: 9032 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9033 +/ 9034 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9035 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 9036 } 9037 9038 /// Call this when you're ready to get rid of it 9039 void dispose() { 9040 version(X11) { 9041 if(xrenderPicture) { 9042 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 9043 xrenderPicture = None; 9044 } 9045 if(handle) 9046 XFreePixmap(XDisplayConnection.get(), handle); 9047 handle = None; 9048 } else version(Windows) { 9049 if(handle) 9050 DeleteObject(handle); 9051 handle = null; 9052 } else version(OSXCocoa) { 9053 if(context) 9054 CGContextRelease(context); 9055 context = null; 9056 } else static assert(0); 9057 9058 } 9059 9060 ~this() { 9061 dispose(); 9062 } 9063 9064 /// 9065 final @property int width() { return _width; } 9066 9067 /// 9068 final @property int height() { return _height; } 9069 9070 /// 9071 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 9072 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 9073 } 9074 9075 auto nativeHandle() { 9076 return handle; 9077 } 9078 9079 private: 9080 9081 int _width; 9082 int _height; 9083 bool enableAlpha; 9084 version(X11) 9085 Pixmap handle; 9086 else version(Windows) 9087 HBITMAP handle; 9088 else version(OSXCocoa) 9089 CGContextRef context; 9090 else static assert(0); 9091 } 9092 9093 /++ 9094 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 9095 9096 History: 9097 Added November 20, 2021 (dub v10.4) 9098 +/ 9099 abstract class Gradient : Sprite { 9100 protected this(int w, int h) { 9101 version(X11) { 9102 Sprite.requireXRender(); 9103 9104 super(); 9105 enableAlpha = true; 9106 _width = w; 9107 _height = h; 9108 } else version(Windows) { 9109 super(null, w, h, true); // on Windows i'm just making a bitmap myself 9110 } 9111 } 9112 9113 version(Windows) 9114 final void forEachPixel(scope Color delegate(int x, int y) dg) { 9115 auto ptr = rawData; 9116 foreach(j; 0 .. _height) 9117 foreach(i; 0 .. _width) { 9118 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 9119 *rawData = (color.a * color.b) / 255; rawData++; 9120 *rawData = (color.a * color.g) / 255; rawData++; 9121 *rawData = (color.a * color.r) / 255; rawData++; 9122 *rawData = color.a; rawData++; 9123 } 9124 } 9125 9126 version(X11) 9127 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 9128 assert(stops.length > 0); 9129 assert(stops.length <= 16, "I got lazy with buffers"); 9130 9131 XFixed[16] stopsPositions = void; 9132 XRenderColor[16] colors = void; 9133 9134 foreach(idx, stop; stops) { 9135 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 9136 auto c = stop.c; 9137 colors[idx] = XRenderColor( 9138 cast(ushort)(c.r * ushort.max / 255), 9139 cast(ushort)(c.g * ushort.max / 255), 9140 cast(ushort)(c.b * ushort.max / 255), 9141 cast(ushort)(c.a * ubyte.max) // max value here is fractional 9142 ); 9143 } 9144 9145 xrenderPicture = dg(stopsPositions, colors); 9146 } 9147 9148 /// 9149 static struct Stop { 9150 float percentage; /// between 0 and 1.0 9151 Color c; 9152 } 9153 } 9154 9155 /++ 9156 Creates a linear gradient between p1 and p2. 9157 9158 X ONLY RIGHT NOW 9159 9160 History: 9161 Added November 20, 2021 (dub v10.4) 9162 9163 Bugs: 9164 Not yet implemented on Windows. 9165 +/ 9166 class LinearGradient : Gradient { 9167 /++ 9168 9169 +/ 9170 this(Point p1, Point p2, Stop[] stops...) { 9171 super(p2.x, p2.y); 9172 9173 version(X11) { 9174 XLinearGradient gradient; 9175 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 9176 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 9177 9178 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9179 return XRenderCreateLinearGradient( 9180 XDisplayConnection.get, 9181 &gradient, 9182 stopsPositions.ptr, 9183 colors.ptr, 9184 cast(int) stops.length); 9185 }); 9186 } else version(Windows) { 9187 // FIXME 9188 forEachPixel((int x, int y) { 9189 import core.stdc.math; 9190 9191 //sqrtf( 9192 9193 return Color.transparent; 9194 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9195 }); 9196 } 9197 } 9198 } 9199 9200 /++ 9201 A conical gradient goes from color to color around a circumference from a center point. 9202 9203 X ONLY RIGHT NOW 9204 9205 History: 9206 Added November 20, 2021 (dub v10.4) 9207 9208 Bugs: 9209 Not yet implemented on Windows. 9210 +/ 9211 class ConicalGradient : Gradient { 9212 /++ 9213 9214 +/ 9215 this(Point center, float angleInDegrees, Stop[] stops...) { 9216 super(center.x * 2, center.y * 2); 9217 9218 version(X11) { 9219 XConicalGradient gradient; 9220 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 9221 gradient.angle = cast(int)(angleInDegrees * ushort.max); 9222 9223 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9224 return XRenderCreateConicalGradient( 9225 XDisplayConnection.get, 9226 &gradient, 9227 stopsPositions.ptr, 9228 colors.ptr, 9229 cast(int) stops.length); 9230 }); 9231 } else version(Windows) { 9232 // FIXME 9233 forEachPixel((int x, int y) { 9234 import core.stdc.math; 9235 9236 //sqrtf( 9237 9238 return Color.transparent; 9239 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9240 }); 9241 9242 } 9243 } 9244 } 9245 9246 /++ 9247 A radial gradient goes from color to color based on distance from the center. 9248 It is like rings of color. 9249 9250 X ONLY RIGHT NOW 9251 9252 9253 More specifically, you create two circles: an inner circle and an outer circle. 9254 The gradient is only drawn in the area outside the inner circle but inside the outer 9255 circle. The closest line between those two circles forms the line for the gradient 9256 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 9257 9258 History: 9259 Added November 20, 2021 (dub v10.4) 9260 9261 Bugs: 9262 Not yet implemented on Windows. 9263 +/ 9264 class RadialGradient : Gradient { 9265 /++ 9266 9267 +/ 9268 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 9269 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 9270 9271 version(X11) { 9272 XRadialGradient gradient; 9273 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 9274 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 9275 9276 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9277 return XRenderCreateRadialGradient( 9278 XDisplayConnection.get, 9279 &gradient, 9280 stopsPositions.ptr, 9281 colors.ptr, 9282 cast(int) stops.length); 9283 }); 9284 } else version(Windows) { 9285 // FIXME 9286 forEachPixel((int x, int y) { 9287 import core.stdc.math; 9288 9289 //sqrtf( 9290 9291 return Color.transparent; 9292 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9293 }); 9294 } 9295 } 9296 } 9297 9298 9299 9300 /+ 9301 NOT IMPLEMENTED 9302 9303 A display-stored image optimized for relatively quick drawing, like 9304 [Sprite], but this one supports alpha channel blending and does NOT 9305 support direct drawing upon it with a [ScreenPainter]. 9306 9307 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 9308 plain [ScreenPainter]... sort of. 9309 9310 On X11, it requires the Xrender extension and library. This is available 9311 almost everywhere though. 9312 9313 History: 9314 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 9315 +/ 9316 version(none) 9317 class AlphaSprite { 9318 /++ 9319 Copies the given image into it. 9320 +/ 9321 this(MemoryImage img) { 9322 9323 if(!XRenderLibrary.loadAttempted) { 9324 XRenderLibrary.loadDynamicLibrary(); 9325 9326 // FIXME: this needs to be reconstructed when the X server changes 9327 repopulateX(); 9328 } 9329 if(!XRenderLibrary.loadSuccessful) 9330 throw new Exception("XRender library load failure"); 9331 9332 // I probably need to put the alpha mask in a separate Picture 9333 // ugh 9334 // maybe the Sprite itself can have an alpha bitmask anyway 9335 9336 9337 auto display = XDisplayConnection.get(); 9338 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 9339 9340 9341 XRenderPictureAttributes attrs; 9342 9343 handle = XRenderCreatePicture( 9344 XDisplayConnection.get, 9345 pixmap, 9346 RGBA, 9347 0, 9348 &attrs 9349 ); 9350 9351 } 9352 9353 // maybe i'll use the create gradient functions too with static factories.. 9354 9355 void drawAt(ScreenPainter painter, Point where) { 9356 //painter.drawPixmap(this, where); 9357 9358 XRenderPictureAttributes attrs; 9359 9360 auto pic = XRenderCreatePicture( 9361 XDisplayConnection.get, 9362 painter.impl.d, 9363 RGB, 9364 0, 9365 &attrs 9366 ); 9367 9368 XRenderComposite( 9369 XDisplayConnection.get, 9370 3, // PictOpOver 9371 handle, 9372 None, 9373 pic, 9374 0, // src 9375 0, 9376 0, // mask 9377 0, 9378 10, // dest 9379 10, 9380 100, // width 9381 100 9382 ); 9383 9384 /+ 9385 XRenderFreePicture( 9386 XDisplayConnection.get, 9387 pic 9388 ); 9389 9390 XRenderFreePicture( 9391 XDisplayConnection.get, 9392 fill 9393 ); 9394 +/ 9395 // on Windows you can stretch but Xrender still can't :( 9396 } 9397 9398 static XRenderPictFormat* RGB; 9399 static XRenderPictFormat* RGBA; 9400 static void repopulateX() { 9401 auto display = XDisplayConnection.get; 9402 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 9403 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 9404 } 9405 9406 XPixmap pixmap; 9407 Picture handle; 9408 } 9409 9410 /// 9411 interface CapableOfBeingDrawnUpon { 9412 /// 9413 ScreenPainter draw(); 9414 /// 9415 int width(); 9416 /// 9417 int height(); 9418 protected ScreenPainterImplementation* activeScreenPainter(); 9419 protected void activeScreenPainter(ScreenPainterImplementation*); 9420 bool closed(); 9421 9422 void delegate() paintingFinishedDg(); 9423 9424 /// Be warned: this can be a very slow operation 9425 TrueColorImage takeScreenshot(); 9426 } 9427 9428 /// 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]. 9429 void flushGui() { 9430 version(X11) { 9431 auto dpy = XDisplayConnection.get(); 9432 XLockDisplay(dpy); 9433 scope(exit) XUnlockDisplay(dpy); 9434 XFlush(dpy); 9435 } 9436 } 9437 9438 /++ 9439 Runs the given code in the GUI thread when its event loop 9440 is available, blocking until it completes. This allows you 9441 to create and manipulate windows from another thread without 9442 invoking undefined behavior. 9443 9444 If this is the gui thread, it runs the code immediately. 9445 9446 If no gui thread exists yet, the current thread is assumed 9447 to be it. Attempting to create windows or run the event loop 9448 in any other thread will cause an assertion failure. 9449 9450 9451 $(TIP 9452 Did you know you can use UFCS on delegate literals? 9453 9454 () { 9455 // code here 9456 }.runInGuiThread; 9457 ) 9458 9459 Returns: 9460 `true` if the function was called, `false` if it was not. 9461 The function may not be called because the gui thread had 9462 already terminated by the time you called this. 9463 9464 History: 9465 Added April 10, 2020 (v7.2.0) 9466 9467 Return value added and implementation tweaked to avoid locking 9468 at program termination on February 24, 2021 (v9.2.1). 9469 +/ 9470 bool runInGuiThread(scope void delegate() dg) @trusted { 9471 claimGuiThread(); 9472 9473 if(thisIsGuiThread) { 9474 dg(); 9475 return true; 9476 } 9477 9478 if(guiThreadTerminating) 9479 return false; 9480 9481 import core.sync.semaphore; 9482 static Semaphore sc; 9483 if(sc is null) 9484 sc = new Semaphore(); 9485 9486 static RunQueueMember* rqm; 9487 if(rqm is null) 9488 rqm = new RunQueueMember; 9489 rqm.dg = cast(typeof(rqm.dg)) dg; 9490 rqm.signal = sc; 9491 rqm.thrown = null; 9492 9493 synchronized(runInGuiThreadLock) { 9494 runInGuiThreadQueue ~= rqm; 9495 } 9496 9497 if(!SimpleWindow.eventWakeUp()) 9498 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 9499 9500 rqm.signal.wait(); 9501 auto t = rqm.thrown; 9502 9503 if(t) 9504 throw t; 9505 9506 return true; 9507 } 9508 9509 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 9510 claimGuiThread(); 9511 9512 try { 9513 9514 if(thisIsGuiThread) { 9515 dg(); 9516 return; 9517 } 9518 9519 if(guiThreadTerminating) 9520 return; 9521 9522 RunQueueMember* rqm = new RunQueueMember; 9523 rqm.dg = cast(typeof(rqm.dg)) dg; 9524 rqm.signal = null; 9525 rqm.thrown = null; 9526 9527 synchronized(runInGuiThreadLock) { 9528 runInGuiThreadQueue ~= rqm; 9529 } 9530 9531 if(!SimpleWindow.eventWakeUp()) 9532 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 9533 } catch(Exception e) { 9534 if(handleError) 9535 handleError(e); 9536 } 9537 } 9538 9539 private void runPendingRunInGuiThreadDelegates() { 9540 more: 9541 RunQueueMember* next; 9542 synchronized(runInGuiThreadLock) { 9543 if(runInGuiThreadQueue.length) { 9544 next = runInGuiThreadQueue[0]; 9545 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 9546 } else { 9547 next = null; 9548 } 9549 } 9550 9551 if(next) { 9552 try { 9553 next.dg(); 9554 next.thrown = null; 9555 } catch(Throwable t) { 9556 next.thrown = t; 9557 } 9558 9559 if(next.signal) 9560 next.signal.notify(); 9561 9562 goto more; 9563 } 9564 } 9565 9566 private void claimGuiThread() nothrow { 9567 import core.atomic; 9568 if(cas(&guiThreadExists, false, true)) 9569 thisIsGuiThread = true; 9570 } 9571 9572 private struct RunQueueMember { 9573 void delegate() dg; 9574 import core.sync.semaphore; 9575 Semaphore signal; 9576 Throwable thrown; 9577 } 9578 9579 private __gshared RunQueueMember*[] runInGuiThreadQueue; 9580 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 9581 private bool thisIsGuiThread = false; 9582 private shared bool guiThreadExists = false; 9583 private shared bool guiThreadTerminating = false; 9584 9585 private void guiThreadFinalize() { 9586 assert(thisIsGuiThread); 9587 9588 guiThreadTerminating = true; // don't add any more from this point on 9589 runPendingRunInGuiThreadDelegates(); 9590 } 9591 9592 /+ 9593 interface IPromise { 9594 void reportProgress(int current, int max, string message); 9595 9596 /+ // not formally in cuz of templates but still 9597 IPromise Then(); 9598 IPromise Catch(); 9599 IPromise Finally(); 9600 +/ 9601 } 9602 9603 /+ 9604 auto promise = async({ ... }); 9605 promise.Then(whatever). 9606 Then(whateverelse). 9607 Catch((exception) { }); 9608 9609 9610 A promise is run inside a fiber and it looks something like: 9611 9612 try { 9613 auto res = whatever(); 9614 auto res2 = whateverelse(res); 9615 } catch(Exception e) { 9616 { }(e); 9617 } 9618 9619 When a thing succeeds, it is passed as an arg to the next 9620 +/ 9621 class Promise(T) : IPromise { 9622 auto Then() { return null; } 9623 auto Catch() { return null; } 9624 auto Finally() { return null; } 9625 9626 // wait for it to resolve and return the value, or rethrow the error if that occurred. 9627 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 9628 T await(); 9629 } 9630 9631 interface Task { 9632 } 9633 9634 interface Resolvable(T) : Task { 9635 void run(); 9636 9637 void resolve(T); 9638 9639 Resolvable!T then(void delegate(T)); // returns a new promise 9640 Resolvable!T error(Throwable); // js catch 9641 Resolvable!T completed(); // js finally 9642 9643 } 9644 9645 /++ 9646 Runs `work` in a helper thread and sends its return value back to the main gui 9647 thread as the argument to `uponCompletion`. If `work` throws, the exception is 9648 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 9649 kill the program. 9650 9651 You can call reportProgress(position, max, message) to update your parent window 9652 on your progress. 9653 9654 I should also use `shared` methods. FIXME 9655 9656 History: 9657 Added March 6, 2021 (dub version 9.3). 9658 +/ 9659 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 9660 uponCompletion(work(null)); 9661 } 9662 9663 +/ 9664 9665 /// Used internal to dispatch events to various classes. 9666 interface CapableOfHandlingNativeEvent { 9667 NativeEventHandler getNativeEventHandler(); 9668 9669 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 9670 9671 version(X11) { 9672 // if this is impossible, you are allowed to just throw from it 9673 // Note: if you call it from another object, set a flag cuz the manger will call you again 9674 void recreateAfterDisconnect(); 9675 // discard any *connection specific* state, but keep enough that you 9676 // can be recreated if possible. discardConnectionState() is always called immediately 9677 // before recreateAfterDisconnect(), so you can set a flag there to decide if 9678 // you need initialization order 9679 void discardConnectionState(); 9680 } 9681 } 9682 9683 version(X11) 9684 /++ 9685 State of keys on mouse events, especially motion. 9686 9687 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 9688 +/ 9689 enum ModifierState : uint { 9690 shift = 1, /// 9691 capsLock = 2, /// 9692 ctrl = 4, /// 9693 alt = 8, /// Not always available on Windows 9694 windows = 64, /// ditto 9695 numLock = 16, /// 9696 9697 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 9698 middleButtonDown = 512, /// ditto 9699 rightButtonDown = 1024, /// ditto 9700 } 9701 else version(Windows) 9702 /// ditto 9703 enum ModifierState : uint { 9704 shift = 4, /// 9705 ctrl = 8, /// 9706 9707 // i'm not sure if the next two are available 9708 alt = 256, /// not always available on Windows 9709 windows = 512, /// ditto 9710 9711 capsLock = 1024, /// 9712 numLock = 2048, /// 9713 9714 leftButtonDown = 1, /// not available on key events 9715 middleButtonDown = 16, /// ditto 9716 rightButtonDown = 2, /// ditto 9717 9718 backButtonDown = 0x20, /// not available on X 9719 forwardButtonDown = 0x40, /// ditto 9720 } 9721 else version(OSXCocoa) 9722 // FIXME FIXME NotYetImplementedException 9723 enum ModifierState : uint { 9724 shift = 1, /// 9725 capsLock = 2, /// 9726 ctrl = 4, /// 9727 alt = 8, /// Not always available on Windows 9728 windows = 64, /// ditto 9729 numLock = 16, /// 9730 9731 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 9732 middleButtonDown = 512, /// ditto 9733 rightButtonDown = 1024, /// ditto 9734 } 9735 9736 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 9737 enum MouseButton : int { 9738 none = 0, 9739 left = 1, /// 9740 right = 2, /// 9741 middle = 4, /// 9742 wheelUp = 8, /// 9743 wheelDown = 16, /// 9744 backButton = 32, /// often found on the thumb and used for back in browsers 9745 forwardButton = 64, /// often found on the thumb and used for forward in browsers 9746 } 9747 9748 version(X11) { 9749 // FIXME: match ASCII whenever we can. Most of it is already there, 9750 // but there's a few exceptions and mismatches with Windows 9751 9752 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 9753 enum Key { 9754 Escape = 0xff1b, /// 9755 F1 = 0xffbe, /// 9756 F2 = 0xffbf, /// 9757 F3 = 0xffc0, /// 9758 F4 = 0xffc1, /// 9759 F5 = 0xffc2, /// 9760 F6 = 0xffc3, /// 9761 F7 = 0xffc4, /// 9762 F8 = 0xffc5, /// 9763 F9 = 0xffc6, /// 9764 F10 = 0xffc7, /// 9765 F11 = 0xffc8, /// 9766 F12 = 0xffc9, /// 9767 PrintScreen = 0xff61, /// 9768 ScrollLock = 0xff14, /// 9769 Pause = 0xff13, /// 9770 Grave = 0x60, /// The $(BACKTICK) ~ key 9771 // number keys across the top of the keyboard 9772 N1 = 0x31, /// Number key atop the keyboard 9773 N2 = 0x32, /// 9774 N3 = 0x33, /// 9775 N4 = 0x34, /// 9776 N5 = 0x35, /// 9777 N6 = 0x36, /// 9778 N7 = 0x37, /// 9779 N8 = 0x38, /// 9780 N9 = 0x39, /// 9781 N0 = 0x30, /// 9782 Dash = 0x2d, /// 9783 Equals = 0x3d, /// 9784 Backslash = 0x5c, /// The \ | key 9785 Backspace = 0xff08, /// 9786 Insert = 0xff63, /// 9787 Home = 0xff50, /// 9788 PageUp = 0xff55, /// 9789 Delete = 0xffff, /// 9790 End = 0xff57, /// 9791 PageDown = 0xff56, /// 9792 Up = 0xff52, /// 9793 Down = 0xff54, /// 9794 Left = 0xff51, /// 9795 Right = 0xff53, /// 9796 9797 Tab = 0xff09, /// 9798 Q = 0x71, /// 9799 W = 0x77, /// 9800 E = 0x65, /// 9801 R = 0x72, /// 9802 T = 0x74, /// 9803 Y = 0x79, /// 9804 U = 0x75, /// 9805 I = 0x69, /// 9806 O = 0x6f, /// 9807 P = 0x70, /// 9808 LeftBracket = 0x5b, /// the [ { key 9809 RightBracket = 0x5d, /// the ] } key 9810 CapsLock = 0xffe5, /// 9811 A = 0x61, /// 9812 S = 0x73, /// 9813 D = 0x64, /// 9814 F = 0x66, /// 9815 G = 0x67, /// 9816 H = 0x68, /// 9817 J = 0x6a, /// 9818 K = 0x6b, /// 9819 L = 0x6c, /// 9820 Semicolon = 0x3b, /// 9821 Apostrophe = 0x27, /// 9822 Enter = 0xff0d, /// 9823 Shift = 0xffe1, /// 9824 Z = 0x7a, /// 9825 X = 0x78, /// 9826 C = 0x63, /// 9827 V = 0x76, /// 9828 B = 0x62, /// 9829 N = 0x6e, /// 9830 M = 0x6d, /// 9831 Comma = 0x2c, /// 9832 Period = 0x2e, /// 9833 Slash = 0x2f, /// the / ? key 9834 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 9835 Ctrl = 0xffe3, /// 9836 Windows = 0xffeb, /// 9837 Alt = 0xffe9, /// 9838 Space = 0x20, /// 9839 Alt_r = 0xffea, /// ditto of shift_r 9840 Windows_r = 0xffec, /// 9841 Menu = 0xff67, /// 9842 Ctrl_r = 0xffe4, /// 9843 9844 NumLock = 0xff7f, /// 9845 Divide = 0xffaf, /// The / key on the number pad 9846 Multiply = 0xffaa, /// The * key on the number pad 9847 Minus = 0xffad, /// The - key on the number pad 9848 Plus = 0xffab, /// The + key on the number pad 9849 PadEnter = 0xff8d, /// Numberpad enter key 9850 Pad1 = 0xff9c, /// Numberpad keys 9851 Pad2 = 0xff99, /// 9852 Pad3 = 0xff9b, /// 9853 Pad4 = 0xff96, /// 9854 Pad5 = 0xff9d, /// 9855 Pad6 = 0xff98, /// 9856 Pad7 = 0xff95, /// 9857 Pad8 = 0xff97, /// 9858 Pad9 = 0xff9a, /// 9859 Pad0 = 0xff9e, /// 9860 PadDot = 0xff9f, /// 9861 } 9862 } else version(Windows) { 9863 // the character here is for en-us layouts and for illustration only 9864 // if you actually want to get characters, wait for character events 9865 // (the argument to your event handler is simply a dchar) 9866 // those will be converted by the OS for the right locale. 9867 9868 enum Key { 9869 Escape = 0x1b, 9870 F1 = 0x70, 9871 F2 = 0x71, 9872 F3 = 0x72, 9873 F4 = 0x73, 9874 F5 = 0x74, 9875 F6 = 0x75, 9876 F7 = 0x76, 9877 F8 = 0x77, 9878 F9 = 0x78, 9879 F10 = 0x79, 9880 F11 = 0x7a, 9881 F12 = 0x7b, 9882 PrintScreen = 0x2c, 9883 ScrollLock = 0x91, 9884 Pause = 0x13, 9885 Grave = 0xc0, 9886 // number keys across the top of the keyboard 9887 N1 = 0x31, 9888 N2 = 0x32, 9889 N3 = 0x33, 9890 N4 = 0x34, 9891 N5 = 0x35, 9892 N6 = 0x36, 9893 N7 = 0x37, 9894 N8 = 0x38, 9895 N9 = 0x39, 9896 N0 = 0x30, 9897 Dash = 0xbd, 9898 Equals = 0xbb, 9899 Backslash = 0xdc, 9900 Backspace = 0x08, 9901 Insert = 0x2d, 9902 Home = 0x24, 9903 PageUp = 0x21, 9904 Delete = 0x2e, 9905 End = 0x23, 9906 PageDown = 0x22, 9907 Up = 0x26, 9908 Down = 0x28, 9909 Left = 0x25, 9910 Right = 0x27, 9911 9912 Tab = 0x09, 9913 Q = 0x51, 9914 W = 0x57, 9915 E = 0x45, 9916 R = 0x52, 9917 T = 0x54, 9918 Y = 0x59, 9919 U = 0x55, 9920 I = 0x49, 9921 O = 0x4f, 9922 P = 0x50, 9923 LeftBracket = 0xdb, 9924 RightBracket = 0xdd, 9925 CapsLock = 0x14, 9926 A = 0x41, 9927 S = 0x53, 9928 D = 0x44, 9929 F = 0x46, 9930 G = 0x47, 9931 H = 0x48, 9932 J = 0x4a, 9933 K = 0x4b, 9934 L = 0x4c, 9935 Semicolon = 0xba, 9936 Apostrophe = 0xde, 9937 Enter = 0x0d, 9938 Shift = 0x10, 9939 Z = 0x5a, 9940 X = 0x58, 9941 C = 0x43, 9942 V = 0x56, 9943 B = 0x42, 9944 N = 0x4e, 9945 M = 0x4d, 9946 Comma = 0xbc, 9947 Period = 0xbe, 9948 Slash = 0xbf, 9949 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 9950 Ctrl = 0x11, 9951 Windows = 0x5b, 9952 Alt = -5, // FIXME 9953 Space = 0x20, 9954 Alt_r = 0xffea, // ditto of shift_r 9955 Windows_r = 0x5c, // ditto of shift_r 9956 Menu = 0x5d, 9957 Ctrl_r = 0xa3, // ditto of shift_r 9958 9959 NumLock = 0x90, 9960 Divide = 0x6f, 9961 Multiply = 0x6a, 9962 Minus = 0x6d, 9963 Plus = 0x6b, 9964 PadEnter = -8, // FIXME 9965 Pad1 = 0x61, 9966 Pad2 = 0x62, 9967 Pad3 = 0x63, 9968 Pad4 = 0x64, 9969 Pad5 = 0x65, 9970 Pad6 = 0x66, 9971 Pad7 = 0x67, 9972 Pad8 = 0x68, 9973 Pad9 = 0x69, 9974 Pad0 = 0x60, 9975 PadDot = 0x6e, 9976 } 9977 9978 // I'm keeping this around for reference purposes 9979 // ideally all these buttons will be listed for all platforms, 9980 // but now now I'm just focusing on my US keyboard 9981 version(none) 9982 enum Key { 9983 LBUTTON = 0x01, 9984 RBUTTON = 0x02, 9985 CANCEL = 0x03, 9986 MBUTTON = 0x04, 9987 //static if (_WIN32_WINNT > = 0x500) { 9988 XBUTTON1 = 0x05, 9989 XBUTTON2 = 0x06, 9990 //} 9991 BACK = 0x08, 9992 TAB = 0x09, 9993 CLEAR = 0x0C, 9994 RETURN = 0x0D, 9995 SHIFT = 0x10, 9996 CONTROL = 0x11, 9997 MENU = 0x12, 9998 PAUSE = 0x13, 9999 CAPITAL = 0x14, 10000 KANA = 0x15, 10001 HANGEUL = 0x15, 10002 HANGUL = 0x15, 10003 JUNJA = 0x17, 10004 FINAL = 0x18, 10005 HANJA = 0x19, 10006 KANJI = 0x19, 10007 ESCAPE = 0x1B, 10008 CONVERT = 0x1C, 10009 NONCONVERT = 0x1D, 10010 ACCEPT = 0x1E, 10011 MODECHANGE = 0x1F, 10012 SPACE = 0x20, 10013 PRIOR = 0x21, 10014 NEXT = 0x22, 10015 END = 0x23, 10016 HOME = 0x24, 10017 LEFT = 0x25, 10018 UP = 0x26, 10019 RIGHT = 0x27, 10020 DOWN = 0x28, 10021 SELECT = 0x29, 10022 PRINT = 0x2A, 10023 EXECUTE = 0x2B, 10024 SNAPSHOT = 0x2C, 10025 INSERT = 0x2D, 10026 DELETE = 0x2E, 10027 HELP = 0x2F, 10028 LWIN = 0x5B, 10029 RWIN = 0x5C, 10030 APPS = 0x5D, 10031 SLEEP = 0x5F, 10032 NUMPAD0 = 0x60, 10033 NUMPAD1 = 0x61, 10034 NUMPAD2 = 0x62, 10035 NUMPAD3 = 0x63, 10036 NUMPAD4 = 0x64, 10037 NUMPAD5 = 0x65, 10038 NUMPAD6 = 0x66, 10039 NUMPAD7 = 0x67, 10040 NUMPAD8 = 0x68, 10041 NUMPAD9 = 0x69, 10042 MULTIPLY = 0x6A, 10043 ADD = 0x6B, 10044 SEPARATOR = 0x6C, 10045 SUBTRACT = 0x6D, 10046 DECIMAL = 0x6E, 10047 DIVIDE = 0x6F, 10048 F1 = 0x70, 10049 F2 = 0x71, 10050 F3 = 0x72, 10051 F4 = 0x73, 10052 F5 = 0x74, 10053 F6 = 0x75, 10054 F7 = 0x76, 10055 F8 = 0x77, 10056 F9 = 0x78, 10057 F10 = 0x79, 10058 F11 = 0x7A, 10059 F12 = 0x7B, 10060 F13 = 0x7C, 10061 F14 = 0x7D, 10062 F15 = 0x7E, 10063 F16 = 0x7F, 10064 F17 = 0x80, 10065 F18 = 0x81, 10066 F19 = 0x82, 10067 F20 = 0x83, 10068 F21 = 0x84, 10069 F22 = 0x85, 10070 F23 = 0x86, 10071 F24 = 0x87, 10072 NUMLOCK = 0x90, 10073 SCROLL = 0x91, 10074 LSHIFT = 0xA0, 10075 RSHIFT = 0xA1, 10076 LCONTROL = 0xA2, 10077 RCONTROL = 0xA3, 10078 LMENU = 0xA4, 10079 RMENU = 0xA5, 10080 //static if (_WIN32_WINNT > = 0x500) { 10081 BROWSER_BACK = 0xA6, 10082 BROWSER_FORWARD = 0xA7, 10083 BROWSER_REFRESH = 0xA8, 10084 BROWSER_STOP = 0xA9, 10085 BROWSER_SEARCH = 0xAA, 10086 BROWSER_FAVORITES = 0xAB, 10087 BROWSER_HOME = 0xAC, 10088 VOLUME_MUTE = 0xAD, 10089 VOLUME_DOWN = 0xAE, 10090 VOLUME_UP = 0xAF, 10091 MEDIA_NEXT_TRACK = 0xB0, 10092 MEDIA_PREV_TRACK = 0xB1, 10093 MEDIA_STOP = 0xB2, 10094 MEDIA_PLAY_PAUSE = 0xB3, 10095 LAUNCH_MAIL = 0xB4, 10096 LAUNCH_MEDIA_SELECT = 0xB5, 10097 LAUNCH_APP1 = 0xB6, 10098 LAUNCH_APP2 = 0xB7, 10099 //} 10100 OEM_1 = 0xBA, 10101 //static if (_WIN32_WINNT > = 0x500) { 10102 OEM_PLUS = 0xBB, 10103 OEM_COMMA = 0xBC, 10104 OEM_MINUS = 0xBD, 10105 OEM_PERIOD = 0xBE, 10106 //} 10107 OEM_2 = 0xBF, 10108 OEM_3 = 0xC0, 10109 OEM_4 = 0xDB, 10110 OEM_5 = 0xDC, 10111 OEM_6 = 0xDD, 10112 OEM_7 = 0xDE, 10113 OEM_8 = 0xDF, 10114 //static if (_WIN32_WINNT > = 0x500) { 10115 OEM_102 = 0xE2, 10116 //} 10117 PROCESSKEY = 0xE5, 10118 //static if (_WIN32_WINNT > = 0x500) { 10119 PACKET = 0xE7, 10120 //} 10121 ATTN = 0xF6, 10122 CRSEL = 0xF7, 10123 EXSEL = 0xF8, 10124 EREOF = 0xF9, 10125 PLAY = 0xFA, 10126 ZOOM = 0xFB, 10127 NONAME = 0xFC, 10128 PA1 = 0xFD, 10129 OEM_CLEAR = 0xFE, 10130 } 10131 10132 } else version(OSXCocoa) { 10133 // FIXME 10134 enum Key { 10135 Escape = 0x1b, 10136 F1 = 0x70, 10137 F2 = 0x71, 10138 F3 = 0x72, 10139 F4 = 0x73, 10140 F5 = 0x74, 10141 F6 = 0x75, 10142 F7 = 0x76, 10143 F8 = 0x77, 10144 F9 = 0x78, 10145 F10 = 0x79, 10146 F11 = 0x7a, 10147 F12 = 0x7b, 10148 PrintScreen = 0x2c, 10149 ScrollLock = -2, // FIXME 10150 Pause = -3, // FIXME 10151 Grave = 0xc0, 10152 // number keys across the top of the keyboard 10153 N1 = 0x31, 10154 N2 = 0x32, 10155 N3 = 0x33, 10156 N4 = 0x34, 10157 N5 = 0x35, 10158 N6 = 0x36, 10159 N7 = 0x37, 10160 N8 = 0x38, 10161 N9 = 0x39, 10162 N0 = 0x30, 10163 Dash = 0xbd, 10164 Equals = 0xbb, 10165 Backslash = 0xdc, 10166 Backspace = 0x08, 10167 Insert = 0x2d, 10168 Home = 0x24, 10169 PageUp = 0x21, 10170 Delete = 0x2e, 10171 End = 0x23, 10172 PageDown = 0x22, 10173 Up = 0x26, 10174 Down = 0x28, 10175 Left = 0x25, 10176 Right = 0x27, 10177 10178 Tab = 0x09, 10179 Q = 0x51, 10180 W = 0x57, 10181 E = 0x45, 10182 R = 0x52, 10183 T = 0x54, 10184 Y = 0x59, 10185 U = 0x55, 10186 I = 0x49, 10187 O = 0x4f, 10188 P = 0x50, 10189 LeftBracket = 0xdb, 10190 RightBracket = 0xdd, 10191 CapsLock = 0x14, 10192 A = 0x41, 10193 S = 0x53, 10194 D = 0x44, 10195 F = 0x46, 10196 G = 0x47, 10197 H = 0x48, 10198 J = 0x4a, 10199 K = 0x4b, 10200 L = 0x4c, 10201 Semicolon = 0xba, 10202 Apostrophe = 0xde, 10203 Enter = 0x0d, 10204 Shift = 0x10, 10205 Z = 0x5a, 10206 X = 0x58, 10207 C = 0x43, 10208 V = 0x56, 10209 B = 0x42, 10210 N = 0x4e, 10211 M = 0x4d, 10212 Comma = 0xbc, 10213 Period = 0xbe, 10214 Slash = 0xbf, 10215 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10216 Ctrl = 0x11, 10217 Windows = 0x5b, 10218 Alt = -5, // FIXME 10219 Space = 0x20, 10220 Alt_r = 0xffea, // ditto of shift_r 10221 Windows_r = -6, // FIXME 10222 Menu = 0x5d, 10223 Ctrl_r = -7, // FIXME 10224 10225 NumLock = 0x90, 10226 Divide = 0x6f, 10227 Multiply = 0x6a, 10228 Minus = 0x6d, 10229 Plus = 0x6b, 10230 PadEnter = -8, // FIXME 10231 // FIXME for the rest of these: 10232 Pad1 = 0xff9c, 10233 Pad2 = 0xff99, 10234 Pad3 = 0xff9b, 10235 Pad4 = 0xff96, 10236 Pad5 = 0xff9d, 10237 Pad6 = 0xff98, 10238 Pad7 = 0xff95, 10239 Pad8 = 0xff97, 10240 Pad9 = 0xff9a, 10241 Pad0 = 0xff9e, 10242 PadDot = 0xff9f, 10243 } 10244 10245 } 10246 10247 /* Additional utilities */ 10248 10249 10250 Color fromHsl(real h, real s, real l) { 10251 return arsd.color.fromHsl([h,s,l]); 10252 } 10253 10254 10255 10256 /* ********** What follows is the system-specific implementations *********/ 10257 version(Windows) { 10258 10259 10260 // helpers for making HICONs from MemoryImages 10261 class WindowsIcon { 10262 struct Win32Icon(int colorCount) { 10263 align(1): 10264 uint biSize; 10265 int biWidth; 10266 int biHeight; 10267 ushort biPlanes; 10268 ushort biBitCount; 10269 uint biCompression; 10270 uint biSizeImage; 10271 int biXPelsPerMeter; 10272 int biYPelsPerMeter; 10273 uint biClrUsed; 10274 uint biClrImportant; 10275 RGBQUAD[colorCount] biColors; 10276 /* Pixels: 10277 Uint8 pixels[] 10278 */ 10279 /* Mask: 10280 Uint8 mask[] 10281 */ 10282 10283 ubyte[4096] data; 10284 10285 void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 10286 width = mi.width; 10287 height = mi.height; 10288 10289 auto indexedImage = cast(IndexedImage) mi; 10290 if(indexedImage is null) 10291 indexedImage = quantize(mi.getAsTrueColorImage()); 10292 10293 assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy 10294 assert(height %4 == 0); 10295 10296 int icon_plen = height*((width+3)&~3); 10297 int icon_mlen = height*((((width+7)/8)+3)&~3); 10298 icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 10299 10300 biSize = 40; 10301 biWidth = width; 10302 biHeight = height*2; 10303 biPlanes = 1; 10304 biBitCount = 8; 10305 biSizeImage = icon_plen+icon_mlen; 10306 10307 int offset = 0; 10308 int andOff = icon_plen * 8; // the and offset is in bits 10309 for(int y = height - 1; y >= 0; y--) { 10310 int off2 = y * width; 10311 foreach(x; 0 .. width) { 10312 const b = indexedImage.data[off2 + x]; 10313 data[offset] = b; 10314 offset++; 10315 10316 const andBit = andOff % 8; 10317 const andIdx = andOff / 8; 10318 assert(b < indexedImage.palette.length); 10319 // this is anded to the destination, since and 0 means erase, 10320 // we want that to be opaque, and 1 for transparent 10321 auto transparent = (indexedImage.palette[b].a <= 127); 10322 data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0); 10323 10324 andOff++; 10325 } 10326 10327 andOff += andOff % 32; 10328 } 10329 10330 foreach(idx, entry; indexedImage.palette) { 10331 if(entry.a > 127) { 10332 biColors[idx].rgbBlue = entry.b; 10333 biColors[idx].rgbGreen = entry.g; 10334 biColors[idx].rgbRed = entry.r; 10335 } else { 10336 biColors[idx].rgbBlue = 255; 10337 biColors[idx].rgbGreen = 255; 10338 biColors[idx].rgbRed = 255; 10339 } 10340 } 10341 10342 /* 10343 data[0..icon_plen] = getFlippedUnfilteredDatastream(png); 10344 data[icon_plen..icon_plen+icon_mlen] = getANDMask(png); 10345 //icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0); 10346 auto pngMap = fetchPaletteWin32(png); 10347 biColors[0..pngMap.length] = pngMap[]; 10348 */ 10349 } 10350 } 10351 10352 10353 Win32Icon!(256) icon_win32; 10354 10355 10356 this(MemoryImage mi) { 10357 int icon_len, width, height; 10358 10359 icon_win32.fromMemoryImage(mi, icon_len, width, height); 10360 10361 /* 10362 PNG* png = readPnpngData); 10363 PNGHeader pngh = getHeader(png); 10364 void* icon_win32; 10365 if(pngh.depth == 4) { 10366 auto i = new Win32Icon!(16); 10367 i.fromPNG(png, pngh, icon_len, width, height); 10368 icon_win32 = i; 10369 } 10370 else if(pngh.depth == 8) { 10371 auto i = new Win32Icon!(256); 10372 i.fromPNG(png, pngh, icon_len, width, height); 10373 icon_win32 = i; 10374 } else assert(0); 10375 */ 10376 10377 hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0); 10378 10379 if(hIcon is null) throw new Exception("CreateIconFromResourceEx"); 10380 } 10381 10382 ~this() { 10383 DestroyIcon(hIcon); 10384 } 10385 10386 HICON hIcon; 10387 } 10388 10389 10390 10391 10392 10393 10394 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 10395 alias HWND NativeWindowHandle; 10396 10397 extern(Windows) 10398 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 10399 try { 10400 if(SimpleWindow.handleNativeGlobalEvent !is null) { 10401 // it returns zero if the message is handled, so we won't do anything more there 10402 // do I like that though? 10403 int mustReturn; 10404 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 10405 if(mustReturn) 10406 return ret; 10407 } 10408 10409 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 10410 if(window.getNativeEventHandler !is null) { 10411 int mustReturn; 10412 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 10413 if(mustReturn) 10414 return ret; 10415 } 10416 if(auto w = cast(SimpleWindow) (*window)) 10417 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 10418 else 10419 return DefWindowProc(hWnd, iMessage, wParam, lParam); 10420 } else { 10421 return DefWindowProc(hWnd, iMessage, wParam, lParam); 10422 } 10423 } catch (Exception e) { 10424 try { 10425 sdpy_abort(e); 10426 return 0; 10427 } catch(Exception e) { assert(0); } 10428 } 10429 } 10430 10431 void sdpy_abort(Throwable e) nothrow { 10432 try 10433 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 10434 catch(Exception e) 10435 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 10436 ExitProcess(1); 10437 } 10438 10439 mixin template NativeScreenPainterImplementation() { 10440 HDC hdc; 10441 HWND hwnd; 10442 //HDC windowHdc; 10443 HBITMAP oldBmp; 10444 10445 void create(NativeWindowHandle window) { 10446 hwnd = window; 10447 10448 if(auto sw = cast(SimpleWindow) this.window) { 10449 // drawing on a window, double buffer 10450 auto windowHdc = GetDC(hwnd); 10451 10452 auto buffer = sw.impl.buffer; 10453 if(buffer is null) { 10454 hdc = windowHdc; 10455 windowDc = true; 10456 } else { 10457 hdc = CreateCompatibleDC(windowHdc); 10458 10459 ReleaseDC(hwnd, windowHdc); 10460 10461 oldBmp = SelectObject(hdc, buffer); 10462 } 10463 } else { 10464 // drawing on something else, draw directly 10465 hdc = CreateCompatibleDC(null); 10466 SelectObject(hdc, window); 10467 10468 } 10469 10470 // X doesn't draw a text background, so neither should we 10471 SetBkMode(hdc, TRANSPARENT); 10472 10473 ensureDefaultFontLoaded(); 10474 10475 if(defaultGuiFont) { 10476 SelectObject(hdc, defaultGuiFont); 10477 // DeleteObject(defaultGuiFont); 10478 } 10479 } 10480 10481 static HFONT defaultGuiFont; 10482 static void ensureDefaultFontLoaded() { 10483 static bool triedDefaultGuiFont = false; 10484 if(!triedDefaultGuiFont) { 10485 NONCLIENTMETRICS params; 10486 params.cbSize = params.sizeof; 10487 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 10488 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 10489 } 10490 triedDefaultGuiFont = true; 10491 } 10492 } 10493 10494 void setFont(OperatingSystemFont font) { 10495 if(font && font.font) { 10496 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 10497 // error... how to handle tho? 10498 } 10499 } 10500 else if(defaultGuiFont) 10501 SelectObject(hdc, defaultGuiFont); 10502 } 10503 10504 arsd.color.Rectangle _clipRectangle; 10505 10506 void setClipRectangle(int x, int y, int width, int height) { 10507 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 10508 10509 if(width == 0 || height == 0) { 10510 SelectClipRgn(hdc, null); 10511 } else { 10512 auto region = CreateRectRgn(x, y, x + width, y + height); 10513 SelectClipRgn(hdc, region); 10514 DeleteObject(region); 10515 } 10516 } 10517 10518 10519 // just because we can on Windows... 10520 //void create(Image image); 10521 10522 void dispose() { 10523 // FIXME: this.window.width/height is probably wrong 10524 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 10525 // ReleaseDC(hwnd, windowHdc); 10526 10527 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 10528 if(cast(SimpleWindow) this.window) 10529 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 10530 10531 if(originalPen !is null) 10532 SelectObject(hdc, originalPen); 10533 if(currentPen !is null) 10534 DeleteObject(currentPen); 10535 if(originalBrush !is null) 10536 SelectObject(hdc, originalBrush); 10537 if(currentBrush !is null) 10538 DeleteObject(currentBrush); 10539 10540 SelectObject(hdc, oldBmp); 10541 10542 if(windowDc) 10543 ReleaseDC(hwnd, hdc); 10544 else 10545 DeleteDC(hdc); 10546 10547 if(window.paintingFinishedDg !is null) 10548 window.paintingFinishedDg()(); 10549 } 10550 10551 bool windowDc; 10552 HPEN originalPen; 10553 HPEN currentPen; 10554 10555 Pen _activePen; 10556 10557 @property void pen(Pen p) { 10558 _activePen = p; 10559 10560 HPEN pen; 10561 if(p.color.a == 0) { 10562 pen = GetStockObject(NULL_PEN); 10563 } else { 10564 int style = PS_SOLID; 10565 final switch(p.style) { 10566 case Pen.Style.Solid: 10567 style = PS_SOLID; 10568 break; 10569 case Pen.Style.Dashed: 10570 style = PS_DASH; 10571 break; 10572 case Pen.Style.Dotted: 10573 style = PS_DOT; 10574 break; 10575 } 10576 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 10577 } 10578 auto orig = SelectObject(hdc, pen); 10579 if(originalPen is null) 10580 originalPen = orig; 10581 10582 if(currentPen !is null) 10583 DeleteObject(currentPen); 10584 10585 currentPen = pen; 10586 10587 // the outline is like a foreground since it's done that way on X 10588 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 10589 10590 } 10591 10592 @property void rasterOp(RasterOp op) { 10593 int mode; 10594 final switch(op) { 10595 case RasterOp.normal: 10596 mode = R2_COPYPEN; 10597 break; 10598 case RasterOp.xor: 10599 mode = R2_XORPEN; 10600 break; 10601 } 10602 SetROP2(hdc, mode); 10603 } 10604 10605 HBRUSH originalBrush; 10606 HBRUSH currentBrush; 10607 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 10608 @property void fillColor(Color c) { 10609 if(c == _fillColor) 10610 return; 10611 _fillColor = c; 10612 HBRUSH brush; 10613 if(c.a == 0) { 10614 brush = GetStockObject(HOLLOW_BRUSH); 10615 } else { 10616 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 10617 } 10618 auto orig = SelectObject(hdc, brush); 10619 if(originalBrush is null) 10620 originalBrush = orig; 10621 10622 if(currentBrush !is null) 10623 DeleteObject(currentBrush); 10624 10625 currentBrush = brush; 10626 10627 // background color is NOT set because X doesn't draw text backgrounds 10628 // SetBkColor(hdc, RGB(255, 255, 255)); 10629 } 10630 10631 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 10632 BITMAP bm; 10633 10634 HDC hdcMem = CreateCompatibleDC(hdc); 10635 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 10636 10637 GetObject(i.handle, bm.sizeof, &bm); 10638 10639 // or should I AlphaBlend!??!?! 10640 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 10641 10642 SelectObject(hdcMem, hbmOld); 10643 DeleteDC(hdcMem); 10644 } 10645 10646 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 10647 BITMAP bm; 10648 10649 HDC hdcMem = CreateCompatibleDC(hdc); 10650 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 10651 10652 GetObject(s.handle, bm.sizeof, &bm); 10653 10654 version(CRuntime_DigitalMars) goto noalpha; 10655 10656 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 10657 if(s.enableAlpha) { 10658 auto dw = w ? w : bm.bmWidth; 10659 auto dh = h ? h : bm.bmHeight; 10660 BLENDFUNCTION bf; 10661 bf.BlendOp = AC_SRC_OVER; 10662 bf.SourceConstantAlpha = 255; 10663 bf.AlphaFormat = AC_SRC_ALPHA; 10664 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 10665 } else { 10666 noalpha: 10667 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 10668 } 10669 10670 SelectObject(hdcMem, hbmOld); 10671 DeleteDC(hdcMem); 10672 } 10673 10674 Size textSize(scope const(char)[] text) { 10675 bool dummyX; 10676 if(text.length == 0) { 10677 text = " "; 10678 dummyX = true; 10679 } 10680 RECT rect; 10681 WCharzBuffer buffer = WCharzBuffer(text); 10682 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 10683 return Size(dummyX ? 0 : rect.right, rect.bottom); 10684 } 10685 10686 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 10687 if(text.length && text[$-1] == '\n') 10688 text = text[0 .. $-1]; // tailing newlines are weird on windows... 10689 if(text.length && text[$-1] == '\r') 10690 text = text[0 .. $-1]; 10691 10692 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 10693 if(x2 == 0 && y2 == 0) { 10694 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 10695 } else { 10696 RECT rect; 10697 rect.left = x; 10698 rect.top = y; 10699 rect.right = x2; 10700 rect.bottom = y2; 10701 10702 uint mode = DT_LEFT; 10703 if(alignment & TextAlignment.Right) 10704 mode = DT_RIGHT; 10705 else if(alignment & TextAlignment.Center) 10706 mode = DT_CENTER; 10707 10708 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 10709 if(alignment & TextAlignment.VerticalCenter) 10710 mode |= DT_VCENTER | DT_SINGLELINE; 10711 10712 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 10713 } 10714 10715 /* 10716 uint mode; 10717 10718 if(alignment & TextAlignment.Center) 10719 mode = TA_CENTER; 10720 10721 SetTextAlign(hdc, mode); 10722 */ 10723 } 10724 10725 int fontHeight() { 10726 TEXTMETRIC metric; 10727 if(GetTextMetricsW(hdc, &metric)) { 10728 return metric.tmHeight; 10729 } 10730 10731 return 16; // idk just guessing here, maybe we should throw 10732 } 10733 10734 void drawPixel(int x, int y) { 10735 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 10736 } 10737 10738 // The basic shapes, outlined 10739 10740 void drawLine(int x1, int y1, int x2, int y2) { 10741 MoveToEx(hdc, x1, y1, null); 10742 LineTo(hdc, x2, y2); 10743 } 10744 10745 void drawRectangle(int x, int y, int width, int height) { 10746 // FIXME: with a wider pen this might not draw quite right. im not sure. 10747 gdi.Rectangle(hdc, x, y, x + width, y + height); 10748 } 10749 10750 /// Arguments are the points of the bounding rectangle 10751 void drawEllipse(int x1, int y1, int x2, int y2) { 10752 Ellipse(hdc, x1, y1, x2, y2); 10753 } 10754 10755 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 10756 if((start % (360*64)) == (finish % (360*64))) 10757 drawEllipse(x1, y1, x1 + width, y1 + height); 10758 else { 10759 import core.stdc.math; 10760 float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323; 10761 float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323; 10762 10763 auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2); 10764 auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2); 10765 auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2); 10766 auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2); 10767 10768 if(_activePen.color.a) 10769 Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 10770 if(_fillColor.a) 10771 Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 10772 } 10773 } 10774 10775 void drawPolygon(Point[] vertexes) { 10776 POINT[] points; 10777 points.length = vertexes.length; 10778 10779 foreach(i, p; vertexes) { 10780 points[i].x = p.x; 10781 points[i].y = p.y; 10782 } 10783 10784 Polygon(hdc, points.ptr, cast(int) points.length); 10785 } 10786 } 10787 10788 10789 // Mix this into the SimpleWindow class 10790 mixin template NativeSimpleWindowImplementation() { 10791 int curHidden = 0; // counter 10792 __gshared static bool[string] knownWinClasses; 10793 static bool altPressed = false; 10794 10795 HANDLE oldCursor; 10796 10797 void hideCursor () { 10798 if(curHidden == 0) 10799 oldCursor = SetCursor(null); 10800 ++curHidden; 10801 } 10802 10803 void showCursor () { 10804 --curHidden; 10805 if(curHidden == 0) { 10806 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 10807 } 10808 } 10809 10810 10811 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 10812 10813 void setMinSize (int minwidth, int minheight) { 10814 minWidth = minwidth; 10815 minHeight = minheight; 10816 } 10817 void setMaxSize (int maxwidth, int maxheight) { 10818 maxWidth = maxwidth; 10819 maxHeight = maxheight; 10820 } 10821 10822 // FIXME i'm not sure that Windows has this functionality 10823 // though it is nonessential anyway. 10824 void setResizeGranularity (int granx, int grany) {} 10825 10826 ScreenPainter getPainter() { 10827 return ScreenPainter(this, hwnd); 10828 } 10829 10830 HBITMAP buffer; 10831 10832 void setTitle(string title) { 10833 WCharzBuffer bfr = WCharzBuffer(title); 10834 SetWindowTextW(hwnd, bfr.ptr); 10835 } 10836 10837 string getTitle() { 10838 auto len = GetWindowTextLengthW(hwnd); 10839 if (!len) 10840 return null; 10841 wchar[256] tmpBuffer; 10842 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 10843 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 10844 auto str = buffer[0 .. len2]; 10845 return makeUtf8StringFromWindowsString(str); 10846 } 10847 10848 void move(int x, int y) { 10849 RECT rect; 10850 GetWindowRect(hwnd, &rect); 10851 // move it while maintaining the same size... 10852 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 10853 } 10854 10855 void resize(int w, int h) { 10856 RECT rect; 10857 GetWindowRect(hwnd, &rect); 10858 10859 RECT client; 10860 GetClientRect(hwnd, &client); 10861 10862 rect.right = rect.right - client.right + w; 10863 rect.bottom = rect.bottom - client.bottom + h; 10864 10865 // same position, new size for the client rectangle 10866 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 10867 10868 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 10869 } 10870 10871 void moveResize (int x, int y, int w, int h) { 10872 // what's given is the client rectangle, we need to adjust 10873 10874 RECT rect; 10875 rect.left = x; 10876 rect.top = y; 10877 rect.right = w + x; 10878 rect.bottom = h + y; 10879 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 10880 throw new Exception("AdjustWindowRect"); 10881 10882 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 10883 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 10884 if (windowResized !is null) windowResized(w, h); 10885 } 10886 10887 version(without_opengl) {} else { 10888 HGLRC ghRC; 10889 HDC ghDC; 10890 } 10891 10892 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 10893 string cnamec; 10894 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 10895 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 10896 cnamec = "DSimpleWindow"; 10897 } else { 10898 cnamec = sdpyWindowClass; 10899 } 10900 10901 WCharzBuffer cn = WCharzBuffer(cnamec); 10902 10903 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 10904 10905 if(cnamec !in knownWinClasses) { 10906 WNDCLASSEX wc; 10907 10908 // FIXME: I might be able to use cbWndExtra to hold the pointer back 10909 // to the object. Maybe. 10910 wc.cbSize = wc.sizeof; 10911 wc.cbClsExtra = 0; 10912 wc.cbWndExtra = 0; 10913 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 10914 wc.hCursor = LoadCursorW(null, IDC_ARROW); 10915 wc.hIcon = LoadIcon(hInstance, null); 10916 wc.hInstance = hInstance; 10917 wc.lpfnWndProc = &WndProc; 10918 wc.lpszClassName = cn.ptr; 10919 wc.hIconSm = null; 10920 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 10921 if(!RegisterClassExW(&wc)) 10922 throw new WindowsApiException("RegisterClassExW"); 10923 knownWinClasses[cnamec] = true; 10924 } 10925 10926 int style; 10927 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 10928 10929 // FIXME: windowType and customizationFlags 10930 final switch(windowType) { 10931 case WindowTypes.normal: 10932 style = WS_OVERLAPPEDWINDOW; 10933 break; 10934 case WindowTypes.undecorated: 10935 style = WS_POPUP | WS_SYSMENU; 10936 break; 10937 case WindowTypes.eventOnly: 10938 _hidden = true; 10939 break; 10940 case WindowTypes.dropdownMenu: 10941 case WindowTypes.popupMenu: 10942 case WindowTypes.notification: 10943 style = WS_POPUP; 10944 flags |= WS_EX_NOACTIVATE; 10945 break; 10946 case WindowTypes.nestedChild: 10947 style = WS_CHILD; 10948 break; 10949 } 10950 10951 if ((customizationFlags & WindowFlags.extraComposite) != 0) 10952 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 10953 10954 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 10955 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 10956 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 10957 10958 if ((customizationFlags & WindowFlags.extraComposite) != 0) 10959 setOpacity(255); 10960 10961 SimpleWindow.nativeMapping[hwnd] = this; 10962 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 10963 10964 if(windowType == WindowTypes.eventOnly) 10965 return; 10966 10967 HDC hdc = GetDC(hwnd); 10968 10969 10970 version(without_opengl) {} 10971 else { 10972 if(opengl == OpenGlOptions.yes) { 10973 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 10974 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 10975 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 10976 ghDC = hdc; 10977 PIXELFORMATDESCRIPTOR pfd; 10978 10979 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 10980 pfd.nVersion = 1; 10981 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 10982 pfd.dwLayerMask = PFD_MAIN_PLANE; 10983 pfd.iPixelType = PFD_TYPE_RGBA; 10984 pfd.cColorBits = 24; 10985 pfd.cDepthBits = 24; 10986 pfd.cAccumBits = 0; 10987 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 10988 10989 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 10990 10991 if (pixelformat == 0) 10992 throw new WindowsApiException("ChoosePixelFormat"); 10993 10994 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 10995 throw new WindowsApiException("SetPixelFormat"); 10996 10997 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 10998 // windoze is idiotic: we have to have OpenGL context to get function addresses 10999 // so we will create fake context to get that stupid address 11000 auto tmpcc = wglCreateContext(ghDC); 11001 if (tmpcc !is null) { 11002 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 11003 wglMakeCurrent(ghDC, tmpcc); 11004 wglInitOtherFunctions(); 11005 } 11006 } 11007 11008 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 11009 int[9] contextAttribs = [ 11010 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 11011 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 11012 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 11013 // for modern context, set "forward compatibility" flag too 11014 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 11015 0/*None*/, 11016 ]; 11017 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 11018 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 11019 // activate fallback mode 11020 // 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; 11021 ghRC = wglCreateContext(ghDC); 11022 } 11023 if (ghRC is null) 11024 throw new WindowsApiException("wglCreateContextAttribsARB"); 11025 } else { 11026 // try to do at least something 11027 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 11028 sdpyOpenGLContextVersion = 0; 11029 ghRC = wglCreateContext(ghDC); 11030 } 11031 if (ghRC is null) 11032 throw new WindowsApiException("wglCreateContext"); 11033 } 11034 } 11035 } 11036 11037 if(opengl == OpenGlOptions.no) { 11038 buffer = CreateCompatibleBitmap(hdc, width, height); 11039 11040 auto hdcBmp = CreateCompatibleDC(hdc); 11041 // make sure it's filled with a blank slate 11042 auto oldBmp = SelectObject(hdcBmp, buffer); 11043 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 11044 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 11045 gdi.Rectangle(hdcBmp, 0, 0, width, height); 11046 SelectObject(hdcBmp, oldBmp); 11047 SelectObject(hdcBmp, oldBrush); 11048 SelectObject(hdcBmp, oldPen); 11049 DeleteDC(hdcBmp); 11050 11051 bmpWidth = width; 11052 bmpHeight = height; 11053 11054 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 11055 } 11056 11057 // We want the window's client area to match the image size 11058 RECT rcClient, rcWindow; 11059 POINT ptDiff; 11060 GetClientRect(hwnd, &rcClient); 11061 GetWindowRect(hwnd, &rcWindow); 11062 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 11063 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 11064 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 11065 11066 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 11067 ShowWindow(hwnd, SW_SHOWNORMAL); 11068 } else { 11069 _hidden = true; 11070 } 11071 this._visibleForTheFirstTimeCalled = false; // hack! 11072 } 11073 11074 11075 void dispose() { 11076 if(buffer) 11077 DeleteObject(buffer); 11078 } 11079 11080 void closeWindow() { 11081 DestroyWindow(hwnd); 11082 } 11083 11084 bool setOpacity(ubyte alpha) { 11085 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 11086 } 11087 11088 HANDLE currentCursor; 11089 11090 // returns zero if it recognized the event 11091 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 11092 MouseEvent mouse; 11093 11094 void mouseEvent(bool isScreen, ulong mods) { 11095 auto x = LOWORD(lParam); 11096 auto y = HIWORD(lParam); 11097 if(isScreen) { 11098 POINT p; 11099 p.x = x; 11100 p.y = y; 11101 ScreenToClient(hwnd, &p); 11102 x = cast(ushort) p.x; 11103 y = cast(ushort) p.y; 11104 } 11105 mouse.x = x + offsetX; 11106 mouse.y = y + offsetY; 11107 11108 wind.mdx(mouse); 11109 mouse.modifierState = cast(int) mods; 11110 mouse.window = wind; 11111 11112 if(wind.handleMouseEvent) 11113 wind.handleMouseEvent(mouse); 11114 } 11115 11116 switch(msg) { 11117 case WM_GETMINMAXINFO: 11118 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 11119 11120 if(wind.minWidth > 0) { 11121 RECT rect; 11122 rect.left = 100; 11123 rect.top = 100; 11124 rect.right = wind.minWidth + 100; 11125 rect.bottom = wind.minHeight + 100; 11126 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11127 throw new WindowsApiException("AdjustWindowRect"); 11128 11129 mmi.ptMinTrackSize.x = rect.right - rect.left; 11130 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 11131 } 11132 11133 if(wind.maxWidth < int.max) { 11134 RECT rect; 11135 rect.left = 100; 11136 rect.top = 100; 11137 rect.right = wind.maxWidth + 100; 11138 rect.bottom = wind.maxHeight + 100; 11139 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11140 throw new WindowsApiException("AdjustWindowRect"); 11141 11142 mmi.ptMaxTrackSize.x = rect.right - rect.left; 11143 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 11144 } 11145 break; 11146 case WM_CHAR: 11147 wchar c = cast(wchar) wParam; 11148 if(wind.handleCharEvent) 11149 wind.handleCharEvent(cast(dchar) c); 11150 break; 11151 case WM_SETFOCUS: 11152 case WM_KILLFOCUS: 11153 wind._focused = (msg == WM_SETFOCUS); 11154 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 11155 if(wind.onFocusChange) 11156 wind.onFocusChange(msg == WM_SETFOCUS); 11157 break; 11158 case WM_SYSKEYDOWN: 11159 goto case; 11160 case WM_SYSKEYUP: 11161 if(lParam & (1 << 29)) { 11162 goto case; 11163 } else { 11164 // no window has keyboard focus 11165 goto default; 11166 } 11167 case WM_KEYDOWN: 11168 case WM_KEYUP: 11169 KeyEvent ev; 11170 ev.key = cast(Key) wParam; 11171 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 11172 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 11173 11174 ev.hardwareCode = (lParam & 0xff0000) >> 16; 11175 11176 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 11177 ev.modifierState |= ModifierState.shift; 11178 //k8: this doesn't work; thanks for nothing, windows 11179 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 11180 ev.modifierState |= ModifierState.alt;*/ 11181 if (wParam == 0x12) altPressed = (msg == WM_SYSKEYDOWN); 11182 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11183 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 11184 ev.modifierState |= ModifierState.ctrl; 11185 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 11186 ev.modifierState |= ModifierState.windows; 11187 if(GetKeyState(Key.NumLock)) 11188 ev.modifierState |= ModifierState.numLock; 11189 if(GetKeyState(Key.CapsLock)) 11190 ev.modifierState |= ModifierState.capsLock; 11191 11192 /+ 11193 // we always want to send the character too, so let's convert it 11194 ubyte[256] state; 11195 wchar[16] buffer; 11196 GetKeyboardState(state.ptr); 11197 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 11198 11199 foreach(dchar d; buffer) { 11200 ev.character = d; 11201 break; 11202 } 11203 +/ 11204 11205 ev.window = wind; 11206 if(wind.handleKeyEvent) 11207 wind.handleKeyEvent(ev); 11208 break; 11209 case 0x020a /*WM_MOUSEWHEEL*/: 11210 // send click 11211 mouse.type = cast(MouseEventType) 1; 11212 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 11213 mouseEvent(true, LOWORD(wParam)); 11214 11215 // also send release 11216 mouse.type = cast(MouseEventType) 2; 11217 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 11218 mouseEvent(true, LOWORD(wParam)); 11219 break; 11220 case WM_MOUSEMOVE: 11221 mouse.type = cast(MouseEventType) 0; 11222 mouseEvent(false, wParam); 11223 break; 11224 case WM_LBUTTONDOWN: 11225 case WM_LBUTTONDBLCLK: 11226 mouse.type = cast(MouseEventType) 1; 11227 mouse.button = MouseButton.left; 11228 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 11229 mouseEvent(false, wParam); 11230 break; 11231 case WM_LBUTTONUP: 11232 mouse.type = cast(MouseEventType) 2; 11233 mouse.button = MouseButton.left; 11234 mouseEvent(false, wParam); 11235 break; 11236 case WM_RBUTTONDOWN: 11237 case WM_RBUTTONDBLCLK: 11238 mouse.type = cast(MouseEventType) 1; 11239 mouse.button = MouseButton.right; 11240 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 11241 mouseEvent(false, wParam); 11242 break; 11243 case WM_RBUTTONUP: 11244 mouse.type = cast(MouseEventType) 2; 11245 mouse.button = MouseButton.right; 11246 mouseEvent(false, wParam); 11247 break; 11248 case WM_MBUTTONDOWN: 11249 case WM_MBUTTONDBLCLK: 11250 mouse.type = cast(MouseEventType) 1; 11251 mouse.button = MouseButton.middle; 11252 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 11253 mouseEvent(false, wParam); 11254 break; 11255 case WM_MBUTTONUP: 11256 mouse.type = cast(MouseEventType) 2; 11257 mouse.button = MouseButton.middle; 11258 mouseEvent(false, wParam); 11259 break; 11260 case WM_XBUTTONDOWN: 11261 case WM_XBUTTONDBLCLK: 11262 mouse.type = cast(MouseEventType) 1; 11263 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 11264 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 11265 mouseEvent(false, wParam); 11266 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 11267 case WM_XBUTTONUP: 11268 mouse.type = cast(MouseEventType) 2; 11269 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 11270 mouseEvent(false, wParam); 11271 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 11272 11273 default: return 1; 11274 } 11275 return 0; 11276 } 11277 11278 HWND hwnd; 11279 private int oldWidth; 11280 private int oldHeight; 11281 private bool inSizeMove; 11282 11283 /++ 11284 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. 11285 11286 History: 11287 Added November 23, 2021 11288 11289 Not fully stable, may be moved out of the impl struct. 11290 +/ 11291 bool doLiveResizing; 11292 11293 private int bmpWidth; 11294 private int bmpHeight; 11295 11296 // the extern(Windows) wndproc should just forward to this 11297 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 11298 assert(hwnd is this.hwnd); 11299 11300 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 11301 switch(msg) { 11302 case WM_SETCURSOR: 11303 if(cast(HWND) wParam !is hwnd) 11304 return 0; // further processing elsewhere 11305 11306 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 11307 SetCursor(this.curHidden > 0 ? null : currentCursor); 11308 return 1; 11309 } else { 11310 return DefWindowProc(hwnd, msg, wParam, lParam); 11311 } 11312 //break; 11313 11314 case WM_CLOSE: 11315 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 11316 break; 11317 case WM_DESTROY: 11318 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 11319 SimpleWindow.nativeMapping.remove(hwnd); 11320 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 11321 11322 bool anyImportant = false; 11323 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 11324 if(w.beingOpenKeepsAppOpen) { 11325 anyImportant = true; 11326 break; 11327 } 11328 if(!anyImportant) { 11329 PostQuitMessage(0); 11330 } 11331 break; 11332 case 0x02E0 /*WM_DPICHANGED*/: 11333 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 11334 11335 RECT* prcNewWindow = cast(RECT*)lParam; 11336 // docs say this is the recommended position and we should honor it 11337 SetWindowPos(hwnd, 11338 null, 11339 prcNewWindow.left, 11340 prcNewWindow.top, 11341 prcNewWindow.right - prcNewWindow.left, 11342 prcNewWindow.bottom - prcNewWindow.top, 11343 SWP_NOZORDER | SWP_NOACTIVATE); 11344 11345 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 11346 // im not sure it is completely correct 11347 // but without it the tabs and such do look weird as things change. 11348 { 11349 LOGFONT lfText; 11350 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 11351 HFONT hFontNew = CreateFontIndirect(&lfText); 11352 if (hFontNew) 11353 { 11354 //DeleteObject(hFontOld); 11355 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 11356 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 11357 return TRUE; 11358 } 11359 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 11360 } 11361 } 11362 11363 if(this.onDpiChanged) 11364 this.onDpiChanged(); 11365 break; 11366 case WM_SIZE: 11367 if(wParam == 1 /* SIZE_MINIMIZED */) 11368 break; 11369 _width = LOWORD(lParam); 11370 _height = HIWORD(lParam); 11371 11372 // I want to avoid tearing in the windows (my code is inefficient 11373 // so this is a hack around that) so while sizing, we don't trigger, 11374 // but we do want to trigger on events like mazimize. 11375 if(!inSizeMove || doLiveResizing) 11376 goto size_changed; 11377 break; 11378 /+ 11379 case WM_SIZING: 11380 import std.stdio; writeln("size"); 11381 break; 11382 +/ 11383 // I don't like the tearing I get when redrawing on WM_SIZE 11384 // (I know there's other ways to fix that but I don't like that behavior anyway) 11385 // so instead it is going to redraw only at the end of a size. 11386 case 0x0231: /* WM_ENTERSIZEMOVE */ 11387 oldWidth = this.width; 11388 oldHeight = this.height; 11389 inSizeMove = true; 11390 break; 11391 case 0x0232: /* WM_EXITSIZEMOVE */ 11392 inSizeMove = false; 11393 11394 size_changed: 11395 11396 // nothing relevant changed, don't bother redrawing 11397 if(oldWidth == width && oldHeight == height) 11398 break; 11399 11400 // note: OpenGL windows don't use a backing bmp, so no need to change them 11401 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 11402 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 11403 // gotta get the double buffer bmp to match the window 11404 // FIXME: could this be more efficient? it never relinquishes a large bitmap 11405 if(width > bmpWidth || height > bmpHeight) { 11406 auto hdc = GetDC(hwnd); 11407 auto oldBuffer = buffer; 11408 buffer = CreateCompatibleBitmap(hdc, width, height); 11409 11410 auto hdcBmp = CreateCompatibleDC(hdc); 11411 auto oldBmp = SelectObject(hdcBmp, buffer); 11412 11413 auto hdcOldBmp = CreateCompatibleDC(hdc); 11414 auto oldOldBmp = SelectObject(hdcOldBmp, oldBmp); 11415 11416 BitBlt(hdcBmp, 0, 0, width, height, hdcOldBmp, oldWidth, oldHeight, SRCCOPY); 11417 11418 bmpWidth = width; 11419 bmpHeight = height; 11420 11421 SelectObject(hdcOldBmp, oldOldBmp); 11422 DeleteDC(hdcOldBmp); 11423 11424 SelectObject(hdcBmp, oldBmp); 11425 DeleteDC(hdcBmp); 11426 11427 ReleaseDC(hwnd, hdc); 11428 11429 DeleteObject(oldBuffer); 11430 } 11431 } 11432 11433 version(without_opengl) {} else 11434 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 11435 glViewport(0, 0, width, height); 11436 } 11437 11438 if(windowResized !is null) 11439 windowResized(width, height); 11440 break; 11441 case WM_ERASEBKGND: 11442 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 11443 if (!this._visibleForTheFirstTimeCalled) { 11444 this._visibleForTheFirstTimeCalled = true; 11445 if (this.visibleForTheFirstTime !is null) { 11446 version(without_opengl) {} else { 11447 if(openglMode == OpenGlOptions.yes) { 11448 this.setAsCurrentOpenGlContextNT(); 11449 glViewport(0, 0, width, height); 11450 } 11451 } 11452 this.visibleForTheFirstTime(); 11453 } 11454 } 11455 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 11456 version(without_opengl) {} else { 11457 if (openglMode == OpenGlOptions.yes) return 1; 11458 } 11459 // call windows default handler, so it can paint standard controls 11460 goto default; 11461 case WM_CTLCOLORBTN: 11462 case WM_CTLCOLORSTATIC: 11463 SetBkMode(cast(HDC) wParam, TRANSPARENT); 11464 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 11465 GetSysColorBrush(COLOR_3DFACE); 11466 //break; 11467 case WM_SHOWWINDOW: 11468 this._visible = (wParam != 0); 11469 if (!this._visibleForTheFirstTimeCalled && this._visible) { 11470 this._visibleForTheFirstTimeCalled = true; 11471 if (this.visibleForTheFirstTime !is null) { 11472 version(without_opengl) {} else { 11473 if(openglMode == OpenGlOptions.yes) { 11474 this.setAsCurrentOpenGlContextNT(); 11475 glViewport(0, 0, width, height); 11476 } 11477 } 11478 this.visibleForTheFirstTime(); 11479 } 11480 } 11481 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 11482 break; 11483 case WM_PAINT: { 11484 if (!this._visibleForTheFirstTimeCalled) { 11485 this._visibleForTheFirstTimeCalled = true; 11486 if (this.visibleForTheFirstTime !is null) { 11487 version(without_opengl) {} else { 11488 if(openglMode == OpenGlOptions.yes) { 11489 this.setAsCurrentOpenGlContextNT(); 11490 glViewport(0, 0, width, height); 11491 } 11492 } 11493 this.visibleForTheFirstTime(); 11494 } 11495 } 11496 11497 BITMAP bm; 11498 PAINTSTRUCT ps; 11499 11500 HDC hdc = BeginPaint(hwnd, &ps); 11501 11502 if(openglMode == OpenGlOptions.no) { 11503 11504 HDC hdcMem = CreateCompatibleDC(hdc); 11505 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 11506 11507 GetObject(buffer, bm.sizeof, &bm); 11508 11509 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 11510 if(resizability == Resizability.automaticallyScaleIfPossible) 11511 StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 11512 else 11513 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 11514 11515 SelectObject(hdcMem, hbmOld); 11516 DeleteDC(hdcMem); 11517 EndPaint(hwnd, &ps); 11518 } else { 11519 EndPaint(hwnd, &ps); 11520 version(without_opengl) {} else 11521 redrawOpenGlSceneNow(); 11522 } 11523 } break; 11524 default: 11525 return DefWindowProc(hwnd, msg, wParam, lParam); 11526 } 11527 return 0; 11528 11529 } 11530 } 11531 11532 mixin template NativeImageImplementation() { 11533 HBITMAP handle; 11534 ubyte* rawData; 11535 11536 final: 11537 11538 Color getPixel(int x, int y) { 11539 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 11540 // remember, bmps are upside down 11541 auto offset = itemsPerLine * (height - y - 1) + x * 3; 11542 11543 Color c; 11544 if(enableAlpha) 11545 c.a = rawData[offset + 3]; 11546 else 11547 c.a = 255; 11548 c.b = rawData[offset + 0]; 11549 c.g = rawData[offset + 1]; 11550 c.r = rawData[offset + 2]; 11551 c.unPremultiply(); 11552 return c; 11553 } 11554 11555 void setPixel(int x, int y, Color c) { 11556 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 11557 // remember, bmps are upside down 11558 auto offset = itemsPerLine * (height - y - 1) + x * 3; 11559 11560 if(enableAlpha) 11561 c.premultiply(); 11562 11563 rawData[offset + 0] = c.b; 11564 rawData[offset + 1] = c.g; 11565 rawData[offset + 2] = c.r; 11566 if(enableAlpha) 11567 rawData[offset + 3] = c.a; 11568 } 11569 11570 void convertToRgbaBytes(ubyte[] where) { 11571 assert(where.length == this.width * this.height * 4); 11572 11573 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 11574 int idx = 0; 11575 int offset = itemsPerLine * (height - 1); 11576 // remember, bmps are upside down 11577 for(int y = height - 1; y >= 0; y--) { 11578 auto offsetStart = offset; 11579 for(int x = 0; x < width; x++) { 11580 where[idx + 0] = rawData[offset + 2]; // r 11581 where[idx + 1] = rawData[offset + 1]; // g 11582 where[idx + 2] = rawData[offset + 0]; // b 11583 if(enableAlpha) { 11584 where[idx + 3] = rawData[offset + 3]; // a 11585 unPremultiplyRgba(where[idx .. idx + 4]); 11586 offset++; 11587 } else 11588 where[idx + 3] = 255; // a 11589 idx += 4; 11590 offset += 3; 11591 } 11592 11593 offset = offsetStart - itemsPerLine; 11594 } 11595 } 11596 11597 void setFromRgbaBytes(in ubyte[] what) { 11598 assert(what.length == this.width * this.height * 4); 11599 11600 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 11601 int idx = 0; 11602 int offset = itemsPerLine * (height - 1); 11603 // remember, bmps are upside down 11604 for(int y = height - 1; y >= 0; y--) { 11605 auto offsetStart = offset; 11606 for(int x = 0; x < width; x++) { 11607 if(enableAlpha) { 11608 auto a = what[idx + 3]; 11609 11610 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 11611 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 11612 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 11613 rawData[offset + 3] = a; // a 11614 //premultiplyBgra(rawData[offset .. offset + 4]); 11615 offset++; 11616 } else { 11617 rawData[offset + 2] = what[idx + 0]; // r 11618 rawData[offset + 1] = what[idx + 1]; // g 11619 rawData[offset + 0] = what[idx + 2]; // b 11620 } 11621 idx += 4; 11622 offset += 3; 11623 } 11624 11625 offset = offsetStart - itemsPerLine; 11626 } 11627 } 11628 11629 11630 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 11631 BITMAPINFO infoheader; 11632 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 11633 infoheader.bmiHeader.biWidth = width; 11634 infoheader.bmiHeader.biHeight = height; 11635 infoheader.bmiHeader.biPlanes = 1; 11636 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 11637 infoheader.bmiHeader.biCompression = BI_RGB; 11638 11639 handle = CreateDIBSection( 11640 null, 11641 &infoheader, 11642 DIB_RGB_COLORS, 11643 cast(void**) &rawData, 11644 null, 11645 0); 11646 if(handle is null) 11647 throw new WindowsApiException("create image failed"); 11648 11649 } 11650 11651 void dispose() { 11652 DeleteObject(handle); 11653 } 11654 } 11655 11656 enum KEY_ESCAPE = 27; 11657 } 11658 version(X11) { 11659 /// This is the default font used. You might change this before doing anything else with 11660 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 11661 /// for cross-platform compatibility. 11662 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 11663 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 11664 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 11665 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 11666 11667 alias int delegate(XEvent) NativeEventHandler; 11668 alias Window NativeWindowHandle; 11669 11670 enum KEY_ESCAPE = 9; 11671 11672 mixin template NativeScreenPainterImplementation() { 11673 Display* display; 11674 Drawable d; 11675 Drawable destiny; 11676 11677 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 11678 GC gc; 11679 11680 __gshared bool fontAttempted; 11681 11682 __gshared XFontStruct* defaultfont; 11683 __gshared XFontSet defaultfontset; 11684 11685 XFontStruct* font; 11686 XFontSet fontset; 11687 11688 void create(NativeWindowHandle window) { 11689 this.display = XDisplayConnection.get(); 11690 11691 Drawable buffer = None; 11692 if(auto sw = cast(SimpleWindow) this.window) { 11693 buffer = sw.impl.buffer; 11694 this.destiny = cast(Drawable) window; 11695 } else { 11696 buffer = cast(Drawable) window; 11697 this.destiny = None; 11698 } 11699 11700 this.d = cast(Drawable) buffer; 11701 11702 auto dgc = DefaultGC(display, DefaultScreen(display)); 11703 11704 this.gc = XCreateGC(display, d, 0, null); 11705 11706 XCopyGC(display, dgc, 0xffffffff, this.gc); 11707 11708 ensureDefaultFontLoaded(); 11709 11710 font = defaultfont; 11711 fontset = defaultfontset; 11712 11713 if(font) { 11714 XSetFont(display, gc, font.fid); 11715 } 11716 } 11717 11718 static void ensureDefaultFontLoaded() { 11719 if(!fontAttempted) { 11720 auto display = XDisplayConnection.get; 11721 auto font = XLoadQueryFont(display, xfontstr.ptr); 11722 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 11723 if(font is null) { 11724 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 11725 font = XLoadQueryFont(display, xfontstr.ptr); 11726 } 11727 11728 char** lol; 11729 int lol2; 11730 char* lol3; 11731 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 11732 11733 fontAttempted = true; 11734 11735 defaultfont = font; 11736 defaultfontset = fontset; 11737 } 11738 } 11739 11740 arsd.color.Rectangle _clipRectangle; 11741 void setClipRectangle(int x, int y, int width, int height) { 11742 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11743 if(width == 0 || height == 0) { 11744 XSetClipMask(display, gc, None); 11745 11746 version(with_xft) { 11747 if(xftFont is null || xftDraw is null) 11748 return; 11749 XftDrawSetClip(xftDraw, null); 11750 } 11751 } else { 11752 XRectangle[1] rects; 11753 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 11754 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 11755 11756 version(with_xft) { 11757 if(xftFont is null || xftDraw is null) 11758 return; 11759 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 11760 } 11761 } 11762 } 11763 11764 version(with_xft) { 11765 XftFont* xftFont; 11766 XftDraw* xftDraw; 11767 11768 XftColor xftColor; 11769 11770 void updateXftColor() { 11771 if(xftFont is null) 11772 return; 11773 11774 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 11775 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 11776 11777 XftColorAllocValue( 11778 display, 11779 DefaultVisual(display, DefaultScreen(display)), 11780 DefaultColormap(display, 0), 11781 &colorIn, 11782 &xftColor 11783 ); 11784 } 11785 } 11786 11787 void setFont(OperatingSystemFont font) { 11788 version(with_xft) { 11789 if(font && font.isXft && font.xftFont) 11790 this.xftFont = font.xftFont; 11791 else 11792 this.xftFont = null; 11793 11794 if(this.xftFont) { 11795 if(xftDraw is null) { 11796 xftDraw = XftDrawCreate( 11797 display, 11798 d, 11799 DefaultVisual(display, DefaultScreen(display)), 11800 DefaultColormap(display, 0) 11801 ); 11802 11803 updateXftColor(); 11804 } 11805 11806 return; 11807 } 11808 } 11809 11810 if(font && font.font) { 11811 this.font = font.font; 11812 this.fontset = font.fontset; 11813 XSetFont(display, gc, font.font.fid); 11814 } else { 11815 this.font = defaultfont; 11816 this.fontset = defaultfontset; 11817 } 11818 11819 } 11820 11821 private Picture xrenderPicturePainter; 11822 11823 void dispose() { 11824 this.rasterOp = RasterOp.normal; 11825 11826 if(xrenderPicturePainter) { 11827 XRenderFreePicture(display, xrenderPicturePainter); 11828 xrenderPicturePainter = None; 11829 } 11830 11831 // FIXME: this.window.width/height is probably wrong 11832 11833 // src x,y then dest x, y 11834 if(destiny != None) { 11835 XSetClipMask(display, gc, None); 11836 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 11837 } 11838 11839 XFreeGC(display, gc); 11840 11841 version(with_xft) 11842 if(xftDraw) { 11843 XftDrawDestroy(xftDraw); 11844 xftDraw = null; 11845 } 11846 11847 /+ 11848 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 11849 if(font && font !is defaultfont) { 11850 XFreeFont(display, font); 11851 font = null; 11852 } 11853 if(fontset && fontset !is defaultfontset) { 11854 XFreeFontSet(display, fontset); 11855 fontset = null; 11856 } 11857 +/ 11858 XFlush(display); 11859 11860 if(window.paintingFinishedDg !is null) 11861 window.paintingFinishedDg()(); 11862 } 11863 11864 bool backgroundIsNotTransparent = true; 11865 bool foregroundIsNotTransparent = true; 11866 11867 bool _penInitialized = false; 11868 Pen _activePen; 11869 11870 Color _outlineColor; 11871 Color _fillColor; 11872 11873 @property void pen(Pen p) { 11874 if(_penInitialized && p == _activePen) { 11875 return; 11876 } 11877 _penInitialized = true; 11878 _activePen = p; 11879 _outlineColor = p.color; 11880 11881 int style; 11882 11883 byte dashLength; 11884 11885 final switch(p.style) { 11886 case Pen.Style.Solid: 11887 style = 0 /*LineSolid*/; 11888 break; 11889 case Pen.Style.Dashed: 11890 style = 1 /*LineOnOffDash*/; 11891 dashLength = 4; 11892 break; 11893 case Pen.Style.Dotted: 11894 style = 1 /*LineOnOffDash*/; 11895 dashLength = 1; 11896 break; 11897 } 11898 11899 XSetLineAttributes(display, gc, p.width, style, 0, 0); 11900 if(dashLength) 11901 XSetDashes(display, gc, 0, &dashLength, 1); 11902 11903 if(p.color.a == 0) { 11904 foregroundIsNotTransparent = false; 11905 return; 11906 } 11907 11908 foregroundIsNotTransparent = true; 11909 11910 XSetForeground(display, gc, colorToX(p.color, display)); 11911 11912 version(with_xft) 11913 updateXftColor(); 11914 } 11915 11916 RasterOp _currentRasterOp; 11917 bool _currentRasterOpInitialized = false; 11918 @property void rasterOp(RasterOp op) { 11919 if(_currentRasterOpInitialized && _currentRasterOp == op) 11920 return; 11921 _currentRasterOp = op; 11922 _currentRasterOpInitialized = true; 11923 int mode; 11924 final switch(op) { 11925 case RasterOp.normal: 11926 mode = GXcopy; 11927 break; 11928 case RasterOp.xor: 11929 mode = GXxor; 11930 break; 11931 } 11932 XSetFunction(display, gc, mode); 11933 } 11934 11935 11936 bool _fillColorInitialized = false; 11937 11938 @property void fillColor(Color c) { 11939 if(_fillColorInitialized && _fillColor == c) 11940 return; // already good, no need to waste time calling it 11941 _fillColor = c; 11942 _fillColorInitialized = true; 11943 if(c.a == 0) { 11944 backgroundIsNotTransparent = false; 11945 return; 11946 } 11947 11948 backgroundIsNotTransparent = true; 11949 11950 XSetBackground(display, gc, colorToX(c, display)); 11951 11952 } 11953 11954 void swapColors() { 11955 auto tmp = _fillColor; 11956 fillColor = _outlineColor; 11957 auto newPen = _activePen; 11958 newPen.color = tmp; 11959 pen(newPen); 11960 } 11961 11962 uint colorToX(Color c, Display* display) { 11963 auto visual = DefaultVisual(display, DefaultScreen(display)); 11964 import core.bitop; 11965 uint color = 0; 11966 { 11967 auto startBit = bsf(visual.red_mask); 11968 auto lastBit = bsr(visual.red_mask); 11969 auto r = cast(uint) c.r; 11970 r >>= 7 - (lastBit - startBit); 11971 r <<= startBit; 11972 color |= r; 11973 } 11974 { 11975 auto startBit = bsf(visual.green_mask); 11976 auto lastBit = bsr(visual.green_mask); 11977 auto g = cast(uint) c.g; 11978 g >>= 7 - (lastBit - startBit); 11979 g <<= startBit; 11980 color |= g; 11981 } 11982 { 11983 auto startBit = bsf(visual.blue_mask); 11984 auto lastBit = bsr(visual.blue_mask); 11985 auto b = cast(uint) c.b; 11986 b >>= 7 - (lastBit - startBit); 11987 b <<= startBit; 11988 color |= b; 11989 } 11990 11991 11992 11993 return color; 11994 } 11995 11996 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11997 // source x, source y 11998 if(ix >= i.width) return; 11999 if(iy >= i.height) return; 12000 if(ix + w > i.width) w = i.width - ix; 12001 if(iy + h > i.height) h = i.height - iy; 12002 if(i.usingXshm) 12003 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 12004 else 12005 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 12006 } 12007 12008 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12009 if(s.enableAlpha) { 12010 // the Sprite must be created first, meaning if we're here, XRender is already loaded 12011 if(this.xrenderPicturePainter == None) { 12012 XRenderPictureAttributes attrs; 12013 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 12014 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 12015 } 12016 12017 XRenderComposite( 12018 display, 12019 3, // PicOpOver 12020 s.xrenderPicture, 12021 None, 12022 this.xrenderPicturePainter, 12023 ix, 12024 iy, 12025 0, 12026 0, 12027 x, 12028 y, 12029 w ? w : s.width, 12030 h ? h : s.height 12031 ); 12032 } else { 12033 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 12034 } 12035 } 12036 12037 int fontHeight() { 12038 version(with_xft) 12039 if(xftFont !is null) 12040 return xftFont.height; 12041 if(font) 12042 return font.max_bounds.ascent + font.max_bounds.descent; 12043 return 12; // pretty common default... 12044 } 12045 12046 int textWidth(in char[] line) { 12047 version(with_xft) 12048 if(xftFont) { 12049 if(line.length == 0) 12050 return 0; 12051 XGlyphInfo extents; 12052 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 12053 return extents.width; 12054 } 12055 12056 if(fontset) { 12057 if(line.length == 0) 12058 return 0; 12059 XRectangle rect; 12060 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 12061 12062 return rect.width; 12063 } 12064 12065 if(font) 12066 // FIXME: unicode 12067 return XTextWidth( font, line.ptr, cast(int) line.length); 12068 else 12069 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 12070 } 12071 12072 Size textSize(in char[] text) { 12073 auto maxWidth = 0; 12074 auto lineHeight = fontHeight; 12075 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 12076 foreach(line; text.split('\n')) { 12077 int textWidth = this.textWidth(line); 12078 if(textWidth > maxWidth) 12079 maxWidth = textWidth; 12080 h += lineHeight + 4; 12081 } 12082 return Size(maxWidth, h); 12083 } 12084 12085 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 12086 const(char)[] text; 12087 version(with_xft) 12088 if(xftFont) { 12089 text = originalText; 12090 goto loaded; 12091 } 12092 12093 if(fontset) 12094 text = originalText; 12095 else { 12096 text.reserve(originalText.length); 12097 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 12098 // then strip the rest so there isn't garbage 12099 foreach(dchar ch; originalText) 12100 if(ch < 256) 12101 text ~= cast(ubyte) ch; 12102 else 12103 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 12104 } 12105 loaded: 12106 if(text.length == 0) 12107 return; 12108 12109 // FIXME: should we clip it to the bounding box? 12110 int textHeight = fontHeight; 12111 12112 auto lines = text.split('\n'); 12113 12114 const lineHeight = textHeight; 12115 textHeight *= lines.length; 12116 12117 int cy = y; 12118 12119 if(alignment & TextAlignment.VerticalBottom) { 12120 assert(y2); 12121 auto h = y2 - y; 12122 if(h > textHeight) { 12123 cy += h - textHeight; 12124 cy -= lineHeight / 2; 12125 } 12126 } else if(alignment & TextAlignment.VerticalCenter) { 12127 assert(y2); 12128 auto h = y2 - y; 12129 if(textHeight < h) { 12130 cy += (h - textHeight) / 2; 12131 //cy -= lineHeight / 4; 12132 } 12133 } 12134 12135 foreach(line; text.split('\n')) { 12136 int textWidth = this.textWidth(line); 12137 12138 int px = x, py = cy; 12139 12140 if(alignment & TextAlignment.Center) { 12141 assert(x2); 12142 auto w = x2 - x; 12143 if(w > textWidth) 12144 px += (w - textWidth) / 2; 12145 } else if(alignment & TextAlignment.Right) { 12146 assert(x2); 12147 auto pos = x2 - textWidth; 12148 if(pos > x) 12149 px = pos; 12150 } 12151 12152 version(with_xft) 12153 if(xftFont) { 12154 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 12155 12156 goto carry_on; 12157 } 12158 12159 if(fontset) 12160 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 12161 else 12162 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 12163 carry_on: 12164 cy += lineHeight + 4; 12165 } 12166 } 12167 12168 void drawPixel(int x, int y) { 12169 XDrawPoint(display, d, gc, x, y); 12170 } 12171 12172 // The basic shapes, outlined 12173 12174 void drawLine(int x1, int y1, int x2, int y2) { 12175 if(foregroundIsNotTransparent) 12176 XDrawLine(display, d, gc, x1, y1, x2, y2); 12177 } 12178 12179 void drawRectangle(int x, int y, int width, int height) { 12180 if(backgroundIsNotTransparent) { 12181 swapColors(); 12182 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 12183 swapColors(); 12184 } 12185 // 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 12186 if(foregroundIsNotTransparent) 12187 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 12188 } 12189 12190 /// Arguments are the points of the bounding rectangle 12191 void drawEllipse(int x1, int y1, int x2, int y2) { 12192 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 12193 } 12194 12195 // NOTE: start and finish are in units of degrees * 64 12196 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 12197 if(backgroundIsNotTransparent) { 12198 swapColors(); 12199 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 12200 swapColors(); 12201 } 12202 if(foregroundIsNotTransparent) { 12203 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 12204 // Windows draws the straight lines on the edges too so FIXME sort of 12205 } 12206 } 12207 12208 void drawPolygon(Point[] vertexes) { 12209 XPoint[16] pointsBuffer; 12210 XPoint[] points; 12211 if(vertexes.length <= pointsBuffer.length) 12212 points = pointsBuffer[0 .. vertexes.length]; 12213 else 12214 points.length = vertexes.length; 12215 12216 foreach(i, p; vertexes) { 12217 points[i].x = cast(short) p.x; 12218 points[i].y = cast(short) p.y; 12219 } 12220 12221 if(backgroundIsNotTransparent) { 12222 swapColors(); 12223 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 12224 swapColors(); 12225 } 12226 if(foregroundIsNotTransparent) { 12227 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 12228 } 12229 } 12230 } 12231 12232 /* XRender { */ 12233 12234 struct XRenderColor { 12235 ushort red; 12236 ushort green; 12237 ushort blue; 12238 ushort alpha; 12239 } 12240 12241 alias Picture = XID; 12242 alias PictFormat = XID; 12243 12244 struct XGlyphInfo { 12245 ushort width; 12246 ushort height; 12247 short x; 12248 short y; 12249 short xOff; 12250 short yOff; 12251 } 12252 12253 struct XRenderDirectFormat { 12254 short red; 12255 short redMask; 12256 short green; 12257 short greenMask; 12258 short blue; 12259 short blueMask; 12260 short alpha; 12261 short alphaMask; 12262 } 12263 12264 struct XRenderPictFormat { 12265 PictFormat id; 12266 int type; 12267 int depth; 12268 XRenderDirectFormat direct; 12269 Colormap colormap; 12270 } 12271 12272 enum PictFormatID = (1 << 0); 12273 enum PictFormatType = (1 << 1); 12274 enum PictFormatDepth = (1 << 2); 12275 enum PictFormatRed = (1 << 3); 12276 enum PictFormatRedMask =(1 << 4); 12277 enum PictFormatGreen = (1 << 5); 12278 enum PictFormatGreenMask=(1 << 6); 12279 enum PictFormatBlue = (1 << 7); 12280 enum PictFormatBlueMask =(1 << 8); 12281 enum PictFormatAlpha = (1 << 9); 12282 enum PictFormatAlphaMask=(1 << 10); 12283 enum PictFormatColormap =(1 << 11); 12284 12285 struct XRenderPictureAttributes { 12286 int repeat; 12287 Picture alpha_map; 12288 int alpha_x_origin; 12289 int alpha_y_origin; 12290 int clip_x_origin; 12291 int clip_y_origin; 12292 Pixmap clip_mask; 12293 Bool graphics_exposures; 12294 int subwindow_mode; 12295 int poly_edge; 12296 int poly_mode; 12297 Atom dither; 12298 Bool component_alpha; 12299 } 12300 12301 alias int XFixed; 12302 12303 struct XPointFixed { 12304 XFixed x, y; 12305 } 12306 12307 struct XCircle { 12308 XFixed x; 12309 XFixed y; 12310 XFixed radius; 12311 } 12312 12313 struct XTransform { 12314 XFixed[3][3] matrix; 12315 } 12316 12317 struct XFilters { 12318 int nfilter; 12319 char **filter; 12320 int nalias; 12321 short *alias_; 12322 } 12323 12324 struct XIndexValue { 12325 c_ulong pixel; 12326 ushort red, green, blue, alpha; 12327 } 12328 12329 struct XAnimCursor { 12330 Cursor cursor; 12331 c_ulong delay; 12332 } 12333 12334 struct XLinearGradient { 12335 XPointFixed p1; 12336 XPointFixed p2; 12337 } 12338 12339 struct XRadialGradient { 12340 XCircle inner; 12341 XCircle outer; 12342 } 12343 12344 struct XConicalGradient { 12345 XPointFixed center; 12346 XFixed angle; /* in degrees */ 12347 } 12348 12349 enum PictStandardARGB32 = 0; 12350 enum PictStandardRGB24 = 1; 12351 enum PictStandardA8 = 2; 12352 enum PictStandardA4 = 3; 12353 enum PictStandardA1 = 4; 12354 enum PictStandardNUM = 5; 12355 12356 interface XRender { 12357 extern(C) @nogc: 12358 12359 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 12360 12361 Status XRenderQueryVersion (Display *dpy, 12362 int *major_versionp, 12363 int *minor_versionp); 12364 12365 Status XRenderQueryFormats (Display *dpy); 12366 12367 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 12368 12369 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 12370 12371 XRenderPictFormat * 12372 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 12373 12374 XRenderPictFormat * 12375 XRenderFindFormat (Display *dpy, 12376 c_ulong mask, 12377 const XRenderPictFormat *templ, 12378 int count); 12379 XRenderPictFormat * 12380 XRenderFindStandardFormat (Display *dpy, 12381 int format); 12382 12383 XIndexValue * 12384 XRenderQueryPictIndexValues(Display *dpy, 12385 const XRenderPictFormat *format, 12386 int *num); 12387 12388 Picture XRenderCreatePicture( 12389 Display *dpy, 12390 Drawable drawable, 12391 const XRenderPictFormat *format, 12392 c_ulong valuemask, 12393 const XRenderPictureAttributes *attributes); 12394 12395 void XRenderChangePicture (Display *dpy, 12396 Picture picture, 12397 c_ulong valuemask, 12398 const XRenderPictureAttributes *attributes); 12399 12400 void 12401 XRenderSetPictureClipRectangles (Display *dpy, 12402 Picture picture, 12403 int xOrigin, 12404 int yOrigin, 12405 const XRectangle *rects, 12406 int n); 12407 12408 void 12409 XRenderSetPictureClipRegion (Display *dpy, 12410 Picture picture, 12411 Region r); 12412 12413 void 12414 XRenderSetPictureTransform (Display *dpy, 12415 Picture picture, 12416 XTransform *transform); 12417 12418 void 12419 XRenderFreePicture (Display *dpy, 12420 Picture picture); 12421 12422 void 12423 XRenderComposite (Display *dpy, 12424 int op, 12425 Picture src, 12426 Picture mask, 12427 Picture dst, 12428 int src_x, 12429 int src_y, 12430 int mask_x, 12431 int mask_y, 12432 int dst_x, 12433 int dst_y, 12434 uint width, 12435 uint height); 12436 12437 12438 Picture XRenderCreateSolidFill (Display *dpy, 12439 const XRenderColor *color); 12440 12441 Picture XRenderCreateLinearGradient (Display *dpy, 12442 const XLinearGradient *gradient, 12443 const XFixed *stops, 12444 const XRenderColor *colors, 12445 int nstops); 12446 12447 Picture XRenderCreateRadialGradient (Display *dpy, 12448 const XRadialGradient *gradient, 12449 const XFixed *stops, 12450 const XRenderColor *colors, 12451 int nstops); 12452 12453 Picture XRenderCreateConicalGradient (Display *dpy, 12454 const XConicalGradient *gradient, 12455 const XFixed *stops, 12456 const XRenderColor *colors, 12457 int nstops); 12458 12459 12460 12461 Cursor 12462 XRenderCreateCursor (Display *dpy, 12463 Picture source, 12464 uint x, 12465 uint y); 12466 12467 XFilters * 12468 XRenderQueryFilters (Display *dpy, Drawable drawable); 12469 12470 void 12471 XRenderSetPictureFilter (Display *dpy, 12472 Picture picture, 12473 const char *filter, 12474 XFixed *params, 12475 int nparams); 12476 12477 Cursor 12478 XRenderCreateAnimCursor (Display *dpy, 12479 int ncursor, 12480 XAnimCursor *cursors); 12481 } 12482 12483 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 12484 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 12485 12486 /* XRender } */ 12487 12488 /* Xrandr { */ 12489 12490 struct XRRMonitorInfo { 12491 Atom name; 12492 Bool primary; 12493 Bool automatic; 12494 int noutput; 12495 int x; 12496 int y; 12497 int width; 12498 int height; 12499 int mwidth; 12500 int mheight; 12501 /*RROutput*/ void *outputs; 12502 } 12503 12504 struct XRRScreenChangeNotifyEvent { 12505 int type; /* event base */ 12506 c_ulong serial; /* # of last request processed by server */ 12507 Bool send_event; /* true if this came from a SendEvent request */ 12508 Display *display; /* Display the event was read from */ 12509 Window window; /* window which selected for this event */ 12510 Window root; /* Root window for changed screen */ 12511 Time timestamp; /* when the screen change occurred */ 12512 Time config_timestamp; /* when the last configuration change */ 12513 ushort/*SizeID*/ size_index; 12514 ushort/*SubpixelOrder*/ subpixel_order; 12515 ushort/*Rotation*/ rotation; 12516 int width; 12517 int height; 12518 int mwidth; 12519 int mheight; 12520 } 12521 12522 enum RRScreenChangeNotify = 0; 12523 12524 enum RRScreenChangeNotifyMask = 1; 12525 12526 __gshared int xrrEventBase = -1; 12527 12528 12529 interface XRandr { 12530 extern(C) @nogc: 12531 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 12532 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 12533 12534 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 12535 void XRRFreeMonitors(XRRMonitorInfo *monitors); 12536 12537 void XRRSelectInput(Display *dpy, Window window, int mask); 12538 } 12539 12540 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 12541 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 12542 /* Xrandr } */ 12543 12544 /* Xft { */ 12545 12546 // actually freetype 12547 alias void FT_Face; 12548 12549 // actually fontconfig 12550 private alias FcBool = int; 12551 alias void FcCharSet; 12552 alias void FcPattern; 12553 alias void FcResult; 12554 enum FcEndian { FcEndianBig, FcEndianLittle } 12555 struct FcFontSet { 12556 int nfont; 12557 int sfont; 12558 FcPattern** fonts; 12559 } 12560 12561 // actually XRegion 12562 struct BOX { 12563 short x1, x2, y1, y2; 12564 } 12565 struct _XRegion { 12566 c_long size; 12567 c_long numRects; 12568 BOX* rects; 12569 BOX extents; 12570 } 12571 12572 alias Region = _XRegion*; 12573 12574 // ok actually Xft 12575 12576 struct XftFontInfo; 12577 12578 struct XftFont { 12579 int ascent; 12580 int descent; 12581 int height; 12582 int max_advance_width; 12583 FcCharSet* charset; 12584 FcPattern* pattern; 12585 } 12586 12587 struct XftDraw; 12588 12589 struct XftColor { 12590 c_ulong pixel; 12591 XRenderColor color; 12592 } 12593 12594 struct XftCharSpec { 12595 dchar ucs4; 12596 short x; 12597 short y; 12598 } 12599 12600 struct XftCharFontSpec { 12601 XftFont *font; 12602 dchar ucs4; 12603 short x; 12604 short y; 12605 } 12606 12607 struct XftGlyphSpec { 12608 uint glyph; 12609 short x; 12610 short y; 12611 } 12612 12613 struct XftGlyphFontSpec { 12614 XftFont *font; 12615 uint glyph; 12616 short x; 12617 short y; 12618 } 12619 12620 interface Xft { 12621 extern(C) @nogc pure: 12622 12623 Bool XftColorAllocName (Display *dpy, 12624 const Visual *visual, 12625 Colormap cmap, 12626 const char *name, 12627 XftColor *result); 12628 12629 Bool XftColorAllocValue (Display *dpy, 12630 Visual *visual, 12631 Colormap cmap, 12632 const XRenderColor *color, 12633 XftColor *result); 12634 12635 void XftColorFree (Display *dpy, 12636 Visual *visual, 12637 Colormap cmap, 12638 XftColor *color); 12639 12640 Bool XftDefaultHasRender (Display *dpy); 12641 12642 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 12643 12644 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 12645 12646 XftDraw * XftDrawCreate (Display *dpy, 12647 Drawable drawable, 12648 Visual *visual, 12649 Colormap colormap); 12650 12651 XftDraw * XftDrawCreateBitmap (Display *dpy, 12652 Pixmap bitmap); 12653 12654 XftDraw * XftDrawCreateAlpha (Display *dpy, 12655 Pixmap pixmap, 12656 int depth); 12657 12658 void XftDrawChange (XftDraw *draw, 12659 Drawable drawable); 12660 12661 Display * XftDrawDisplay (XftDraw *draw); 12662 12663 Drawable XftDrawDrawable (XftDraw *draw); 12664 12665 Colormap XftDrawColormap (XftDraw *draw); 12666 12667 Visual * XftDrawVisual (XftDraw *draw); 12668 12669 void XftDrawDestroy (XftDraw *draw); 12670 12671 Picture XftDrawPicture (XftDraw *draw); 12672 12673 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 12674 12675 void XftDrawGlyphs (XftDraw *draw, 12676 const XftColor *color, 12677 XftFont *pub, 12678 int x, 12679 int y, 12680 const uint *glyphs, 12681 int nglyphs); 12682 12683 void XftDrawString8 (XftDraw *draw, 12684 const XftColor *color, 12685 XftFont *pub, 12686 int x, 12687 int y, 12688 const char *string, 12689 int len); 12690 12691 void XftDrawString16 (XftDraw *draw, 12692 const XftColor *color, 12693 XftFont *pub, 12694 int x, 12695 int y, 12696 const wchar *string, 12697 int len); 12698 12699 void XftDrawString32 (XftDraw *draw, 12700 const XftColor *color, 12701 XftFont *pub, 12702 int x, 12703 int y, 12704 const dchar *string, 12705 int len); 12706 12707 void XftDrawStringUtf8 (XftDraw *draw, 12708 const XftColor *color, 12709 XftFont *pub, 12710 int x, 12711 int y, 12712 const char *string, 12713 int len); 12714 void XftDrawStringUtf16 (XftDraw *draw, 12715 const XftColor *color, 12716 XftFont *pub, 12717 int x, 12718 int y, 12719 const char *string, 12720 FcEndian endian, 12721 int len); 12722 12723 void XftDrawCharSpec (XftDraw *draw, 12724 const XftColor *color, 12725 XftFont *pub, 12726 const XftCharSpec *chars, 12727 int len); 12728 12729 void XftDrawCharFontSpec (XftDraw *draw, 12730 const XftColor *color, 12731 const XftCharFontSpec *chars, 12732 int len); 12733 12734 void XftDrawGlyphSpec (XftDraw *draw, 12735 const XftColor *color, 12736 XftFont *pub, 12737 const XftGlyphSpec *glyphs, 12738 int len); 12739 12740 void XftDrawGlyphFontSpec (XftDraw *draw, 12741 const XftColor *color, 12742 const XftGlyphFontSpec *glyphs, 12743 int len); 12744 12745 void XftDrawRect (XftDraw *draw, 12746 const XftColor *color, 12747 int x, 12748 int y, 12749 uint width, 12750 uint height); 12751 12752 Bool XftDrawSetClip (XftDraw *draw, 12753 Region r); 12754 12755 12756 Bool XftDrawSetClipRectangles (XftDraw *draw, 12757 int xOrigin, 12758 int yOrigin, 12759 const XRectangle *rects, 12760 int n); 12761 12762 void XftDrawSetSubwindowMode (XftDraw *draw, 12763 int mode); 12764 12765 void XftGlyphExtents (Display *dpy, 12766 XftFont *pub, 12767 const uint *glyphs, 12768 int nglyphs, 12769 XGlyphInfo *extents); 12770 12771 void XftTextExtents8 (Display *dpy, 12772 XftFont *pub, 12773 const char *string, 12774 int len, 12775 XGlyphInfo *extents); 12776 12777 void XftTextExtents16 (Display *dpy, 12778 XftFont *pub, 12779 const wchar *string, 12780 int len, 12781 XGlyphInfo *extents); 12782 12783 void XftTextExtents32 (Display *dpy, 12784 XftFont *pub, 12785 const dchar *string, 12786 int len, 12787 XGlyphInfo *extents); 12788 12789 void XftTextExtentsUtf8 (Display *dpy, 12790 XftFont *pub, 12791 const char *string, 12792 int len, 12793 XGlyphInfo *extents); 12794 12795 void XftTextExtentsUtf16 (Display *dpy, 12796 XftFont *pub, 12797 const char *string, 12798 FcEndian endian, 12799 int len, 12800 XGlyphInfo *extents); 12801 12802 FcPattern * XftFontMatch (Display *dpy, 12803 int screen, 12804 const FcPattern *pattern, 12805 FcResult *result); 12806 12807 XftFont * XftFontOpen (Display *dpy, int screen, ...); 12808 12809 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 12810 12811 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 12812 12813 FT_Face XftLockFace (XftFont *pub); 12814 12815 void XftUnlockFace (XftFont *pub); 12816 12817 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 12818 12819 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 12820 12821 dchar XftFontInfoHash (const XftFontInfo *fi); 12822 12823 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 12824 12825 XftFont * XftFontOpenInfo (Display *dpy, 12826 FcPattern *pattern, 12827 XftFontInfo *fi); 12828 12829 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 12830 12831 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 12832 12833 void XftFontClose (Display *dpy, XftFont *pub); 12834 12835 FcBool XftInitFtLibrary(); 12836 void XftFontLoadGlyphs (Display *dpy, 12837 XftFont *pub, 12838 FcBool need_bitmaps, 12839 const uint *glyphs, 12840 int nglyph); 12841 12842 void XftFontUnloadGlyphs (Display *dpy, 12843 XftFont *pub, 12844 const uint *glyphs, 12845 int nglyph); 12846 12847 FcBool XftFontCheckGlyph (Display *dpy, 12848 XftFont *pub, 12849 FcBool need_bitmaps, 12850 uint glyph, 12851 uint *missing, 12852 int *nmissing); 12853 12854 FcBool XftCharExists (Display *dpy, 12855 XftFont *pub, 12856 dchar ucs4); 12857 12858 uint XftCharIndex (Display *dpy, 12859 XftFont *pub, 12860 dchar ucs4); 12861 FcBool XftInit (const char *config); 12862 12863 int XftGetVersion (); 12864 12865 FcFontSet * XftListFonts (Display *dpy, 12866 int screen, 12867 ...); 12868 12869 FcPattern *XftNameParse (const char *name); 12870 12871 void XftGlyphRender (Display *dpy, 12872 int op, 12873 Picture src, 12874 XftFont *pub, 12875 Picture dst, 12876 int srcx, 12877 int srcy, 12878 int x, 12879 int y, 12880 const uint *glyphs, 12881 int nglyphs); 12882 12883 void XftGlyphSpecRender (Display *dpy, 12884 int op, 12885 Picture src, 12886 XftFont *pub, 12887 Picture dst, 12888 int srcx, 12889 int srcy, 12890 const XftGlyphSpec *glyphs, 12891 int nglyphs); 12892 12893 void XftCharSpecRender (Display *dpy, 12894 int op, 12895 Picture src, 12896 XftFont *pub, 12897 Picture dst, 12898 int srcx, 12899 int srcy, 12900 const XftCharSpec *chars, 12901 int len); 12902 void XftGlyphFontSpecRender (Display *dpy, 12903 int op, 12904 Picture src, 12905 Picture dst, 12906 int srcx, 12907 int srcy, 12908 const XftGlyphFontSpec *glyphs, 12909 int nglyphs); 12910 12911 void XftCharFontSpecRender (Display *dpy, 12912 int op, 12913 Picture src, 12914 Picture dst, 12915 int srcx, 12916 int srcy, 12917 const XftCharFontSpec *chars, 12918 int len); 12919 12920 void XftTextRender8 (Display *dpy, 12921 int op, 12922 Picture src, 12923 XftFont *pub, 12924 Picture dst, 12925 int srcx, 12926 int srcy, 12927 int x, 12928 int y, 12929 const char *string, 12930 int len); 12931 void XftTextRender16 (Display *dpy, 12932 int op, 12933 Picture src, 12934 XftFont *pub, 12935 Picture dst, 12936 int srcx, 12937 int srcy, 12938 int x, 12939 int y, 12940 const wchar *string, 12941 int len); 12942 12943 void XftTextRender16BE (Display *dpy, 12944 int op, 12945 Picture src, 12946 XftFont *pub, 12947 Picture dst, 12948 int srcx, 12949 int srcy, 12950 int x, 12951 int y, 12952 const char *string, 12953 int len); 12954 12955 void XftTextRender16LE (Display *dpy, 12956 int op, 12957 Picture src, 12958 XftFont *pub, 12959 Picture dst, 12960 int srcx, 12961 int srcy, 12962 int x, 12963 int y, 12964 const char *string, 12965 int len); 12966 12967 void XftTextRender32 (Display *dpy, 12968 int op, 12969 Picture src, 12970 XftFont *pub, 12971 Picture dst, 12972 int srcx, 12973 int srcy, 12974 int x, 12975 int y, 12976 const dchar *string, 12977 int len); 12978 12979 void XftTextRender32BE (Display *dpy, 12980 int op, 12981 Picture src, 12982 XftFont *pub, 12983 Picture dst, 12984 int srcx, 12985 int srcy, 12986 int x, 12987 int y, 12988 const char *string, 12989 int len); 12990 12991 void XftTextRender32LE (Display *dpy, 12992 int op, 12993 Picture src, 12994 XftFont *pub, 12995 Picture dst, 12996 int srcx, 12997 int srcy, 12998 int x, 12999 int y, 13000 const char *string, 13001 int len); 13002 13003 void XftTextRenderUtf8 (Display *dpy, 13004 int op, 13005 Picture src, 13006 XftFont *pub, 13007 Picture dst, 13008 int srcx, 13009 int srcy, 13010 int x, 13011 int y, 13012 const char *string, 13013 int len); 13014 13015 void XftTextRenderUtf16 (Display *dpy, 13016 int op, 13017 Picture src, 13018 XftFont *pub, 13019 Picture dst, 13020 int srcx, 13021 int srcy, 13022 int x, 13023 int y, 13024 const char *string, 13025 FcEndian endian, 13026 int len); 13027 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 13028 13029 } 13030 13031 interface FontConfig { 13032 extern(C) @nogc pure: 13033 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 13034 void FcFontSetDestroy(FcFontSet*); 13035 char* FcNameUnparse(const FcPattern *); 13036 } 13037 13038 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 13039 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 13040 13041 13042 /* Xft } */ 13043 13044 class XDisconnectException : Exception { 13045 bool userRequested; 13046 this(bool userRequested = true) { 13047 this.userRequested = userRequested; 13048 super("X disconnected"); 13049 } 13050 } 13051 13052 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 13053 class XDisplayConnection { 13054 private __gshared Display* display; 13055 private __gshared XIM xim; 13056 private __gshared char* displayName; 13057 13058 private __gshared int connectionSequence_; 13059 private __gshared bool isLocal_; 13060 13061 /// use this for lazy caching when reconnection 13062 static int connectionSequenceNumber() { return connectionSequence_; } 13063 13064 /++ 13065 Guesses if the connection appears to be local. 13066 13067 History: 13068 Added June 3, 2021 13069 +/ 13070 static @property bool isLocal() nothrow @trusted @nogc { 13071 return isLocal_; 13072 } 13073 13074 /// Attempts recreation of state, may require application assistance 13075 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 13076 /// then call this, and if successful, reenter the loop. 13077 static void discardAndRecreate(string newDisplayString = null) { 13078 if(insideXEventLoop) 13079 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 13080 13081 // 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 13082 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 13083 13084 foreach(handle; chnenhm) { 13085 handle.discardConnectionState(); 13086 } 13087 13088 discardState(); 13089 13090 if(newDisplayString !is null) 13091 setDisplayName(newDisplayString); 13092 13093 auto display = get(); 13094 13095 foreach(handle; chnenhm) { 13096 handle.recreateAfterDisconnect(); 13097 } 13098 } 13099 13100 private __gshared EventMask rootEventMask; 13101 13102 /++ 13103 Requests the specified input from the root window on the connection, in addition to any other request. 13104 13105 13106 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. 13107 13108 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 13109 +/ 13110 static void addRootInput(EventMask mask) { 13111 auto old = rootEventMask; 13112 rootEventMask |= mask; 13113 get(); // to ensure display connected 13114 if(display !is null && rootEventMask != old) 13115 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 13116 } 13117 13118 static void discardState() { 13119 freeImages(); 13120 13121 foreach(atomPtr; interredAtoms) 13122 *atomPtr = 0; 13123 interredAtoms = null; 13124 interredAtoms.assumeSafeAppend(); 13125 13126 ScreenPainterImplementation.fontAttempted = false; 13127 ScreenPainterImplementation.defaultfont = null; 13128 ScreenPainterImplementation.defaultfontset = null; 13129 13130 Image.impl.xshmQueryCompleted = false; 13131 Image.impl._xshmAvailable = false; 13132 13133 SimpleWindow.nativeMapping = null; 13134 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 13135 // GlobalHotkeyManager 13136 13137 display = null; 13138 xim = null; 13139 } 13140 13141 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 13142 private static void createXIM () { 13143 import core.stdc.locale : setlocale, LC_ALL; 13144 import core.stdc.stdio : stderr, fprintf; 13145 import core.stdc.stdlib : free; 13146 import core.stdc.string : strdup; 13147 13148 static immutable string[3] mtry = [ null, "@im=local", "@im=" ]; 13149 13150 auto olocale = strdup(setlocale(LC_ALL, null)); 13151 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 13152 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 13153 13154 //fprintf(stderr, "opening IM...\n"); 13155 foreach (string s; mtry) { 13156 if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 13157 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 13158 } 13159 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 13160 } 13161 13162 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 13163 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 13164 static struct ImgList { 13165 size_t img; // class; hide it from GC 13166 ImgList* next; 13167 } 13168 13169 static __gshared ImgList* imglist = null; 13170 static __gshared bool imglistLocked = false; // true: don't register and unregister images 13171 13172 static void registerImage (Image img) { 13173 if (!imglistLocked && img !is null) { 13174 import core.stdc.stdlib : malloc; 13175 auto it = cast(ImgList*)malloc(ImgList.sizeof); 13176 assert(it !is null); // do proper checks 13177 it.img = cast(size_t)cast(void*)img; 13178 it.next = imglist; 13179 imglist = it; 13180 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 13181 } 13182 } 13183 13184 static void unregisterImage (Image img) { 13185 if (!imglistLocked && img !is null) { 13186 import core.stdc.stdlib : free; 13187 ImgList* prev = null; 13188 ImgList* cur = imglist; 13189 while (cur !is null) { 13190 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 13191 prev = cur; 13192 cur = cur.next; 13193 } 13194 if (cur !is null) { 13195 if (prev is null) imglist = cur.next; else prev.next = cur.next; 13196 free(cur); 13197 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 13198 } else { 13199 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 13200 } 13201 } 13202 } 13203 13204 static void freeImages () { // needed for discardAndRecreate 13205 imglistLocked = true; 13206 scope(exit) imglistLocked = false; 13207 ImgList* cur = imglist; 13208 ImgList* next = null; 13209 while (cur !is null) { 13210 import core.stdc.stdlib : free; 13211 next = cur.next; 13212 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 13213 (cast(Image)cast(void*)cur.img).dispose(); 13214 free(cur); 13215 cur = next; 13216 } 13217 imglist = null; 13218 } 13219 13220 /// can be used to override normal handling of display name 13221 /// from environment and/or command line 13222 static setDisplayName(string newDisplayName) { 13223 displayName = cast(char*) (newDisplayName ~ '\0'); 13224 } 13225 13226 /// resets to the default display string 13227 static resetDisplayName() { 13228 displayName = null; 13229 } 13230 13231 /// 13232 static Display* get() { 13233 if(display is null) { 13234 if(!librariesSuccessfullyLoaded) 13235 throw new Exception("Unable to load X11 client libraries"); 13236 display = XOpenDisplay(displayName); 13237 13238 isLocal_ = false; 13239 13240 connectionSequence_++; 13241 if(display is null) 13242 throw new Exception("Unable to open X display"); 13243 13244 auto str = display.display_name; 13245 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 13246 // and otherwise it probably isn't 13247 if(str is null || (str[0] != ':' && str[0] != '/')) 13248 isLocal_ = false; 13249 else 13250 isLocal_ = true; 13251 13252 //XSetErrorHandler(&adrlogger); 13253 //XSynchronize(display, true); 13254 13255 13256 XSetIOErrorHandler(&x11ioerrCB); 13257 Bool sup; 13258 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 13259 createXIM(); 13260 version(with_eventloop) { 13261 import arsd.eventloop; 13262 addFileEventListeners(display.fd, &eventListener, null, null); 13263 } 13264 } 13265 13266 return display; 13267 } 13268 13269 extern(C) 13270 static int x11ioerrCB(Display* dpy) { 13271 throw new XDisconnectException(false); 13272 } 13273 13274 version(with_eventloop) { 13275 import arsd.eventloop; 13276 static void eventListener(OsFileHandle fd) { 13277 //this.mtLock(); 13278 //scope(exit) this.mtUnlock(); 13279 while(XPending(display)) 13280 doXNextEvent(display); 13281 } 13282 } 13283 13284 // close connection on program exit -- we need this to properly free all images 13285 static ~this () { 13286 // the gui thread must clean up after itself or else Xlib might deadlock 13287 // using this flag on any thread destruction is the easiest way i know of 13288 // (shared static this is run by the LAST thread to exit, which may not be 13289 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 13290 if(thisIsGuiThread) 13291 close(); 13292 } 13293 13294 /// 13295 static void close() { 13296 if(display is null) 13297 return; 13298 13299 version(with_eventloop) { 13300 import arsd.eventloop; 13301 removeFileEventListeners(display.fd); 13302 } 13303 13304 // now remove all registered images to prevent shared memory leaks 13305 freeImages(); 13306 13307 // tbh I don't know why it is doing this but like if this happens to run 13308 // from the other thread there's frequent hanging inside here. 13309 if(thisIsGuiThread) 13310 XCloseDisplay(display); 13311 display = null; 13312 } 13313 } 13314 13315 mixin template NativeImageImplementation() { 13316 XImage* handle; 13317 ubyte* rawData; 13318 13319 XShmSegmentInfo shminfo; 13320 13321 __gshared bool xshmQueryCompleted; 13322 __gshared bool _xshmAvailable; 13323 public static @property bool xshmAvailable() { 13324 if(!xshmQueryCompleted) { 13325 int i1, i2, i3; 13326 xshmQueryCompleted = true; 13327 13328 if(!XDisplayConnection.isLocal) 13329 _xshmAvailable = false; 13330 else 13331 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 13332 } 13333 return _xshmAvailable; 13334 } 13335 13336 bool usingXshm; 13337 final: 13338 13339 private __gshared bool xshmfailed; 13340 13341 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 13342 auto display = XDisplayConnection.get(); 13343 assert(display !is null); 13344 auto screen = DefaultScreen(display); 13345 13346 // it will only use shared memory for somewhat largish images, 13347 // since otherwise we risk wasting shared memory handles on a lot of little ones 13348 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 13349 13350 13351 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 13352 // the actual use still fails. For example, if the program is in a container and permission denied 13353 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 13354 // 13355 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 13356 13357 13358 // synchronize so preexisting buffers are clear 13359 XSync(display, false); 13360 xshmfailed = false; 13361 13362 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 13363 13364 13365 usingXshm = true; 13366 handle = XShmCreateImage( 13367 display, 13368 DefaultVisual(display, screen), 13369 enableAlpha ? 32: 24, 13370 ImageFormat.ZPixmap, 13371 null, 13372 &shminfo, 13373 width, height); 13374 if(handle is null) 13375 goto abortXshm1; 13376 13377 if(handle.bytes_per_line != 4 * width) 13378 goto abortXshm2; 13379 13380 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 13381 if(shminfo.shmid < 0) 13382 goto abortXshm3; 13383 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 13384 if(rawData == cast(ubyte*) -1) 13385 goto abortXshm4; 13386 shminfo.readOnly = 0; 13387 XShmAttach(display, &shminfo); 13388 13389 // and now to the final error check to ensure it actually worked. 13390 XSync(display, false); 13391 if(xshmfailed) 13392 goto abortXshm5; 13393 13394 XSetErrorHandler(oldErrorHandler); 13395 13396 XDisplayConnection.registerImage(this); 13397 // if I don't flush here there's a chance the dtor will run before the 13398 // ctor and lead to a bad value X error. While this hurts the efficiency 13399 // it is local anyway so prolly better to keep it simple 13400 XFlush(display); 13401 13402 return; 13403 13404 abortXshm5: 13405 shmdt(shminfo.shmaddr); 13406 rawData = null; 13407 13408 abortXshm4: 13409 shmctl(shminfo.shmid, IPC_RMID, null); 13410 13411 abortXshm3: 13412 // nothing needed, the shmget failed so there's nothing to free 13413 13414 abortXshm2: 13415 XDestroyImage(handle); 13416 handle = null; 13417 13418 abortXshm1: 13419 XSetErrorHandler(oldErrorHandler); 13420 usingXshm = false; 13421 handle = null; 13422 13423 shminfo = typeof(shminfo).init; 13424 13425 _xshmAvailable = false; // don't try again in the future 13426 13427 //import std.stdio; writeln("fallingback"); 13428 13429 goto fallback; 13430 13431 } else { 13432 fallback: 13433 13434 if (forcexshm) throw new Exception("can't create XShm Image"); 13435 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 13436 import core.stdc.stdlib : malloc; 13437 rawData = cast(ubyte*) malloc(width * height * 4); 13438 13439 handle = XCreateImage( 13440 display, 13441 DefaultVisual(display, screen), 13442 enableAlpha ? 32 : 24, // bpp 13443 ImageFormat.ZPixmap, 13444 0, // offset 13445 rawData, 13446 width, height, 13447 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 13448 } 13449 } 13450 13451 void dispose() { 13452 // note: this calls free(rawData) for us 13453 if(handle) { 13454 if (usingXshm) { 13455 XDisplayConnection.unregisterImage(this); 13456 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 13457 } 13458 XDestroyImage(handle); 13459 if(usingXshm) { 13460 shmdt(shminfo.shmaddr); 13461 shmctl(shminfo.shmid, IPC_RMID, null); 13462 } 13463 handle = null; 13464 } 13465 } 13466 13467 Color getPixel(int x, int y) { 13468 auto offset = (y * width + x) * 4; 13469 Color c; 13470 c.a = enableAlpha ? rawData[offset + 3] : 255; 13471 c.b = rawData[offset + 0]; 13472 c.g = rawData[offset + 1]; 13473 c.r = rawData[offset + 2]; 13474 if(enableAlpha) 13475 c.unPremultiply; 13476 return c; 13477 } 13478 13479 void setPixel(int x, int y, Color c) { 13480 if(enableAlpha) 13481 c.premultiply(); 13482 auto offset = (y * width + x) * 4; 13483 rawData[offset + 0] = c.b; 13484 rawData[offset + 1] = c.g; 13485 rawData[offset + 2] = c.r; 13486 if(enableAlpha) 13487 rawData[offset + 3] = c.a; 13488 } 13489 13490 void convertToRgbaBytes(ubyte[] where) { 13491 assert(where.length == this.width * this.height * 4); 13492 13493 // if rawData had a length.... 13494 //assert(rawData.length == where.length); 13495 for(int idx = 0; idx < where.length; idx += 4) { 13496 where[idx + 0] = rawData[idx + 2]; // r 13497 where[idx + 1] = rawData[idx + 1]; // g 13498 where[idx + 2] = rawData[idx + 0]; // b 13499 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 13500 13501 if(enableAlpha) 13502 unPremultiplyRgba(where[idx .. idx + 4]); 13503 } 13504 } 13505 13506 void setFromRgbaBytes(in ubyte[] where) { 13507 assert(where.length == this.width * this.height * 4); 13508 13509 // if rawData had a length.... 13510 //assert(rawData.length == where.length); 13511 for(int idx = 0; idx < where.length; idx += 4) { 13512 rawData[idx + 2] = where[idx + 0]; // r 13513 rawData[idx + 1] = where[idx + 1]; // g 13514 rawData[idx + 0] = where[idx + 2]; // b 13515 if(enableAlpha) { 13516 rawData[idx + 3] = where[idx + 3]; // a 13517 premultiplyBgra(rawData[idx .. idx + 4]); 13518 } 13519 } 13520 } 13521 13522 } 13523 13524 mixin template NativeSimpleWindowImplementation() { 13525 GC gc; 13526 Window window; 13527 Display* display; 13528 13529 Pixmap buffer; 13530 int bufferw, bufferh; // size of the buffer; can be bigger than window 13531 XIC xic; // input context 13532 int curHidden = 0; // counter 13533 Cursor blankCurPtr = 0; 13534 int cursorSequenceNumber = 0; 13535 int warpEventCount = 0; // number of mouse movement events to eat 13536 13537 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 13538 X11GetSelectionHandler[Atom] getSelectionHandlers; 13539 13540 version(without_opengl) {} else 13541 GLXContext glc; 13542 13543 private void fixFixedSize(bool forced=false) (int width, int height) { 13544 if (forced || this.resizability == Resizability.fixedSize) { 13545 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 13546 XSizeHints sh; 13547 static if (!forced) { 13548 c_long spr; 13549 XGetWMNormalHints(display, window, &sh, &spr); 13550 sh.flags |= PMaxSize | PMinSize; 13551 } else { 13552 sh.flags = PMaxSize | PMinSize; 13553 } 13554 sh.min_width = width; 13555 sh.min_height = height; 13556 sh.max_width = width; 13557 sh.max_height = height; 13558 XSetWMNormalHints(display, window, &sh); 13559 //XFlush(display); 13560 } 13561 } 13562 13563 ScreenPainter getPainter() { 13564 return ScreenPainter(this, window); 13565 } 13566 13567 void move(int x, int y) { 13568 XMoveWindow(display, window, x, y); 13569 } 13570 13571 void resize(int w, int h) { 13572 if (w < 1) w = 1; 13573 if (h < 1) h = 1; 13574 XResizeWindow(display, window, w, h); 13575 13576 // calling this now to avoid waiting for the server to 13577 // acknowledge the resize; draws without returning to the 13578 // event loop will thus actually work. the server's event 13579 // btw might overrule this and resize it again 13580 recordX11Resize(display, this, w, h); 13581 13582 // FIXME: do we need to set this as the opengl context to do the glViewport change? 13583 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 13584 } 13585 13586 void moveResize (int x, int y, int w, int h) { 13587 if (w < 1) w = 1; 13588 if (h < 1) h = 1; 13589 XMoveResizeWindow(display, window, x, y, w, h); 13590 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 13591 } 13592 13593 void hideCursor () { 13594 if (curHidden++ == 0) { 13595 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 13596 static const(char)[1] cmbmp = 0; 13597 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 13598 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 13599 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 13600 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 13601 XFreePixmap(display, pm); 13602 } 13603 XDefineCursor(display, window, blankCurPtr); 13604 } 13605 } 13606 13607 void showCursor () { 13608 if (--curHidden == 0) XUndefineCursor(display, window); 13609 } 13610 13611 void warpMouse (int x, int y) { 13612 // here i will send dummy "ignore next mouse motion" event, 13613 // 'cause `XWarpPointer()` sends synthesised mouse motion, 13614 // and we don't need to report it to the user (as warping is 13615 // used when the user needs movement deltas). 13616 //XClientMessageEvent xclient; 13617 XEvent e; 13618 e.xclient.type = EventType.ClientMessage; 13619 e.xclient.window = window; 13620 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 13621 e.xclient.format = 32; 13622 e.xclient.data.l[0] = 0; 13623 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 13624 //{ 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]); } 13625 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 13626 // now warp pointer... 13627 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 13628 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 13629 // ...and flush 13630 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 13631 XFlush(display); 13632 } 13633 13634 void sendDummyEvent () { 13635 // here i will send dummy event to ping event queue 13636 XEvent e; 13637 e.xclient.type = EventType.ClientMessage; 13638 e.xclient.window = window; 13639 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 13640 e.xclient.format = 32; 13641 e.xclient.data.l[0] = 0; 13642 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 13643 XFlush(display); 13644 } 13645 13646 void setTitle(string title) { 13647 if (title.ptr is null) title = ""; 13648 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 13649 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 13650 XTextProperty windowName; 13651 windowName.value = title.ptr; 13652 windowName.encoding = XA_UTF8; //XA_STRING; 13653 windowName.format = 8; 13654 windowName.nitems = cast(uint)title.length; 13655 XSetWMName(display, window, &windowName); 13656 char[1024] namebuf = 0; 13657 auto maxlen = namebuf.length-1; 13658 if (maxlen > title.length) maxlen = title.length; 13659 namebuf[0..maxlen] = title[0..maxlen]; 13660 XStoreName(display, window, namebuf.ptr); 13661 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 13662 flushGui(); // without this OpenGL windows has a LONG delay before changing title 13663 } 13664 13665 string[] getTitles() { 13666 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 13667 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 13668 XTextProperty textProp; 13669 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 13670 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 13671 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 13672 } else 13673 return []; 13674 } else 13675 return null; 13676 } 13677 13678 string getTitle() { 13679 auto titles = getTitles(); 13680 return titles.length ? titles[0] : null; 13681 } 13682 13683 void setMinSize (int minwidth, int minheight) { 13684 import core.stdc.config : c_long; 13685 if (minwidth < 1) minwidth = 1; 13686 if (minheight < 1) minheight = 1; 13687 XSizeHints sh; 13688 c_long spr; 13689 XGetWMNormalHints(display, window, &sh, &spr); 13690 sh.min_width = minwidth; 13691 sh.min_height = minheight; 13692 sh.flags |= PMinSize; 13693 XSetWMNormalHints(display, window, &sh); 13694 flushGui(); 13695 } 13696 13697 void setMaxSize (int maxwidth, int maxheight) { 13698 import core.stdc.config : c_long; 13699 if (maxwidth < 1) maxwidth = 1; 13700 if (maxheight < 1) maxheight = 1; 13701 XSizeHints sh; 13702 c_long spr; 13703 XGetWMNormalHints(display, window, &sh, &spr); 13704 sh.max_width = maxwidth; 13705 sh.max_height = maxheight; 13706 sh.flags |= PMaxSize; 13707 XSetWMNormalHints(display, window, &sh); 13708 flushGui(); 13709 } 13710 13711 void setResizeGranularity (int granx, int grany) { 13712 import core.stdc.config : c_long; 13713 if (granx < 1) granx = 1; 13714 if (grany < 1) grany = 1; 13715 XSizeHints sh; 13716 c_long spr; 13717 XGetWMNormalHints(display, window, &sh, &spr); 13718 sh.width_inc = granx; 13719 sh.height_inc = grany; 13720 sh.flags |= PResizeInc; 13721 XSetWMNormalHints(display, window, &sh); 13722 flushGui(); 13723 } 13724 13725 void setOpacity (uint opacity) { 13726 arch_ulong o = opacity; 13727 if (opacity == uint.max) 13728 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 13729 else 13730 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 13731 XA_CARDINAL, 32, PropModeReplace, &o, 1); 13732 } 13733 13734 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 13735 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 13736 display = XDisplayConnection.get(); 13737 auto screen = DefaultScreen(display); 13738 13739 version(without_opengl) {} 13740 else { 13741 if(opengl == OpenGlOptions.yes) { 13742 GLXFBConfig fbconf = null; 13743 XVisualInfo* vi = null; 13744 bool useLegacy = false; 13745 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 13746 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 13747 int[23] visualAttribs = [ 13748 GLX_X_RENDERABLE , 1/*True*/, 13749 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 13750 GLX_RENDER_TYPE , GLX_RGBA_BIT, 13751 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 13752 GLX_RED_SIZE , 8, 13753 GLX_GREEN_SIZE , 8, 13754 GLX_BLUE_SIZE , 8, 13755 GLX_ALPHA_SIZE , 8, 13756 GLX_DEPTH_SIZE , 24, 13757 GLX_STENCIL_SIZE , 8, 13758 GLX_DOUBLEBUFFER , 1/*True*/, 13759 0/*None*/, 13760 ]; 13761 int fbcount; 13762 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 13763 if (fbcount == 0) { 13764 useLegacy = true; // try to do at least something 13765 } else { 13766 // pick the FB config/visual with the most samples per pixel 13767 int bestidx = -1, bestns = -1; 13768 foreach (int fbi; 0..fbcount) { 13769 int sb, samples; 13770 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 13771 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 13772 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 13773 } 13774 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 13775 fbconf = fbc[bestidx]; 13776 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 13777 XFree(fbc); 13778 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 13779 } 13780 } 13781 if (vi is null || useLegacy) { 13782 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 13783 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 13784 useLegacy = true; 13785 } 13786 if (vi is null) throw new Exception("no open gl visual found"); 13787 13788 XSetWindowAttributes swa; 13789 auto root = RootWindow(display, screen); 13790 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 13791 13792 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 13793 0, 0, width, height, 13794 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap, &swa); 13795 13796 // now try to use `glXCreateContextAttribsARB()` if it's here 13797 if (!useLegacy) { 13798 // request fairly advanced context, even with stencil buffer! 13799 int[9] contextAttribs = [ 13800 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 13801 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 13802 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 13803 // for modern context, set "forward compatibility" flag too 13804 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 13805 0/*None*/, 13806 ]; 13807 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 13808 if (glc is null && sdpyOpenGLContextAllowFallback) { 13809 sdpyOpenGLContextVersion = 0; 13810 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 13811 } 13812 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 13813 } else { 13814 // fallback to old GLX call 13815 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 13816 sdpyOpenGLContextVersion = 0; 13817 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 13818 } 13819 } 13820 // sync to ensure any errors generated are processed 13821 XSync(display, 0/*False*/); 13822 //{ import core.stdc.stdio; printf("ogl is here\n"); } 13823 if(glc is null) 13824 throw new Exception("glc"); 13825 } 13826 } 13827 13828 if(opengl == OpenGlOptions.no) { 13829 13830 bool overrideRedirect = false; 13831 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification) 13832 overrideRedirect = true; 13833 13834 XSetWindowAttributes swa; 13835 swa.background_pixel = WhitePixel(display, screen); 13836 swa.border_pixel = BlackPixel(display, screen); 13837 swa.override_redirect = overrideRedirect; 13838 auto root = RootWindow(display, screen); 13839 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 13840 13841 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 13842 0, 0, width, height, 13843 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap | CWBackPixel | CWBorderPixel | CWOverrideRedirect, &swa); 13844 13845 13846 13847 /* 13848 window = XCreateSimpleWindow( 13849 display, 13850 parent is null ? RootWindow(display, screen) : parent.impl.window, 13851 0, 0, // x, y 13852 width, height, 13853 1, // border width 13854 BlackPixel(display, screen), // border 13855 WhitePixel(display, screen)); // background 13856 */ 13857 13858 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 13859 bufferw = width; 13860 bufferh = height; 13861 13862 gc = DefaultGC(display, screen); 13863 13864 // clear out the buffer to get us started... 13865 XSetForeground(display, gc, WhitePixel(display, screen)); 13866 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 13867 XSetForeground(display, gc, BlackPixel(display, screen)); 13868 } 13869 13870 // input context 13871 //TODO: create this only for top-level windows, and reuse that? 13872 if (XDisplayConnection.xim !is null) { 13873 xic = XCreateIC(XDisplayConnection.xim, 13874 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 13875 /*XNClientWindow*/"clientWindow".ptr, window, 13876 /*XNFocusWindow*/"focusWindow".ptr, window, 13877 null); 13878 if (xic is null) { 13879 import core.stdc.stdio : stderr, fprintf; 13880 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 13881 } 13882 } 13883 13884 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 13885 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 13886 // window class 13887 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 13888 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 13889 XClassHint klass; 13890 XWMHints wh; 13891 XSizeHints size; 13892 klass.res_name = sdpyWindowClassStr; 13893 klass.res_class = sdpyWindowClassStr; 13894 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 13895 } 13896 13897 setTitle(title); 13898 SimpleWindow.nativeMapping[window] = this; 13899 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 13900 13901 // This gives our window a close button 13902 if (windowType != WindowTypes.eventOnly) { 13903 // FIXME: actually implement the WM_TAKE_FOCUS correctly 13904 //Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 13905 Atom[1] atoms = [GetAtom!"WM_DELETE_WINDOW"(display)]; 13906 XSetWMProtocols(display, window, atoms.ptr, cast(int) atoms.length); 13907 } 13908 13909 // FIXME: windowType and customizationFlags 13910 Atom[8] wsatoms; // here, due to goto 13911 int wmsacount = 0; // here, due to goto 13912 13913 try 13914 final switch(windowType) { 13915 case WindowTypes.normal: 13916 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 13917 break; 13918 case WindowTypes.undecorated: 13919 motifHideDecorations(); 13920 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 13921 break; 13922 case WindowTypes.eventOnly: 13923 _hidden = true; 13924 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 13925 goto hiddenWindow; 13926 //break; 13927 case WindowTypes.nestedChild: 13928 // handled in XCreateWindow calls 13929 break; 13930 13931 case WindowTypes.dropdownMenu: 13932 motifHideDecorations(); 13933 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 13934 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 13935 break; 13936 case WindowTypes.popupMenu: 13937 motifHideDecorations(); 13938 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 13939 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 13940 break; 13941 case WindowTypes.notification: 13942 motifHideDecorations(); 13943 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 13944 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 13945 break; 13946 /+ 13947 case WindowTypes.menu: 13948 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 13949 motifHideDecorations(); 13950 break; 13951 case WindowTypes.desktop: 13952 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 13953 break; 13954 case WindowTypes.dock: 13955 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 13956 break; 13957 case WindowTypes.toolbar: 13958 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 13959 break; 13960 case WindowTypes.menu: 13961 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 13962 break; 13963 case WindowTypes.utility: 13964 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 13965 break; 13966 case WindowTypes.splash: 13967 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 13968 break; 13969 case WindowTypes.dialog: 13970 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 13971 break; 13972 case WindowTypes.tooltip: 13973 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 13974 break; 13975 case WindowTypes.notification: 13976 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 13977 break; 13978 case WindowTypes.combo: 13979 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 13980 break; 13981 case WindowTypes.dnd: 13982 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 13983 break; 13984 +/ 13985 } 13986 catch(Exception e) { 13987 // XInternAtom failed, prolly a WM 13988 // that doesn't support these things 13989 } 13990 13991 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 13992 // the two following flags may be ignored by WM 13993 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 13994 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 13995 13996 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 13997 13998 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 13999 14000 // What would be ideal here is if they only were 14001 // selected if there was actually an event handler 14002 // for them... 14003 14004 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 14005 14006 hiddenWindow: 14007 14008 // set the pid property for lookup later by window managers 14009 // a standard convenience 14010 import core.sys.posix.unistd; 14011 arch_ulong pid = getpid(); 14012 14013 XChangeProperty( 14014 display, 14015 impl.window, 14016 GetAtom!("_NET_WM_PID", true)(display), 14017 XA_CARDINAL, 14018 32 /* bits */, 14019 0 /*PropModeReplace*/, 14020 &pid, 14021 1); 14022 14023 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 14024 if(parent is null) assert(0); 14025 XChangeProperty( 14026 display, 14027 impl.window, 14028 GetAtom!("WM_TRANSIENT_FOR", true)(display), 14029 XA_WINDOW, 14030 32 /* bits */, 14031 0 /*PropModeReplace*/, 14032 &parent.impl.window, 14033 1); 14034 14035 } 14036 14037 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 14038 XMapWindow(display, window); 14039 } else { 14040 _hidden = true; 14041 } 14042 } 14043 14044 void selectDefaultInput(bool forceIncludeMouseMotion) { 14045 auto mask = EventMask.ExposureMask | 14046 EventMask.KeyPressMask | 14047 EventMask.KeyReleaseMask | 14048 EventMask.PropertyChangeMask | 14049 EventMask.FocusChangeMask | 14050 EventMask.StructureNotifyMask | 14051 EventMask.VisibilityChangeMask 14052 | EventMask.ButtonPressMask 14053 | EventMask.ButtonReleaseMask 14054 ; 14055 14056 // xshm is our shortcut for local connections 14057 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 14058 mask |= EventMask.PointerMotionMask; 14059 else 14060 mask |= EventMask.ButtonMotionMask; 14061 14062 XSelectInput(display, window, mask); 14063 } 14064 14065 14066 void setNetWMWindowType(Atom type) { 14067 Atom[2] atoms; 14068 14069 atoms[0] = type; 14070 // generic fallback 14071 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 14072 14073 XChangeProperty( 14074 display, 14075 impl.window, 14076 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 14077 XA_ATOM, 14078 32 /* bits */, 14079 0 /*PropModeReplace*/, 14080 atoms.ptr, 14081 cast(int) atoms.length); 14082 } 14083 14084 void motifHideDecorations(bool hide = true) { 14085 MwmHints hints; 14086 hints.flags = MWM_HINTS_DECORATIONS; 14087 hints.decorations = hide ? 0 : 1; 14088 14089 XChangeProperty( 14090 display, 14091 impl.window, 14092 GetAtom!"_MOTIF_WM_HINTS"(display), 14093 GetAtom!"_MOTIF_WM_HINTS"(display), 14094 32 /* bits */, 14095 0 /*PropModeReplace*/, 14096 &hints, 14097 hints.sizeof / 4); 14098 } 14099 14100 /*k8: unused 14101 void createOpenGlContext() { 14102 14103 } 14104 */ 14105 14106 void closeWindow() { 14107 // I can't close this or a child window closing will 14108 // break events for everyone. So I'm just leaking it right 14109 // now and that is probably perfectly fine... 14110 version(none) 14111 if (customEventFDRead != -1) { 14112 import core.sys.posix.unistd : close; 14113 auto same = customEventFDRead == customEventFDWrite; 14114 14115 close(customEventFDRead); 14116 if(!same) 14117 close(customEventFDWrite); 14118 customEventFDRead = -1; 14119 customEventFDWrite = -1; 14120 } 14121 if(buffer) 14122 XFreePixmap(display, buffer); 14123 bufferw = bufferh = 0; 14124 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 14125 XDestroyWindow(display, window); 14126 XFlush(display); 14127 } 14128 14129 void dispose() { 14130 } 14131 14132 bool destroyed = false; 14133 } 14134 14135 bool insideXEventLoop; 14136 } 14137 14138 version(X11) { 14139 14140 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 14141 14142 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 14143 if(width != win.width || height != win.height) { 14144 win._width = width; 14145 win._height = height; 14146 14147 if(win.openglMode == OpenGlOptions.no) { 14148 // FIXME: could this be more efficient? 14149 14150 if (win.bufferw < width || win.bufferh < height) { 14151 //{ 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); } 14152 // grow the internal buffer to match the window... 14153 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 14154 { 14155 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 14156 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 14157 scope(exit) XFreeGC(win.display, xgc); 14158 XSetClipMask(win.display, xgc, None); 14159 XSetForeground(win.display, xgc, 0); 14160 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 14161 } 14162 XCopyArea(display, 14163 cast(Drawable) win.buffer, 14164 cast(Drawable) newPixmap, 14165 win.gc, 0, 0, 14166 win.bufferw < width ? win.bufferw : win.width, 14167 win.bufferh < height ? win.bufferh : win.height, 14168 0, 0); 14169 14170 XFreePixmap(display, win.buffer); 14171 win.buffer = newPixmap; 14172 win.bufferw = width; 14173 win.bufferh = height; 14174 } 14175 14176 // clear unused parts of the buffer 14177 if (win.bufferw > width || win.bufferh > height) { 14178 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 14179 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 14180 scope(exit) XFreeGC(win.display, xgc); 14181 XSetClipMask(win.display, xgc, None); 14182 XSetForeground(win.display, xgc, 0); 14183 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 14184 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 14185 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 14186 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 14187 } 14188 14189 } 14190 14191 version(without_opengl) {} else 14192 if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) { 14193 glViewport(0, 0, width, height); 14194 } 14195 14196 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 14197 14198 if(win.windowResized !is null) { 14199 XUnlockDisplay(display); 14200 scope(exit) XLockDisplay(display); 14201 win.windowResized(width, height); 14202 } 14203 } 14204 } 14205 14206 14207 /// Platform-specific, you might use it when doing a custom event loop. 14208 bool doXNextEvent(Display* display) { 14209 bool done; 14210 XEvent e; 14211 XNextEvent(display, &e); 14212 version(sddddd) { 14213 import std.stdio, std.conv : to; 14214 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 14215 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 14216 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 14217 } 14218 } 14219 14220 // filter out compose events 14221 if (XFilterEvent(&e, None)) { 14222 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 14223 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 14224 return false; 14225 } 14226 // process keyboard mapping changes 14227 if (e.type == EventType.KeymapNotify) { 14228 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 14229 XRefreshKeyboardMapping(&e.xmapping); 14230 return false; 14231 } 14232 14233 version(with_eventloop) 14234 import arsd.eventloop; 14235 14236 if(SimpleWindow.handleNativeGlobalEvent !is null) { 14237 // see windows impl's comments 14238 XUnlockDisplay(display); 14239 scope(exit) XLockDisplay(display); 14240 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 14241 if(ret == 0) 14242 return done; 14243 } 14244 14245 14246 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 14247 if(win.getNativeEventHandler !is null) { 14248 XUnlockDisplay(display); 14249 scope(exit) XLockDisplay(display); 14250 auto ret = win.getNativeEventHandler()(e); 14251 if(ret == 0) 14252 return done; 14253 } 14254 } 14255 14256 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 14257 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 14258 // we get this because of the RRScreenChangeNotifyMask 14259 14260 // this isn't actually an ideal way to do it since it wastes time 14261 // but meh it is simple and it works. 14262 win.actualDpiLoadAttempted = false; 14263 SimpleWindow.xRandrInfoLoadAttemped = false; 14264 win.updateActualDpi(); // trigger a reload 14265 } 14266 } 14267 14268 switch(e.type) { 14269 case EventType.SelectionClear: 14270 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 14271 // FIXME so it is supposed to finish any in progress transfers... but idk... 14272 //import std.stdio; writeln("SelectionClear"); 14273 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 14274 } 14275 break; 14276 case EventType.SelectionRequest: 14277 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 14278 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 14279 // import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 14280 XUnlockDisplay(display); 14281 scope(exit) XLockDisplay(display); 14282 (*ssh).handleRequest(e); 14283 } 14284 break; 14285 case EventType.PropertyNotify: 14286 // import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 14287 14288 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 14289 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 14290 ssh.sendMoreIncr(&e.xproperty); 14291 } 14292 14293 14294 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 14295 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 14296 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 14297 Atom target; 14298 int format; 14299 arch_ulong bytesafter, length; 14300 void* value; 14301 14302 ubyte[] s; 14303 Atom targetToKeep; 14304 14305 XGetWindowProperty( 14306 e.xproperty.display, 14307 e.xproperty.window, 14308 e.xproperty.atom, 14309 0, 14310 100000 /* length */, 14311 true, /* erase it to signal we got it and want more */ 14312 0 /*AnyPropertyType*/, 14313 &target, &format, &length, &bytesafter, &value); 14314 14315 if(!targetToKeep) 14316 targetToKeep = target; 14317 14318 auto id = (cast(ubyte*) value)[0 .. length]; 14319 14320 handler.handleIncrData(targetToKeep, id); 14321 14322 XFree(value); 14323 } 14324 } 14325 break; 14326 case EventType.SelectionNotify: 14327 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 14328 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 14329 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 14330 XUnlockDisplay(display); 14331 scope(exit) XLockDisplay(display); 14332 handler.handleData(None, null); 14333 } else { 14334 Atom target; 14335 int format; 14336 arch_ulong bytesafter, length; 14337 void* value; 14338 XGetWindowProperty( 14339 e.xselection.display, 14340 e.xselection.requestor, 14341 e.xselection.property, 14342 0, 14343 100000 /* length */, 14344 //false, /* don't erase it */ 14345 true, /* do erase it lol */ 14346 0 /*AnyPropertyType*/, 14347 &target, &format, &length, &bytesafter, &value); 14348 14349 // FIXME: I don't have to copy it now since it is in char[] instead of string 14350 14351 { 14352 XUnlockDisplay(display); 14353 scope(exit) XLockDisplay(display); 14354 14355 if(target == XA_ATOM) { 14356 // initial request, see what they are able to work with and request the best one 14357 // we can handle, if available 14358 14359 Atom[] answer = (cast(Atom*) value)[0 .. length]; 14360 Atom best = handler.findBestFormat(answer); 14361 14362 /+ 14363 writeln("got ", answer); 14364 foreach(a; answer) 14365 printf("%s\n", XGetAtomName(display, a)); 14366 writeln("best ", best); 14367 +/ 14368 14369 if(best != None) { 14370 // actually request the best format 14371 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 14372 } 14373 } else if(target == GetAtom!"INCR"(display)) { 14374 // incremental 14375 14376 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 14377 14378 // signal the sending program that we see 14379 // the incr and are ready to receive more. 14380 XDeleteProperty( 14381 e.xselection.display, 14382 e.xselection.requestor, 14383 e.xselection.property); 14384 } else { 14385 // unsupported type... maybe, forward 14386 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 14387 } 14388 } 14389 XFree(value); 14390 /* 14391 XDeleteProperty( 14392 e.xselection.display, 14393 e.xselection.requestor, 14394 e.xselection.property); 14395 */ 14396 } 14397 } 14398 break; 14399 case EventType.ConfigureNotify: 14400 auto event = e.xconfigure; 14401 if(auto win = event.window in SimpleWindow.nativeMapping) { 14402 //version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); } 14403 14404 win.screenPositionX = event.x; 14405 win.screenPositionY = event.y; 14406 win.updateActualDpi(); 14407 14408 recordX11Resize(display, *win, event.width, event.height); 14409 } 14410 break; 14411 case EventType.Expose: 14412 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 14413 // if it is closing from a popup menu, it can get 14414 // an Expose event right by the end and trigger a 14415 // BadDrawable error ... we'll just check 14416 // closed to handle that. 14417 if((*win).closed) break; 14418 if((*win).openglMode == OpenGlOptions.no) { 14419 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 14420 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 14421 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); 14422 } else { 14423 // need to redraw the scene somehow 14424 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 14425 XUnlockDisplay(display); 14426 scope(exit) XLockDisplay(display); 14427 version(without_opengl) {} else 14428 win.redrawOpenGlSceneSoon(); 14429 } 14430 } 14431 } 14432 break; 14433 case EventType.FocusIn: 14434 case EventType.FocusOut: 14435 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 14436 if (win.xic !is null) { 14437 //{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); } 14438 if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic); 14439 } 14440 14441 win._focused = e.type == EventType.FocusIn; 14442 14443 if(win.demandingAttention) 14444 demandAttention(*win, false); 14445 14446 if(win.onFocusChange) { 14447 XUnlockDisplay(display); 14448 scope(exit) XLockDisplay(display); 14449 win.onFocusChange(e.type == EventType.FocusIn); 14450 } 14451 } 14452 break; 14453 case EventType.VisibilityNotify: 14454 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 14455 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 14456 if (win.visibilityChanged !is null) { 14457 XUnlockDisplay(display); 14458 scope(exit) XLockDisplay(display); 14459 win.visibilityChanged(false); 14460 } 14461 } else { 14462 if (win.visibilityChanged !is null) { 14463 XUnlockDisplay(display); 14464 scope(exit) XLockDisplay(display); 14465 win.visibilityChanged(true); 14466 } 14467 } 14468 } 14469 break; 14470 case EventType.ClientMessage: 14471 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 14472 // "ignore next mouse motion" event, increment ignore counter for teh window 14473 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 14474 ++(*win).warpEventCount; 14475 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 14476 } else { 14477 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 14478 } 14479 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 14480 // user clicked the close button on the window manager 14481 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 14482 XUnlockDisplay(display); 14483 scope(exit) XLockDisplay(display); 14484 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 14485 } 14486 14487 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 14488 import std.stdio; writeln("HAPPENED"); 14489 // user clicked the close button on the window manager 14490 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 14491 XUnlockDisplay(display); 14492 scope(exit) XLockDisplay(display); 14493 14494 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 14495 XSetInputFocus(display, e.xclient.window, RevertToParent, e.xclient.data.l[1]); 14496 } 14497 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 14498 foreach(nai; NotificationAreaIcon.activeIcons) 14499 nai.newManager(); 14500 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 14501 14502 bool xDragWindow = true; 14503 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 14504 //XDefineCursor(display, xDragWindow.impl.window, 14505 //import std.stdio; writeln("XdndStatus ", e.xclient.data.l); 14506 } 14507 if(auto dh = win.dropHandler) { 14508 14509 static Atom[3] xFormatsBuffer; 14510 static Atom[] xFormats; 14511 14512 void resetXFormats() { 14513 xFormatsBuffer[] = 0; 14514 xFormats = xFormatsBuffer[]; 14515 } 14516 14517 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 14518 // on Windows it is supposed to return the effect you actually do FIXME 14519 14520 auto sourceWindow = e.xclient.data.l[0]; 14521 14522 xFormatsBuffer[0] = e.xclient.data.l[2]; 14523 xFormatsBuffer[1] = e.xclient.data.l[3]; 14524 xFormatsBuffer[2] = e.xclient.data.l[4]; 14525 14526 if(e.xclient.data.l[1] & 1) { 14527 // can just grab it all but like we don't necessarily need them... 14528 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 14529 } else { 14530 int len; 14531 foreach(fmt; xFormatsBuffer) 14532 if(fmt) len++; 14533 xFormats = xFormatsBuffer[0 .. len]; 14534 } 14535 14536 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 14537 14538 dh.dragEnter(&pkg); 14539 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 14540 14541 auto pack = e.xclient.data.l[2]; 14542 14543 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 14544 14545 14546 XClientMessageEvent xclient; 14547 14548 xclient.type = EventType.ClientMessage; 14549 xclient.window = e.xclient.data.l[0]; 14550 xclient.message_type = GetAtom!"XdndStatus"(display); 14551 xclient.format = 32; 14552 xclient.data.l[0] = win.impl.window; 14553 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 14554 auto r = result.consistentWithin; 14555 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 14556 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 14557 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 14558 14559 XSendEvent( 14560 display, 14561 e.xclient.data.l[0], 14562 false, 14563 EventMask.NoEventMask, 14564 cast(XEvent*) &xclient 14565 ); 14566 14567 14568 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 14569 //import std.stdio; writeln("XdndLeave"); 14570 // drop cancelled. 14571 // data.l[0] is the source window 14572 dh.dragLeave(); 14573 14574 resetXFormats(); 14575 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 14576 // drop happening, should fetch data, then send finished 14577 //import std.stdio; writeln("XdndDrop"); 14578 14579 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 14580 14581 dh.drop(&pkg); 14582 14583 resetXFormats(); 14584 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 14585 // import std.stdio; writeln("XdndFinished"); 14586 14587 dh.finish(); 14588 } 14589 14590 } 14591 } 14592 break; 14593 case EventType.MapNotify: 14594 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 14595 (*win)._visible = true; 14596 if (!(*win)._visibleForTheFirstTimeCalled) { 14597 (*win)._visibleForTheFirstTimeCalled = true; 14598 if ((*win).visibleForTheFirstTime !is null) { 14599 XUnlockDisplay(display); 14600 scope(exit) XLockDisplay(display); 14601 version(without_opengl) {} else { 14602 if((*win).openglMode == OpenGlOptions.yes) { 14603 (*win).setAsCurrentOpenGlContextNT(); 14604 glViewport(0, 0, (*win).width, (*win).height); 14605 } 14606 } 14607 (*win).visibleForTheFirstTime(); 14608 } 14609 } 14610 if ((*win).visibilityChanged !is null) { 14611 XUnlockDisplay(display); 14612 scope(exit) XLockDisplay(display); 14613 (*win).visibilityChanged(true); 14614 } 14615 } 14616 break; 14617 case EventType.UnmapNotify: 14618 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 14619 win._visible = false; 14620 if (win.visibilityChanged !is null) { 14621 XUnlockDisplay(display); 14622 scope(exit) XLockDisplay(display); 14623 win.visibilityChanged(false); 14624 } 14625 } 14626 break; 14627 case EventType.DestroyNotify: 14628 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 14629 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 14630 win._closed = true; // just in case 14631 win.destroyed = true; 14632 if (win.xic !is null) { 14633 XDestroyIC(win.xic); 14634 win.xic = null; // just in calse 14635 } 14636 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 14637 bool anyImportant = false; 14638 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 14639 if(w.beingOpenKeepsAppOpen) { 14640 anyImportant = true; 14641 break; 14642 } 14643 if(!anyImportant) 14644 done = true; 14645 } 14646 auto window = e.xdestroywindow.window; 14647 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 14648 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 14649 14650 version(with_eventloop) { 14651 if(done) exit(); 14652 } 14653 break; 14654 14655 case EventType.MotionNotify: 14656 MouseEvent mouse; 14657 auto event = e.xmotion; 14658 14659 mouse.type = MouseEventType.motion; 14660 mouse.x = event.x; 14661 mouse.y = event.y; 14662 mouse.modifierState = event.state; 14663 14664 mouse.timestamp = event.time; 14665 14666 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 14667 mouse.window = *win; 14668 if (win.warpEventCount > 0) { 14669 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 14670 --(*win).warpEventCount; 14671 (*win).mdx(mouse); // so deltas will be correctly updated 14672 } else { 14673 win.warpEventCount = 0; // just in case 14674 (*win).mdx(mouse); 14675 if((*win).handleMouseEvent) { 14676 XUnlockDisplay(display); 14677 scope(exit) XLockDisplay(display); 14678 (*win).handleMouseEvent(mouse); 14679 } 14680 } 14681 } 14682 14683 version(with_eventloop) 14684 send(mouse); 14685 break; 14686 case EventType.ButtonPress: 14687 case EventType.ButtonRelease: 14688 MouseEvent mouse; 14689 auto event = e.xbutton; 14690 14691 mouse.timestamp = event.time; 14692 14693 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 14694 mouse.x = event.x; 14695 mouse.y = event.y; 14696 14697 static Time lastMouseDownTime = 0; 14698 14699 mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 14700 if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time; 14701 14702 switch(event.button) { 14703 case 1: mouse.button = MouseButton.left; break; // left 14704 case 2: mouse.button = MouseButton.middle; break; // middle 14705 case 3: mouse.button = MouseButton.right; break; // right 14706 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 14707 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 14708 case 6: break; // idk 14709 case 7: break; // idk 14710 case 8: mouse.button = MouseButton.backButton; break; 14711 case 9: mouse.button = MouseButton.forwardButton; break; 14712 default: 14713 } 14714 14715 // FIXME: double check this 14716 mouse.modifierState = event.state; 14717 14718 //mouse.modifierState = event.detail; 14719 14720 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 14721 mouse.window = *win; 14722 (*win).mdx(mouse); 14723 if((*win).handleMouseEvent) { 14724 XUnlockDisplay(display); 14725 scope(exit) XLockDisplay(display); 14726 (*win).handleMouseEvent(mouse); 14727 } 14728 } 14729 version(with_eventloop) 14730 send(mouse); 14731 break; 14732 14733 case EventType.KeyPress: 14734 case EventType.KeyRelease: 14735 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 14736 KeyEvent ke; 14737 ke.pressed = e.type == EventType.KeyPress; 14738 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 14739 14740 auto sym = XKeycodeToKeysym( 14741 XDisplayConnection.get(), 14742 e.xkey.keycode, 14743 0); 14744 14745 ke.key = cast(Key) sym;//e.xkey.keycode; 14746 14747 ke.modifierState = e.xkey.state; 14748 14749 // import std.stdio; writefln("%x", sym); 14750 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 14751 int charbuflen = 0; // return value of XwcLookupString 14752 if (ke.pressed) { 14753 auto win = e.xkey.window in SimpleWindow.nativeMapping; 14754 if (win !is null && win.xic !is null) { 14755 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 14756 Status status; 14757 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 14758 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 14759 } else { 14760 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 14761 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 14762 char[16] buffer; 14763 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 14764 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 14765 } 14766 } 14767 14768 // if there's no char, subst one 14769 if (charbuflen == 0) { 14770 switch (sym) { 14771 case 0xff09: charbuf[charbuflen++] = '\t'; break; 14772 case 0xff8d: // keypad enter 14773 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 14774 default : // ignore 14775 } 14776 } 14777 14778 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 14779 ke.window = *win; 14780 14781 14782 if(win.inputProxy) 14783 win = &win.inputProxy; 14784 14785 // char events are separate since they are on Windows too 14786 // also, xcompose can generate long char sequences 14787 // don't send char events if Meta and/or Hyper is pressed 14788 // TODO: ctrl+char should only send control chars; not yet 14789 if ((e.xkey.state&ModifierState.ctrl) != 0) { 14790 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 14791 } 14792 14793 dchar[32] charsComingBuffer; 14794 int charsComingPosition; 14795 dchar[] charsComing = charsComingBuffer[]; 14796 14797 if (ke.pressed && charbuflen > 0) { 14798 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 14799 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 14800 if(charsComingPosition >= charsComing.length) 14801 charsComing.length = charsComingPosition + 8; 14802 14803 charsComing[charsComingPosition++] = ch; 14804 } 14805 14806 charsComing = charsComing[0 .. charsComingPosition]; 14807 } else { 14808 charsComing = null; 14809 } 14810 14811 ke.charsPossible = charsComing; 14812 14813 if (win.handleKeyEvent) { 14814 XUnlockDisplay(display); 14815 scope(exit) XLockDisplay(display); 14816 win.handleKeyEvent(ke); 14817 } 14818 14819 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 14820 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 14821 XUnlockDisplay(display); 14822 scope(exit) XLockDisplay(display); 14823 foreach(ch; charsComing) 14824 win.handleCharEvent(ch); 14825 } 14826 } 14827 14828 version(with_eventloop) 14829 send(ke); 14830 break; 14831 default: 14832 } 14833 14834 return done; 14835 } 14836 } 14837 14838 /* *************************************** */ 14839 /* Done with simpledisplay stuff */ 14840 /* *************************************** */ 14841 14842 // Necessary C library bindings follow 14843 version(Windows) {} else 14844 version(X11) { 14845 14846 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 14847 14848 // X11 bindings needed here 14849 /* 14850 A little of this is from the bindings project on 14851 D Source and some of it is copy/paste from the C 14852 header. 14853 14854 The DSource listing consistently used D's long 14855 where C used long. That's wrong - C long is 32 bit, so 14856 it should be int in D. I changed that here. 14857 14858 Note: 14859 This isn't complete, just took what I needed for myself. 14860 */ 14861 14862 import core.stdc.stddef : wchar_t; 14863 14864 interface XLib { 14865 extern(C) nothrow @nogc { 14866 char* XResourceManagerString(Display*); 14867 void XrmInitialize(); 14868 XrmDatabase XrmGetStringDatabase(char* data); 14869 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 14870 14871 Cursor XCreateFontCursor(Display*, uint shape); 14872 int XDefineCursor(Display* display, Window w, Cursor cursor); 14873 int XUndefineCursor(Display* display, Window w); 14874 14875 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 14876 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 14877 int XFreeCursor(Display* display, Cursor cursor); 14878 14879 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 14880 14881 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 14882 14883 char *XKeysymToString(KeySym keysym); 14884 KeySym XKeycodeToKeysym( 14885 Display* /* display */, 14886 KeyCode /* keycode */, 14887 int /* index */ 14888 ); 14889 14890 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 14891 14892 int XFree(void*); 14893 int XDeleteProperty(Display *display, Window w, Atom property); 14894 14895 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements); 14896 14897 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 14898 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 14899 *actual_type_return, int *actual_format_return, arch_ulong 14900 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 14901 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 14902 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 14903 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 14904 14905 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 14906 14907 Window XGetSelectionOwner(Display *display, Atom selection); 14908 14909 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 14910 14911 char** XListFonts(Display*, const char*, int, int*); 14912 void XFreeFontNames(char**); 14913 14914 Display* XOpenDisplay(const char*); 14915 int XCloseDisplay(Display*); 14916 14917 int XSynchronize(Display*, bool); 14918 14919 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 14920 14921 Bool XSupportsLocale(); 14922 char* XSetLocaleModifiers(const(char)* modifier_list); 14923 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 14924 Status XCloseOM(XOM om); 14925 14926 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 14927 Status XCloseIM(XIM im); 14928 14929 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 14930 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 14931 Display* XDisplayOfIM(XIM im); 14932 char* XLocaleOfIM(XIM im); 14933 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 14934 void XDestroyIC(XIC ic); 14935 void XSetICFocus(XIC ic); 14936 void XUnsetICFocus(XIC ic); 14937 //wchar_t* XwcResetIC(XIC ic); 14938 char* XmbResetIC(XIC ic); 14939 char* Xutf8ResetIC(XIC ic); 14940 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 14941 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 14942 XIM XIMOfIC(XIC ic); 14943 14944 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 14945 14946 14947 XFontStruct *XLoadQueryFont(Display *display, in char *name); 14948 int XFreeFont(Display *display, XFontStruct *font_struct); 14949 int XSetFont(Display* display, GC gc, Font font); 14950 int XTextWidth(XFontStruct*, in char*, int); 14951 14952 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 14953 int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n); 14954 14955 Window XCreateSimpleWindow( 14956 Display* /* display */, 14957 Window /* parent */, 14958 int /* x */, 14959 int /* y */, 14960 uint /* width */, 14961 uint /* height */, 14962 uint /* border_width */, 14963 uint /* border */, 14964 uint /* background */ 14965 ); 14966 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); 14967 14968 int XReparentWindow(Display*, Window, Window, int, int); 14969 int XClearWindow(Display*, Window); 14970 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 14971 int XMoveWindow(Display*, Window, int, int); 14972 int XResizeWindow(Display *display, Window w, uint width, uint height); 14973 14974 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 14975 14976 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 14977 14978 XImage *XCreateImage( 14979 Display* /* display */, 14980 Visual* /* visual */, 14981 uint /* depth */, 14982 int /* format */, 14983 int /* offset */, 14984 ubyte* /* data */, 14985 uint /* width */, 14986 uint /* height */, 14987 int /* bitmap_pad */, 14988 int /* bytes_per_line */ 14989 ); 14990 14991 Status XInitImage (XImage* image); 14992 14993 Atom XInternAtom( 14994 Display* /* display */, 14995 const char* /* atom_name */, 14996 Bool /* only_if_exists */ 14997 ); 14998 14999 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 15000 char* XGetAtomName(Display*, Atom); 15001 Status XGetAtomNames(Display*, Atom*, int count, char**); 15002 15003 int XPutImage( 15004 Display* /* display */, 15005 Drawable /* d */, 15006 GC /* gc */, 15007 XImage* /* image */, 15008 int /* src_x */, 15009 int /* src_y */, 15010 int /* dest_x */, 15011 int /* dest_y */, 15012 uint /* width */, 15013 uint /* height */ 15014 ); 15015 15016 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 15017 15018 15019 int XDestroyWindow( 15020 Display* /* display */, 15021 Window /* w */ 15022 ); 15023 15024 int XDestroyImage(XImage*); 15025 15026 int XSelectInput( 15027 Display* /* display */, 15028 Window /* w */, 15029 EventMask /* event_mask */ 15030 ); 15031 15032 int XMapWindow( 15033 Display* /* display */, 15034 Window /* w */ 15035 ); 15036 15037 Status XIconifyWindow(Display*, Window, int); 15038 int XMapRaised(Display*, Window); 15039 int XMapSubwindows(Display*, Window); 15040 15041 int XNextEvent( 15042 Display* /* display */, 15043 XEvent* /* event_return */ 15044 ); 15045 15046 int XMaskEvent(Display*, arch_long, XEvent*); 15047 15048 Bool XFilterEvent(XEvent *event, Window window); 15049 int XRefreshKeyboardMapping(XMappingEvent *event_map); 15050 15051 Status XSetWMProtocols( 15052 Display* /* display */, 15053 Window /* w */, 15054 Atom* /* protocols */, 15055 int /* count */ 15056 ); 15057 15058 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 15059 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 15060 15061 15062 Status XInitThreads(); 15063 void XLockDisplay (Display* display); 15064 void XUnlockDisplay (Display* display); 15065 15066 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 15067 15068 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 15069 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 15070 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 15071 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 15072 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 15073 15074 15075 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 15076 int XDrawString(Display*, Drawable, GC, int, int, in char*, int); 15077 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 15078 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 15079 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 15080 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 15081 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 15082 int XDrawPoint(Display*, Drawable, GC, int, int); 15083 int XSetForeground(Display*, GC, uint); 15084 int XSetBackground(Display*, GC, uint); 15085 15086 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 15087 void XFreeFontSet(Display*, XFontSet); 15088 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int); 15089 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 15090 15091 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 15092 15093 15094 //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); 15095 15096 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 15097 int XSetFunction(Display*, GC, int); 15098 15099 GC XCreateGC(Display*, Drawable, uint, void*); 15100 int XCopyGC(Display*, GC, uint, GC); 15101 int XFreeGC(Display*, GC); 15102 15103 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 15104 bool XCheckMaskEvent(Display*, int, XEvent*); 15105 15106 int XPending(Display*); 15107 int XEventsQueued(Display* display, int mode); 15108 15109 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 15110 int XFreePixmap(Display*, Pixmap); 15111 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 15112 int XFlush(Display*); 15113 int XBell(Display*, int); 15114 int XSync(Display*, bool); 15115 15116 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 15117 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 15118 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 15119 15120 KeySym XStringToKeysym(const char *string); 15121 15122 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 15123 15124 Window XDefaultRootWindow(Display*); 15125 15126 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); 15127 15128 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 15129 15130 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 15131 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 15132 15133 Status XAllocColor(Display*, Colormap, XColor*); 15134 15135 int XWithdrawWindow(Display*, Window, int); 15136 int XUnmapWindow(Display*, Window); 15137 int XLowerWindow(Display*, Window); 15138 int XRaiseWindow(Display*, Window); 15139 15140 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); 15141 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); 15142 15143 int XGetInputFocus(Display*, Window*, int*); 15144 int XSetInputFocus(Display*, Window, int, Time); 15145 15146 XErrorHandler XSetErrorHandler(XErrorHandler); 15147 15148 int XGetErrorText(Display*, int, char*, int); 15149 15150 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 15151 15152 15153 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); 15154 int XUngrabPointer(Display *display, Time time); 15155 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 15156 15157 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 15158 15159 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 15160 int XSetClipMask(Display*, GC, Pixmap); 15161 int XSetClipOrigin(Display*, GC, int, int); 15162 15163 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 15164 15165 void XSetWMName(Display*, Window, XTextProperty*); 15166 Status XGetWMName(Display*, Window, XTextProperty*); 15167 int XStoreName(Display* display, Window w, const(char)* window_name); 15168 15169 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 15170 15171 } 15172 } 15173 15174 interface Xext { 15175 extern(C) nothrow @nogc { 15176 Status XShmAttach(Display*, XShmSegmentInfo*); 15177 Status XShmDetach(Display*, XShmSegmentInfo*); 15178 Status XShmPutImage( 15179 Display* /* dpy */, 15180 Drawable /* d */, 15181 GC /* gc */, 15182 XImage* /* image */, 15183 int /* src_x */, 15184 int /* src_y */, 15185 int /* dst_x */, 15186 int /* dst_y */, 15187 uint /* src_width */, 15188 uint /* src_height */, 15189 Bool /* send_event */ 15190 ); 15191 15192 Status XShmQueryExtension(Display*); 15193 15194 XImage *XShmCreateImage( 15195 Display* /* dpy */, 15196 Visual* /* visual */, 15197 uint /* depth */, 15198 int /* format */, 15199 char* /* data */, 15200 XShmSegmentInfo* /* shminfo */, 15201 uint /* width */, 15202 uint /* height */ 15203 ); 15204 15205 Pixmap XShmCreatePixmap( 15206 Display* /* dpy */, 15207 Drawable /* d */, 15208 char* /* data */, 15209 XShmSegmentInfo* /* shminfo */, 15210 uint /* width */, 15211 uint /* height */, 15212 uint /* depth */ 15213 ); 15214 15215 } 15216 } 15217 15218 // this requires -lXpm 15219 //int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 15220 15221 15222 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 15223 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 15224 shared static this() { 15225 xlib.loadDynamicLibrary(); 15226 xext.loadDynamicLibrary(); 15227 } 15228 15229 15230 extern(C) nothrow @nogc { 15231 15232 alias XrmDatabase = void*; 15233 struct XrmValue { 15234 uint size; 15235 void* addr; 15236 } 15237 15238 struct XVisualInfo { 15239 Visual* visual; 15240 VisualID visualid; 15241 int screen; 15242 uint depth; 15243 int c_class; 15244 c_ulong red_mask; 15245 c_ulong green_mask; 15246 c_ulong blue_mask; 15247 int colormap_size; 15248 int bits_per_rgb; 15249 } 15250 15251 enum VisualNoMask= 0x0; 15252 enum VisualIDMask= 0x1; 15253 enum VisualScreenMask=0x2; 15254 enum VisualDepthMask= 0x4; 15255 enum VisualClassMask= 0x8; 15256 enum VisualRedMaskMask=0x10; 15257 enum VisualGreenMaskMask=0x20; 15258 enum VisualBlueMaskMask=0x40; 15259 enum VisualColormapSizeMask=0x80; 15260 enum VisualBitsPerRGBMask=0x100; 15261 enum VisualAllMask= 0x1FF; 15262 15263 15264 // XIM and other crap 15265 struct _XOM {} 15266 struct _XIM {} 15267 struct _XIC {} 15268 alias XOM = _XOM*; 15269 alias XIM = _XIM*; 15270 alias XIC = _XIC*; 15271 15272 alias XIMStyle = arch_ulong; 15273 enum : arch_ulong { 15274 XIMPreeditArea = 0x0001, 15275 XIMPreeditCallbacks = 0x0002, 15276 XIMPreeditPosition = 0x0004, 15277 XIMPreeditNothing = 0x0008, 15278 XIMPreeditNone = 0x0010, 15279 XIMStatusArea = 0x0100, 15280 XIMStatusCallbacks = 0x0200, 15281 XIMStatusNothing = 0x0400, 15282 XIMStatusNone = 0x0800, 15283 } 15284 15285 15286 /* X Shared Memory Extension functions */ 15287 //pragma(lib, "Xshm"); 15288 alias arch_ulong ShmSeg; 15289 struct XShmSegmentInfo { 15290 ShmSeg shmseg; 15291 int shmid; 15292 ubyte* shmaddr; 15293 Bool readOnly; 15294 } 15295 15296 // and the necessary OS functions 15297 int shmget(int, size_t, int); 15298 void* shmat(int, in void*, int); 15299 int shmdt(in void*); 15300 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 15301 15302 enum IPC_PRIVATE = 0; 15303 enum IPC_CREAT = 512; 15304 enum IPC_RMID = 0; 15305 15306 /* MIT-SHM end */ 15307 15308 15309 enum MappingType:int { 15310 MappingModifier =0, 15311 MappingKeyboard =1, 15312 MappingPointer =2 15313 } 15314 15315 /* ImageFormat -- PutImage, GetImage */ 15316 enum ImageFormat:int { 15317 XYBitmap =0, /* depth 1, XYFormat */ 15318 XYPixmap =1, /* depth == drawable depth */ 15319 ZPixmap =2 /* depth == drawable depth */ 15320 } 15321 15322 enum ModifierName:int { 15323 ShiftMapIndex =0, 15324 LockMapIndex =1, 15325 ControlMapIndex =2, 15326 Mod1MapIndex =3, 15327 Mod2MapIndex =4, 15328 Mod3MapIndex =5, 15329 Mod4MapIndex =6, 15330 Mod5MapIndex =7 15331 } 15332 15333 enum ButtonMask:int { 15334 Button1Mask =1<<8, 15335 Button2Mask =1<<9, 15336 Button3Mask =1<<10, 15337 Button4Mask =1<<11, 15338 Button5Mask =1<<12, 15339 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 15340 } 15341 15342 enum KeyOrButtonMask:uint { 15343 ShiftMask =1<<0, 15344 LockMask =1<<1, 15345 ControlMask =1<<2, 15346 Mod1Mask =1<<3, 15347 Mod2Mask =1<<4, 15348 Mod3Mask =1<<5, 15349 Mod4Mask =1<<6, 15350 Mod5Mask =1<<7, 15351 Button1Mask =1<<8, 15352 Button2Mask =1<<9, 15353 Button3Mask =1<<10, 15354 Button4Mask =1<<11, 15355 Button5Mask =1<<12, 15356 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 15357 } 15358 15359 enum ButtonName:int { 15360 Button1 =1, 15361 Button2 =2, 15362 Button3 =3, 15363 Button4 =4, 15364 Button5 =5 15365 } 15366 15367 /* Notify modes */ 15368 enum NotifyModes:int 15369 { 15370 NotifyNormal =0, 15371 NotifyGrab =1, 15372 NotifyUngrab =2, 15373 NotifyWhileGrabbed =3 15374 } 15375 enum NotifyHint = 1; /* for MotionNotify events */ 15376 15377 /* Notify detail */ 15378 enum NotifyDetail:int 15379 { 15380 NotifyAncestor =0, 15381 NotifyVirtual =1, 15382 NotifyInferior =2, 15383 NotifyNonlinear =3, 15384 NotifyNonlinearVirtual =4, 15385 NotifyPointer =5, 15386 NotifyPointerRoot =6, 15387 NotifyDetailNone =7 15388 } 15389 15390 /* Visibility notify */ 15391 15392 enum VisibilityNotify:int 15393 { 15394 VisibilityUnobscured =0, 15395 VisibilityPartiallyObscured =1, 15396 VisibilityFullyObscured =2 15397 } 15398 15399 15400 enum WindowStackingMethod:int 15401 { 15402 Above =0, 15403 Below =1, 15404 TopIf =2, 15405 BottomIf =3, 15406 Opposite =4 15407 } 15408 15409 /* Circulation request */ 15410 enum CirculationRequest:int 15411 { 15412 PlaceOnTop =0, 15413 PlaceOnBottom =1 15414 } 15415 15416 enum PropertyNotification:int 15417 { 15418 PropertyNewValue =0, 15419 PropertyDelete =1 15420 } 15421 15422 enum ColorMapNotification:int 15423 { 15424 ColormapUninstalled =0, 15425 ColormapInstalled =1 15426 } 15427 15428 15429 struct _XPrivate {} 15430 struct _XrmHashBucketRec {} 15431 15432 alias void* XPointer; 15433 alias void* XExtData; 15434 15435 version( X86_64 ) { 15436 alias ulong XID; 15437 alias ulong arch_ulong; 15438 alias long arch_long; 15439 } else version (AArch64) { 15440 alias ulong XID; 15441 alias ulong arch_ulong; 15442 alias long arch_long; 15443 } else { 15444 alias uint XID; 15445 alias uint arch_ulong; 15446 alias int arch_long; 15447 } 15448 15449 alias XID Window; 15450 alias XID Drawable; 15451 alias XID Pixmap; 15452 15453 alias arch_ulong Atom; 15454 alias int Bool; 15455 alias Display XDisplay; 15456 15457 alias int ByteOrder; 15458 alias arch_ulong Time; 15459 alias void ScreenFormat; 15460 15461 struct XImage { 15462 int width, height; /* size of image */ 15463 int xoffset; /* number of pixels offset in X direction */ 15464 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 15465 void *data; /* pointer to image data */ 15466 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 15467 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 15468 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 15469 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 15470 int depth; /* depth of image */ 15471 int bytes_per_line; /* accelarator to next line */ 15472 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 15473 arch_ulong red_mask; /* bits in z arrangment */ 15474 arch_ulong green_mask; 15475 arch_ulong blue_mask; 15476 XPointer obdata; /* hook for the object routines to hang on */ 15477 static struct F { /* image manipulation routines */ 15478 XImage* function( 15479 XDisplay* /* display */, 15480 Visual* /* visual */, 15481 uint /* depth */, 15482 int /* format */, 15483 int /* offset */, 15484 ubyte* /* data */, 15485 uint /* width */, 15486 uint /* height */, 15487 int /* bitmap_pad */, 15488 int /* bytes_per_line */) create_image; 15489 int function(XImage *) destroy_image; 15490 arch_ulong function(XImage *, int, int) get_pixel; 15491 int function(XImage *, int, int, arch_ulong) put_pixel; 15492 XImage* function(XImage *, int, int, uint, uint) sub_image; 15493 int function(XImage *, arch_long) add_pixel; 15494 } 15495 F f; 15496 } 15497 version(X86_64) static assert(XImage.sizeof == 136); 15498 else version(X86) static assert(XImage.sizeof == 88); 15499 15500 struct XCharStruct { 15501 short lbearing; /* origin to left edge of raster */ 15502 short rbearing; /* origin to right edge of raster */ 15503 short width; /* advance to next char's origin */ 15504 short ascent; /* baseline to top edge of raster */ 15505 short descent; /* baseline to bottom edge of raster */ 15506 ushort attributes; /* per char flags (not predefined) */ 15507 } 15508 15509 /* 15510 * To allow arbitrary information with fonts, there are additional properties 15511 * returned. 15512 */ 15513 struct XFontProp { 15514 Atom name; 15515 arch_ulong card32; 15516 } 15517 15518 alias Atom Font; 15519 15520 struct XFontStruct { 15521 XExtData *ext_data; /* Hook for extension to hang data */ 15522 Font fid; /* Font ID for this font */ 15523 uint direction; /* Direction the font is painted */ 15524 uint min_char_or_byte2; /* First character */ 15525 uint max_char_or_byte2; /* Last character */ 15526 uint min_byte1; /* First row that exists (for two-byte fonts) */ 15527 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 15528 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 15529 uint default_char; /* Char to print for undefined character */ 15530 int n_properties; /* How many properties there are */ 15531 XFontProp *properties; /* Pointer to array of additional properties*/ 15532 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 15533 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 15534 XCharStruct *per_char; /* first_char to last_char information */ 15535 int ascent; /* Max extent above baseline for spacing */ 15536 int descent; /* Max descent below baseline for spacing */ 15537 } 15538 15539 15540 /* 15541 * Definitions of specific events. 15542 */ 15543 struct XKeyEvent 15544 { 15545 int type; /* of event */ 15546 arch_ulong serial; /* # of last request processed by server */ 15547 Bool send_event; /* true if this came from a SendEvent request */ 15548 Display *display; /* Display the event was read from */ 15549 Window window; /* "event" window it is reported relative to */ 15550 Window root; /* root window that the event occurred on */ 15551 Window subwindow; /* child window */ 15552 Time time; /* milliseconds */ 15553 int x, y; /* pointer x, y coordinates in event window */ 15554 int x_root, y_root; /* coordinates relative to root */ 15555 KeyOrButtonMask state; /* key or button mask */ 15556 uint keycode; /* detail */ 15557 Bool same_screen; /* same screen flag */ 15558 } 15559 version(X86_64) static assert(XKeyEvent.sizeof == 96); 15560 alias XKeyEvent XKeyPressedEvent; 15561 alias XKeyEvent XKeyReleasedEvent; 15562 15563 struct XButtonEvent 15564 { 15565 int type; /* of event */ 15566 arch_ulong serial; /* # of last request processed by server */ 15567 Bool send_event; /* true if this came from a SendEvent request */ 15568 Display *display; /* Display the event was read from */ 15569 Window window; /* "event" window it is reported relative to */ 15570 Window root; /* root window that the event occurred on */ 15571 Window subwindow; /* child window */ 15572 Time time; /* milliseconds */ 15573 int x, y; /* pointer x, y coordinates in event window */ 15574 int x_root, y_root; /* coordinates relative to root */ 15575 KeyOrButtonMask state; /* key or button mask */ 15576 uint button; /* detail */ 15577 Bool same_screen; /* same screen flag */ 15578 } 15579 alias XButtonEvent XButtonPressedEvent; 15580 alias XButtonEvent XButtonReleasedEvent; 15581 15582 struct XMotionEvent{ 15583 int type; /* of event */ 15584 arch_ulong serial; /* # of last request processed by server */ 15585 Bool send_event; /* true if this came from a SendEvent request */ 15586 Display *display; /* Display the event was read from */ 15587 Window window; /* "event" window reported relative to */ 15588 Window root; /* root window that the event occurred on */ 15589 Window subwindow; /* child window */ 15590 Time time; /* milliseconds */ 15591 int x, y; /* pointer x, y coordinates in event window */ 15592 int x_root, y_root; /* coordinates relative to root */ 15593 KeyOrButtonMask state; /* key or button mask */ 15594 byte is_hint; /* detail */ 15595 Bool same_screen; /* same screen flag */ 15596 } 15597 alias XMotionEvent XPointerMovedEvent; 15598 15599 struct XCrossingEvent{ 15600 int type; /* of event */ 15601 arch_ulong serial; /* # of last request processed by server */ 15602 Bool send_event; /* true if this came from a SendEvent request */ 15603 Display *display; /* Display the event was read from */ 15604 Window window; /* "event" window reported relative to */ 15605 Window root; /* root window that the event occurred on */ 15606 Window subwindow; /* child window */ 15607 Time time; /* milliseconds */ 15608 int x, y; /* pointer x, y coordinates in event window */ 15609 int x_root, y_root; /* coordinates relative to root */ 15610 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 15611 NotifyDetail detail; 15612 /* 15613 * NotifyAncestor, NotifyVirtual, NotifyInferior, 15614 * NotifyNonlinear,NotifyNonlinearVirtual 15615 */ 15616 Bool same_screen; /* same screen flag */ 15617 Bool focus; /* Boolean focus */ 15618 KeyOrButtonMask state; /* key or button mask */ 15619 } 15620 alias XCrossingEvent XEnterWindowEvent; 15621 alias XCrossingEvent XLeaveWindowEvent; 15622 15623 struct XFocusChangeEvent{ 15624 int type; /* FocusIn or FocusOut */ 15625 arch_ulong serial; /* # of last request processed by server */ 15626 Bool send_event; /* true if this came from a SendEvent request */ 15627 Display *display; /* Display the event was read from */ 15628 Window window; /* window of event */ 15629 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 15630 NotifyGrab, NotifyUngrab */ 15631 NotifyDetail detail; 15632 /* 15633 * NotifyAncestor, NotifyVirtual, NotifyInferior, 15634 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 15635 * NotifyPointerRoot, NotifyDetailNone 15636 */ 15637 } 15638 alias XFocusChangeEvent XFocusInEvent; 15639 alias XFocusChangeEvent XFocusOutEvent; 15640 15641 enum CWBackPixmap = (1L<<0); 15642 enum CWBackPixel = (1L<<1); 15643 enum CWBorderPixmap = (1L<<2); 15644 enum CWBorderPixel = (1L<<3); 15645 enum CWBitGravity = (1L<<4); 15646 enum CWWinGravity = (1L<<5); 15647 enum CWBackingStore = (1L<<6); 15648 enum CWBackingPlanes = (1L<<7); 15649 enum CWBackingPixel = (1L<<8); 15650 enum CWOverrideRedirect = (1L<<9); 15651 enum CWSaveUnder = (1L<<10); 15652 enum CWEventMask = (1L<<11); 15653 enum CWDontPropagate = (1L<<12); 15654 enum CWColormap = (1L<<13); 15655 enum CWCursor = (1L<<14); 15656 15657 struct XWindowAttributes { 15658 int x, y; /* location of window */ 15659 int width, height; /* width and height of window */ 15660 int border_width; /* border width of window */ 15661 int depth; /* depth of window */ 15662 Visual *visual; /* the associated visual structure */ 15663 Window root; /* root of screen containing window */ 15664 int class_; /* InputOutput, InputOnly*/ 15665 int bit_gravity; /* one of the bit gravity values */ 15666 int win_gravity; /* one of the window gravity values */ 15667 int backing_store; /* NotUseful, WhenMapped, Always */ 15668 arch_ulong backing_planes; /* planes to be preserved if possible */ 15669 arch_ulong backing_pixel; /* value to be used when restoring planes */ 15670 Bool save_under; /* boolean, should bits under be saved? */ 15671 Colormap colormap; /* color map to be associated with window */ 15672 Bool map_installed; /* boolean, is color map currently installed*/ 15673 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 15674 arch_long all_event_masks; /* set of events all people have interest in*/ 15675 arch_long your_event_mask; /* my event mask */ 15676 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 15677 Bool override_redirect; /* boolean value for override-redirect */ 15678 Screen *screen; /* back pointer to correct screen */ 15679 } 15680 15681 enum IsUnmapped = 0; 15682 enum IsUnviewable = 1; 15683 enum IsViewable = 2; 15684 15685 struct XSetWindowAttributes { 15686 Pixmap background_pixmap;/* background, None, or ParentRelative */ 15687 arch_ulong background_pixel;/* background pixel */ 15688 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 15689 arch_ulong border_pixel;/* border pixel value */ 15690 int bit_gravity; /* one of bit gravity values */ 15691 int win_gravity; /* one of the window gravity values */ 15692 int backing_store; /* NotUseful, WhenMapped, Always */ 15693 arch_ulong backing_planes;/* planes to be preserved if possible */ 15694 arch_ulong backing_pixel;/* value to use in restoring planes */ 15695 Bool save_under; /* should bits under be saved? (popups) */ 15696 arch_long event_mask; /* set of events that should be saved */ 15697 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 15698 Bool override_redirect; /* boolean value for override_redirect */ 15699 Colormap colormap; /* color map to be associated with window */ 15700 Cursor cursor; /* cursor to be displayed (or None) */ 15701 } 15702 15703 15704 alias int Status; 15705 15706 15707 enum EventMask:int 15708 { 15709 NoEventMask =0, 15710 KeyPressMask =1<<0, 15711 KeyReleaseMask =1<<1, 15712 ButtonPressMask =1<<2, 15713 ButtonReleaseMask =1<<3, 15714 EnterWindowMask =1<<4, 15715 LeaveWindowMask =1<<5, 15716 PointerMotionMask =1<<6, 15717 PointerMotionHintMask =1<<7, 15718 Button1MotionMask =1<<8, 15719 Button2MotionMask =1<<9, 15720 Button3MotionMask =1<<10, 15721 Button4MotionMask =1<<11, 15722 Button5MotionMask =1<<12, 15723 ButtonMotionMask =1<<13, 15724 KeymapStateMask =1<<14, 15725 ExposureMask =1<<15, 15726 VisibilityChangeMask =1<<16, 15727 StructureNotifyMask =1<<17, 15728 ResizeRedirectMask =1<<18, 15729 SubstructureNotifyMask =1<<19, 15730 SubstructureRedirectMask=1<<20, 15731 FocusChangeMask =1<<21, 15732 PropertyChangeMask =1<<22, 15733 ColormapChangeMask =1<<23, 15734 OwnerGrabButtonMask =1<<24 15735 } 15736 15737 struct MwmHints { 15738 c_ulong flags; 15739 c_ulong functions; 15740 c_ulong decorations; 15741 c_long input_mode; 15742 c_ulong status; 15743 } 15744 15745 enum { 15746 MWM_HINTS_FUNCTIONS = (1L << 0), 15747 MWM_HINTS_DECORATIONS = (1L << 1), 15748 15749 MWM_FUNC_ALL = (1L << 0), 15750 MWM_FUNC_RESIZE = (1L << 1), 15751 MWM_FUNC_MOVE = (1L << 2), 15752 MWM_FUNC_MINIMIZE = (1L << 3), 15753 MWM_FUNC_MAXIMIZE = (1L << 4), 15754 MWM_FUNC_CLOSE = (1L << 5), 15755 15756 MWM_DECOR_ALL = (1L << 0), 15757 MWM_DECOR_BORDER = (1L << 1), 15758 MWM_DECOR_RESIZEH = (1L << 2), 15759 MWM_DECOR_TITLE = (1L << 3), 15760 MWM_DECOR_MENU = (1L << 4), 15761 MWM_DECOR_MINIMIZE = (1L << 5), 15762 MWM_DECOR_MAXIMIZE = (1L << 6), 15763 } 15764 15765 import core.stdc.config : c_long, c_ulong; 15766 15767 /* Size hints mask bits */ 15768 15769 enum USPosition = (1L << 0) /* user specified x, y */; 15770 enum USSize = (1L << 1) /* user specified width, height */; 15771 enum PPosition = (1L << 2) /* program specified position */; 15772 enum PSize = (1L << 3) /* program specified size */; 15773 enum PMinSize = (1L << 4) /* program specified minimum size */; 15774 enum PMaxSize = (1L << 5) /* program specified maximum size */; 15775 enum PResizeInc = (1L << 6) /* program specified resize increments */; 15776 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 15777 enum PBaseSize = (1L << 8); 15778 enum PWinGravity = (1L << 9); 15779 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 15780 struct XSizeHints { 15781 arch_long flags; /* marks which fields in this structure are defined */ 15782 int x, y; /* Obsolete */ 15783 int width, height; /* Obsolete */ 15784 int min_width, min_height; 15785 int max_width, max_height; 15786 int width_inc, height_inc; 15787 struct Aspect { 15788 int x; /* numerator */ 15789 int y; /* denominator */ 15790 } 15791 15792 Aspect min_aspect; 15793 Aspect max_aspect; 15794 int base_width, base_height; 15795 int win_gravity; 15796 /* this structure may be extended in the future */ 15797 } 15798 15799 15800 15801 enum EventType:int 15802 { 15803 KeyPress =2, 15804 KeyRelease =3, 15805 ButtonPress =4, 15806 ButtonRelease =5, 15807 MotionNotify =6, 15808 EnterNotify =7, 15809 LeaveNotify =8, 15810 FocusIn =9, 15811 FocusOut =10, 15812 KeymapNotify =11, 15813 Expose =12, 15814 GraphicsExpose =13, 15815 NoExpose =14, 15816 VisibilityNotify =15, 15817 CreateNotify =16, 15818 DestroyNotify =17, 15819 UnmapNotify =18, 15820 MapNotify =19, 15821 MapRequest =20, 15822 ReparentNotify =21, 15823 ConfigureNotify =22, 15824 ConfigureRequest =23, 15825 GravityNotify =24, 15826 ResizeRequest =25, 15827 CirculateNotify =26, 15828 CirculateRequest =27, 15829 PropertyNotify =28, 15830 SelectionClear =29, 15831 SelectionRequest =30, 15832 SelectionNotify =31, 15833 ColormapNotify =32, 15834 ClientMessage =33, 15835 MappingNotify =34, 15836 LASTEvent =35 /* must be bigger than any event # */ 15837 } 15838 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 15839 struct XKeymapEvent 15840 { 15841 int type; 15842 arch_ulong serial; /* # of last request processed by server */ 15843 Bool send_event; /* true if this came from a SendEvent request */ 15844 Display *display; /* Display the event was read from */ 15845 Window window; 15846 byte[32] key_vector; 15847 } 15848 15849 struct XExposeEvent 15850 { 15851 int type; 15852 arch_ulong serial; /* # of last request processed by server */ 15853 Bool send_event; /* true if this came from a SendEvent request */ 15854 Display *display; /* Display the event was read from */ 15855 Window window; 15856 int x, y; 15857 int width, height; 15858 int count; /* if non-zero, at least this many more */ 15859 } 15860 15861 struct XGraphicsExposeEvent{ 15862 int type; 15863 arch_ulong serial; /* # of last request processed by server */ 15864 Bool send_event; /* true if this came from a SendEvent request */ 15865 Display *display; /* Display the event was read from */ 15866 Drawable drawable; 15867 int x, y; 15868 int width, height; 15869 int count; /* if non-zero, at least this many more */ 15870 int major_code; /* core is CopyArea or CopyPlane */ 15871 int minor_code; /* not defined in the core */ 15872 } 15873 15874 struct XNoExposeEvent{ 15875 int type; 15876 arch_ulong serial; /* # of last request processed by server */ 15877 Bool send_event; /* true if this came from a SendEvent request */ 15878 Display *display; /* Display the event was read from */ 15879 Drawable drawable; 15880 int major_code; /* core is CopyArea or CopyPlane */ 15881 int minor_code; /* not defined in the core */ 15882 } 15883 15884 struct XVisibilityEvent{ 15885 int type; 15886 arch_ulong serial; /* # of last request processed by server */ 15887 Bool send_event; /* true if this came from a SendEvent request */ 15888 Display *display; /* Display the event was read from */ 15889 Window window; 15890 VisibilityNotify state; /* Visibility state */ 15891 } 15892 15893 struct XCreateWindowEvent{ 15894 int type; 15895 arch_ulong serial; /* # of last request processed by server */ 15896 Bool send_event; /* true if this came from a SendEvent request */ 15897 Display *display; /* Display the event was read from */ 15898 Window parent; /* parent of the window */ 15899 Window window; /* window id of window created */ 15900 int x, y; /* window location */ 15901 int width, height; /* size of window */ 15902 int border_width; /* border width */ 15903 Bool override_redirect; /* creation should be overridden */ 15904 } 15905 15906 struct XDestroyWindowEvent 15907 { 15908 int type; 15909 arch_ulong serial; /* # of last request processed by server */ 15910 Bool send_event; /* true if this came from a SendEvent request */ 15911 Display *display; /* Display the event was read from */ 15912 Window event; 15913 Window window; 15914 } 15915 15916 struct XUnmapEvent 15917 { 15918 int type; 15919 arch_ulong serial; /* # of last request processed by server */ 15920 Bool send_event; /* true if this came from a SendEvent request */ 15921 Display *display; /* Display the event was read from */ 15922 Window event; 15923 Window window; 15924 Bool from_configure; 15925 } 15926 15927 struct XMapEvent 15928 { 15929 int type; 15930 arch_ulong serial; /* # of last request processed by server */ 15931 Bool send_event; /* true if this came from a SendEvent request */ 15932 Display *display; /* Display the event was read from */ 15933 Window event; 15934 Window window; 15935 Bool override_redirect; /* Boolean, is override set... */ 15936 } 15937 15938 struct XMapRequestEvent 15939 { 15940 int type; 15941 arch_ulong serial; /* # of last request processed by server */ 15942 Bool send_event; /* true if this came from a SendEvent request */ 15943 Display *display; /* Display the event was read from */ 15944 Window parent; 15945 Window window; 15946 } 15947 15948 struct XReparentEvent 15949 { 15950 int type; 15951 arch_ulong serial; /* # of last request processed by server */ 15952 Bool send_event; /* true if this came from a SendEvent request */ 15953 Display *display; /* Display the event was read from */ 15954 Window event; 15955 Window window; 15956 Window parent; 15957 int x, y; 15958 Bool override_redirect; 15959 } 15960 15961 struct XConfigureEvent 15962 { 15963 int type; 15964 arch_ulong serial; /* # of last request processed by server */ 15965 Bool send_event; /* true if this came from a SendEvent request */ 15966 Display *display; /* Display the event was read from */ 15967 Window event; 15968 Window window; 15969 int x, y; 15970 int width, height; 15971 int border_width; 15972 Window above; 15973 Bool override_redirect; 15974 } 15975 15976 struct XGravityEvent 15977 { 15978 int type; 15979 arch_ulong serial; /* # of last request processed by server */ 15980 Bool send_event; /* true if this came from a SendEvent request */ 15981 Display *display; /* Display the event was read from */ 15982 Window event; 15983 Window window; 15984 int x, y; 15985 } 15986 15987 struct XResizeRequestEvent 15988 { 15989 int type; 15990 arch_ulong serial; /* # of last request processed by server */ 15991 Bool send_event; /* true if this came from a SendEvent request */ 15992 Display *display; /* Display the event was read from */ 15993 Window window; 15994 int width, height; 15995 } 15996 15997 struct XConfigureRequestEvent 15998 { 15999 int type; 16000 arch_ulong serial; /* # of last request processed by server */ 16001 Bool send_event; /* true if this came from a SendEvent request */ 16002 Display *display; /* Display the event was read from */ 16003 Window parent; 16004 Window window; 16005 int x, y; 16006 int width, height; 16007 int border_width; 16008 Window above; 16009 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 16010 arch_ulong value_mask; 16011 } 16012 16013 struct XCirculateEvent 16014 { 16015 int type; 16016 arch_ulong serial; /* # of last request processed by server */ 16017 Bool send_event; /* true if this came from a SendEvent request */ 16018 Display *display; /* Display the event was read from */ 16019 Window event; 16020 Window window; 16021 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 16022 } 16023 16024 struct XCirculateRequestEvent 16025 { 16026 int type; 16027 arch_ulong serial; /* # of last request processed by server */ 16028 Bool send_event; /* true if this came from a SendEvent request */ 16029 Display *display; /* Display the event was read from */ 16030 Window parent; 16031 Window window; 16032 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 16033 } 16034 16035 struct XPropertyEvent 16036 { 16037 int type; 16038 arch_ulong serial; /* # of last request processed by server */ 16039 Bool send_event; /* true if this came from a SendEvent request */ 16040 Display *display; /* Display the event was read from */ 16041 Window window; 16042 Atom atom; 16043 Time time; 16044 PropertyNotification state; /* NewValue, Deleted */ 16045 } 16046 16047 struct XSelectionClearEvent 16048 { 16049 int type; 16050 arch_ulong serial; /* # of last request processed by server */ 16051 Bool send_event; /* true if this came from a SendEvent request */ 16052 Display *display; /* Display the event was read from */ 16053 Window window; 16054 Atom selection; 16055 Time time; 16056 } 16057 16058 struct XSelectionRequestEvent 16059 { 16060 int type; 16061 arch_ulong serial; /* # of last request processed by server */ 16062 Bool send_event; /* true if this came from a SendEvent request */ 16063 Display *display; /* Display the event was read from */ 16064 Window owner; 16065 Window requestor; 16066 Atom selection; 16067 Atom target; 16068 Atom property; 16069 Time time; 16070 } 16071 16072 struct XSelectionEvent 16073 { 16074 int type; 16075 arch_ulong serial; /* # of last request processed by server */ 16076 Bool send_event; /* true if this came from a SendEvent request */ 16077 Display *display; /* Display the event was read from */ 16078 Window requestor; 16079 Atom selection; 16080 Atom target; 16081 Atom property; /* ATOM or None */ 16082 Time time; 16083 } 16084 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 16085 16086 struct XColormapEvent 16087 { 16088 int type; 16089 arch_ulong serial; /* # of last request processed by server */ 16090 Bool send_event; /* true if this came from a SendEvent request */ 16091 Display *display; /* Display the event was read from */ 16092 Window window; 16093 Colormap colormap; /* COLORMAP or None */ 16094 Bool new_; /* C++ */ 16095 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 16096 } 16097 version(X86_64) static assert(XColormapEvent.sizeof == 56); 16098 16099 struct XClientMessageEvent 16100 { 16101 int type; 16102 arch_ulong serial; /* # of last request processed by server */ 16103 Bool send_event; /* true if this came from a SendEvent request */ 16104 Display *display; /* Display the event was read from */ 16105 Window window; 16106 Atom message_type; 16107 int format; 16108 union Data{ 16109 byte[20] b; 16110 short[10] s; 16111 arch_ulong[5] l; 16112 } 16113 Data data; 16114 16115 } 16116 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 16117 16118 struct XMappingEvent 16119 { 16120 int type; 16121 arch_ulong serial; /* # of last request processed by server */ 16122 Bool send_event; /* true if this came from a SendEvent request */ 16123 Display *display; /* Display the event was read from */ 16124 Window window; /* unused */ 16125 MappingType request; /* one of MappingModifier, MappingKeyboard, 16126 MappingPointer */ 16127 int first_keycode; /* first keycode */ 16128 int count; /* defines range of change w. first_keycode*/ 16129 } 16130 16131 struct XErrorEvent 16132 { 16133 int type; 16134 Display *display; /* Display the event was read from */ 16135 XID resourceid; /* resource id */ 16136 arch_ulong serial; /* serial number of failed request */ 16137 ubyte error_code; /* error code of failed request */ 16138 ubyte request_code; /* Major op-code of failed request */ 16139 ubyte minor_code; /* Minor op-code of failed request */ 16140 } 16141 16142 struct XAnyEvent 16143 { 16144 int type; 16145 arch_ulong serial; /* # of last request processed by server */ 16146 Bool send_event; /* true if this came from a SendEvent request */ 16147 Display *display;/* Display the event was read from */ 16148 Window window; /* window on which event was requested in event mask */ 16149 } 16150 16151 union XEvent{ 16152 int type; /* must not be changed; first element */ 16153 XAnyEvent xany; 16154 XKeyEvent xkey; 16155 XButtonEvent xbutton; 16156 XMotionEvent xmotion; 16157 XCrossingEvent xcrossing; 16158 XFocusChangeEvent xfocus; 16159 XExposeEvent xexpose; 16160 XGraphicsExposeEvent xgraphicsexpose; 16161 XNoExposeEvent xnoexpose; 16162 XVisibilityEvent xvisibility; 16163 XCreateWindowEvent xcreatewindow; 16164 XDestroyWindowEvent xdestroywindow; 16165 XUnmapEvent xunmap; 16166 XMapEvent xmap; 16167 XMapRequestEvent xmaprequest; 16168 XReparentEvent xreparent; 16169 XConfigureEvent xconfigure; 16170 XGravityEvent xgravity; 16171 XResizeRequestEvent xresizerequest; 16172 XConfigureRequestEvent xconfigurerequest; 16173 XCirculateEvent xcirculate; 16174 XCirculateRequestEvent xcirculaterequest; 16175 XPropertyEvent xproperty; 16176 XSelectionClearEvent xselectionclear; 16177 XSelectionRequestEvent xselectionrequest; 16178 XSelectionEvent xselection; 16179 XColormapEvent xcolormap; 16180 XClientMessageEvent xclient; 16181 XMappingEvent xmapping; 16182 XErrorEvent xerror; 16183 XKeymapEvent xkeymap; 16184 arch_ulong[24] pad; 16185 } 16186 16187 16188 struct Display { 16189 XExtData *ext_data; /* hook for extension to hang data */ 16190 _XPrivate *private1; 16191 int fd; /* Network socket. */ 16192 int private2; 16193 int proto_major_version;/* major version of server's X protocol */ 16194 int proto_minor_version;/* minor version of servers X protocol */ 16195 char *vendor; /* vendor of the server hardware */ 16196 XID private3; 16197 XID private4; 16198 XID private5; 16199 int private6; 16200 XID function(Display*)resource_alloc;/* allocator function */ 16201 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 16202 int bitmap_unit; /* padding and data requirements */ 16203 int bitmap_pad; /* padding requirements on bitmaps */ 16204 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 16205 int nformats; /* number of pixmap formats in list */ 16206 ScreenFormat *pixmap_format; /* pixmap format list */ 16207 int private8; 16208 int release; /* release of the server */ 16209 _XPrivate *private9; 16210 _XPrivate *private10; 16211 int qlen; /* Length of input event queue */ 16212 arch_ulong last_request_read; /* seq number of last event read */ 16213 arch_ulong request; /* sequence number of last request. */ 16214 XPointer private11; 16215 XPointer private12; 16216 XPointer private13; 16217 XPointer private14; 16218 uint max_request_size; /* maximum number 32 bit words in request*/ 16219 _XrmHashBucketRec *db; 16220 int function (Display*)private15; 16221 char *display_name; /* "host:display" string used on this connect*/ 16222 int default_screen; /* default screen for operations */ 16223 int nscreens; /* number of screens on this server*/ 16224 Screen *screens; /* pointer to list of screens */ 16225 arch_ulong motion_buffer; /* size of motion buffer */ 16226 arch_ulong private16; 16227 int min_keycode; /* minimum defined keycode */ 16228 int max_keycode; /* maximum defined keycode */ 16229 XPointer private17; 16230 XPointer private18; 16231 int private19; 16232 byte *xdefaults; /* contents of defaults from server */ 16233 /* there is more to this structure, but it is private to Xlib */ 16234 } 16235 16236 // I got these numbers from a C program as a sanity test 16237 version(X86_64) { 16238 static assert(Display.sizeof == 296); 16239 static assert(XPointer.sizeof == 8); 16240 static assert(XErrorEvent.sizeof == 40); 16241 static assert(XAnyEvent.sizeof == 40); 16242 static assert(XMappingEvent.sizeof == 56); 16243 static assert(XEvent.sizeof == 192); 16244 } else version (AArch64) { 16245 // omit check for aarch64 16246 } else { 16247 static assert(Display.sizeof == 176); 16248 static assert(XPointer.sizeof == 4); 16249 static assert(XEvent.sizeof == 96); 16250 } 16251 16252 struct Depth 16253 { 16254 int depth; /* this depth (Z) of the depth */ 16255 int nvisuals; /* number of Visual types at this depth */ 16256 Visual *visuals; /* list of visuals possible at this depth */ 16257 } 16258 16259 alias void* GC; 16260 alias c_ulong VisualID; 16261 alias XID Colormap; 16262 alias XID Cursor; 16263 alias XID KeySym; 16264 alias uint KeyCode; 16265 enum None = 0; 16266 } 16267 16268 version(without_opengl) {} 16269 else { 16270 extern(C) nothrow @nogc { 16271 16272 16273 static if(!SdpyIsUsingIVGLBinds) { 16274 enum GLX_USE_GL= 1; /* support GLX rendering */ 16275 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 16276 enum GLX_LEVEL= 3; /* level in plane stacking */ 16277 enum GLX_RGBA= 4; /* true if RGBA mode */ 16278 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 16279 enum GLX_STEREO= 6; /* stereo buffering supported */ 16280 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 16281 enum GLX_RED_SIZE= 8; /* number of red component bits */ 16282 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 16283 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 16284 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 16285 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 16286 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 16287 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 16288 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 16289 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 16290 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 16291 16292 16293 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 16294 16295 16296 16297 enum GL_TRUE = 1; 16298 enum GL_FALSE = 0; 16299 alias int GLint; 16300 } 16301 16302 alias XID GLXContextID; 16303 alias XID GLXPixmap; 16304 alias XID GLXDrawable; 16305 alias XID GLXPbuffer; 16306 alias XID GLXWindow; 16307 alias XID GLXFBConfigID; 16308 alias void* GLXContext; 16309 16310 } 16311 } 16312 16313 enum AllocNone = 0; 16314 16315 extern(C) { 16316 /* WARNING, this type not in Xlib spec */ 16317 extern(C) alias XIOErrorHandler = int function (Display* display); 16318 } 16319 16320 extern(C) nothrow @nogc { 16321 struct Screen{ 16322 XExtData *ext_data; /* hook for extension to hang data */ 16323 Display *display; /* back pointer to display structure */ 16324 Window root; /* Root window id. */ 16325 int width, height; /* width and height of screen */ 16326 int mwidth, mheight; /* width and height of in millimeters */ 16327 int ndepths; /* number of depths possible */ 16328 Depth *depths; /* list of allowable depths on the screen */ 16329 int root_depth; /* bits per pixel */ 16330 Visual *root_visual; /* root visual */ 16331 GC default_gc; /* GC for the root root visual */ 16332 Colormap cmap; /* default color map */ 16333 uint white_pixel; 16334 uint black_pixel; /* White and Black pixel values */ 16335 int max_maps, min_maps; /* max and min color maps */ 16336 int backing_store; /* Never, WhenMapped, Always */ 16337 bool save_unders; 16338 int root_input_mask; /* initial root input mask */ 16339 } 16340 16341 struct Visual 16342 { 16343 XExtData *ext_data; /* hook for extension to hang data */ 16344 VisualID visualid; /* visual id of this visual */ 16345 int class_; /* class of screen (monochrome, etc.) */ 16346 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 16347 int bits_per_rgb; /* log base 2 of distinct color values */ 16348 int map_entries; /* color map entries */ 16349 } 16350 16351 alias Display* _XPrivDisplay; 16352 16353 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) { 16354 assert(dpy !is null); 16355 return &dpy.screens[scr]; 16356 } 16357 16358 extern(D) Window RootWindow(Display *dpy,int scr) { 16359 return ScreenOfDisplay(dpy,scr).root; 16360 } 16361 16362 struct XWMHints { 16363 arch_long flags; 16364 Bool input; 16365 int initial_state; 16366 Pixmap icon_pixmap; 16367 Window icon_window; 16368 int icon_x, icon_y; 16369 Pixmap icon_mask; 16370 XID window_group; 16371 } 16372 16373 struct XClassHint { 16374 char* res_name; 16375 char* res_class; 16376 } 16377 16378 extern(D) int DefaultScreen(Display *dpy) { 16379 return dpy.default_screen; 16380 } 16381 16382 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 16383 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 16384 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 16385 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 16386 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 16387 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 16388 16389 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 16390 16391 enum int AnyPropertyType = 0; 16392 enum int Success = 0; 16393 16394 enum int RevertToNone = None; 16395 enum int PointerRoot = 1; 16396 enum Time CurrentTime = 0; 16397 enum int RevertToPointerRoot = PointerRoot; 16398 enum int RevertToParent = 2; 16399 16400 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 16401 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 16402 } 16403 16404 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 16405 return ScreenOfDisplay(dpy,scr).root_visual; 16406 } 16407 16408 extern(D) GC DefaultGC(Display *dpy,int scr) { 16409 return ScreenOfDisplay(dpy,scr).default_gc; 16410 } 16411 16412 extern(D) uint BlackPixel(Display *dpy,int scr) { 16413 return ScreenOfDisplay(dpy,scr).black_pixel; 16414 } 16415 16416 extern(D) uint WhitePixel(Display *dpy,int scr) { 16417 return ScreenOfDisplay(dpy,scr).white_pixel; 16418 } 16419 16420 alias void* XFontSet; // i think 16421 struct XmbTextItem { 16422 char* chars; 16423 int nchars; 16424 int delta; 16425 XFontSet font_set; 16426 } 16427 16428 struct XTextItem { 16429 char* chars; 16430 int nchars; 16431 int delta; 16432 Font font; 16433 } 16434 16435 enum { 16436 GXclear = 0x0, /* 0 */ 16437 GXand = 0x1, /* src AND dst */ 16438 GXandReverse = 0x2, /* src AND NOT dst */ 16439 GXcopy = 0x3, /* src */ 16440 GXandInverted = 0x4, /* NOT src AND dst */ 16441 GXnoop = 0x5, /* dst */ 16442 GXxor = 0x6, /* src XOR dst */ 16443 GXor = 0x7, /* src OR dst */ 16444 GXnor = 0x8, /* NOT src AND NOT dst */ 16445 GXequiv = 0x9, /* NOT src XOR dst */ 16446 GXinvert = 0xa, /* NOT dst */ 16447 GXorReverse = 0xb, /* src OR NOT dst */ 16448 GXcopyInverted = 0xc, /* NOT src */ 16449 GXorInverted = 0xd, /* NOT src OR dst */ 16450 GXnand = 0xe, /* NOT src OR NOT dst */ 16451 GXset = 0xf, /* 1 */ 16452 } 16453 enum QueueMode : int { 16454 QueuedAlready, 16455 QueuedAfterReading, 16456 QueuedAfterFlush 16457 } 16458 16459 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 16460 16461 struct XPoint { 16462 short x; 16463 short y; 16464 } 16465 16466 enum CoordMode:int { 16467 CoordModeOrigin = 0, 16468 CoordModePrevious = 1 16469 } 16470 16471 enum PolygonShape:int { 16472 Complex = 0, 16473 Nonconvex = 1, 16474 Convex = 2 16475 } 16476 16477 struct XTextProperty { 16478 const(char)* value; /* same as Property routines */ 16479 Atom encoding; /* prop type */ 16480 int format; /* prop data format: 8, 16, or 32 */ 16481 arch_ulong nitems; /* number of data items in value */ 16482 } 16483 16484 version( X86_64 ) { 16485 static assert(XTextProperty.sizeof == 32); 16486 } 16487 16488 16489 struct XGCValues { 16490 int function_; /* logical operation */ 16491 arch_ulong plane_mask;/* plane mask */ 16492 arch_ulong foreground;/* foreground pixel */ 16493 arch_ulong background;/* background pixel */ 16494 int line_width; /* line width */ 16495 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 16496 int cap_style; /* CapNotLast, CapButt, 16497 CapRound, CapProjecting */ 16498 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 16499 int fill_style; /* FillSolid, FillTiled, 16500 FillStippled, FillOpaeueStippled */ 16501 int fill_rule; /* EvenOddRule, WindingRule */ 16502 int arc_mode; /* ArcChord, ArcPieSlice */ 16503 Pixmap tile; /* tile pixmap for tiling operations */ 16504 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 16505 int ts_x_origin; /* offset for tile or stipple operations */ 16506 int ts_y_origin; 16507 Font font; /* default text font for text operations */ 16508 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 16509 Bool graphics_exposures;/* boolean, should exposures be generated */ 16510 int clip_x_origin; /* origin for clipping */ 16511 int clip_y_origin; 16512 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 16513 int dash_offset; /* patterned/dashed line information */ 16514 char dashes; 16515 } 16516 16517 struct XColor { 16518 arch_ulong pixel; 16519 ushort red, green, blue; 16520 byte flags; 16521 byte pad; 16522 } 16523 16524 alias XErrorHandler = int function(Display*, XErrorEvent*); 16525 16526 struct XRectangle { 16527 short x; 16528 short y; 16529 ushort width; 16530 ushort height; 16531 } 16532 16533 enum ClipByChildren = 0; 16534 enum IncludeInferiors = 1; 16535 16536 enum Atom XA_PRIMARY = 1; 16537 enum Atom XA_SECONDARY = 2; 16538 enum Atom XA_STRING = 31; 16539 enum Atom XA_CARDINAL = 6; 16540 enum Atom XA_WM_NAME = 39; 16541 enum Atom XA_ATOM = 4; 16542 enum Atom XA_WINDOW = 33; 16543 enum Atom XA_WM_HINTS = 35; 16544 enum int PropModeAppend = 2; 16545 enum int PropModeReplace = 0; 16546 enum int PropModePrepend = 1; 16547 16548 enum int CopyFromParent = 0; 16549 enum int InputOutput = 1; 16550 16551 // XWMHints 16552 enum InputHint = 1 << 0; 16553 enum StateHint = 1 << 1; 16554 enum IconPixmapHint = (1L << 2); 16555 enum IconWindowHint = (1L << 3); 16556 enum IconPositionHint = (1L << 4); 16557 enum IconMaskHint = (1L << 5); 16558 enum WindowGroupHint = (1L << 6); 16559 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 16560 enum XUrgencyHint = (1L << 8); 16561 16562 // GC Components 16563 enum GCFunction = (1L<<0); 16564 enum GCPlaneMask = (1L<<1); 16565 enum GCForeground = (1L<<2); 16566 enum GCBackground = (1L<<3); 16567 enum GCLineWidth = (1L<<4); 16568 enum GCLineStyle = (1L<<5); 16569 enum GCCapStyle = (1L<<6); 16570 enum GCJoinStyle = (1L<<7); 16571 enum GCFillStyle = (1L<<8); 16572 enum GCFillRule = (1L<<9); 16573 enum GCTile = (1L<<10); 16574 enum GCStipple = (1L<<11); 16575 enum GCTileStipXOrigin = (1L<<12); 16576 enum GCTileStipYOrigin = (1L<<13); 16577 enum GCFont = (1L<<14); 16578 enum GCSubwindowMode = (1L<<15); 16579 enum GCGraphicsExposures= (1L<<16); 16580 enum GCClipXOrigin = (1L<<17); 16581 enum GCClipYOrigin = (1L<<18); 16582 enum GCClipMask = (1L<<19); 16583 enum GCDashOffset = (1L<<20); 16584 enum GCDashList = (1L<<21); 16585 enum GCArcMode = (1L<<22); 16586 enum GCLastBit = 22; 16587 16588 16589 enum int WithdrawnState = 0; 16590 enum int NormalState = 1; 16591 enum int IconicState = 3; 16592 16593 } 16594 } else version (OSXCocoa) { 16595 private: 16596 alias void* id; 16597 alias void* Class; 16598 alias void* SEL; 16599 alias void* IMP; 16600 alias void* Ivar; 16601 alias byte BOOL; 16602 alias const(void)* CFStringRef; 16603 alias const(void)* CFAllocatorRef; 16604 alias const(void)* CFTypeRef; 16605 alias const(void)* CGContextRef; 16606 alias const(void)* CGColorSpaceRef; 16607 alias const(void)* CGImageRef; 16608 alias ulong CGBitmapInfo; 16609 16610 struct objc_super { 16611 id self; 16612 Class superclass; 16613 } 16614 16615 struct CFRange { 16616 long location, length; 16617 } 16618 16619 struct NSPoint { 16620 double x, y; 16621 16622 static fromTuple(T)(T tupl) { 16623 return NSPoint(tupl.tupleof); 16624 } 16625 } 16626 struct NSSize { 16627 double width, height; 16628 } 16629 struct NSRect { 16630 NSPoint origin; 16631 NSSize size; 16632 } 16633 alias NSPoint CGPoint; 16634 alias NSSize CGSize; 16635 alias NSRect CGRect; 16636 16637 struct CGAffineTransform { 16638 double a, b, c, d, tx, ty; 16639 } 16640 16641 enum NSApplicationActivationPolicyRegular = 0; 16642 enum NSBackingStoreBuffered = 2; 16643 enum kCFStringEncodingUTF8 = 0x08000100; 16644 16645 enum : size_t { 16646 NSBorderlessWindowMask = 0, 16647 NSTitledWindowMask = 1 << 0, 16648 NSClosableWindowMask = 1 << 1, 16649 NSMiniaturizableWindowMask = 1 << 2, 16650 NSResizableWindowMask = 1 << 3, 16651 NSTexturedBackgroundWindowMask = 1 << 8 16652 } 16653 16654 enum : ulong { 16655 kCGImageAlphaNone, 16656 kCGImageAlphaPremultipliedLast, 16657 kCGImageAlphaPremultipliedFirst, 16658 kCGImageAlphaLast, 16659 kCGImageAlphaFirst, 16660 kCGImageAlphaNoneSkipLast, 16661 kCGImageAlphaNoneSkipFirst 16662 } 16663 enum : ulong { 16664 kCGBitmapAlphaInfoMask = 0x1F, 16665 kCGBitmapFloatComponents = (1 << 8), 16666 kCGBitmapByteOrderMask = 0x7000, 16667 kCGBitmapByteOrderDefault = (0 << 12), 16668 kCGBitmapByteOrder16Little = (1 << 12), 16669 kCGBitmapByteOrder32Little = (2 << 12), 16670 kCGBitmapByteOrder16Big = (3 << 12), 16671 kCGBitmapByteOrder32Big = (4 << 12) 16672 } 16673 enum CGPathDrawingMode { 16674 kCGPathFill, 16675 kCGPathEOFill, 16676 kCGPathStroke, 16677 kCGPathFillStroke, 16678 kCGPathEOFillStroke 16679 } 16680 enum objc_AssociationPolicy : size_t { 16681 OBJC_ASSOCIATION_ASSIGN = 0, 16682 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 16683 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 16684 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 16685 OBJC_ASSOCIATION_COPY = 0x303 //01403 16686 } 16687 16688 extern(C) { 16689 id objc_msgSend(id receiver, SEL selector, ...); 16690 id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...); 16691 id objc_getClass(const(char)* name); 16692 SEL sel_registerName(const(char)* str); 16693 Class objc_allocateClassPair(Class superclass, const(char)* name, 16694 size_t extra_bytes); 16695 void objc_registerClassPair(Class cls); 16696 BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types); 16697 id objc_getAssociatedObject(id object, void* key); 16698 void objc_setAssociatedObject(id object, void* key, id value, 16699 objc_AssociationPolicy policy); 16700 Ivar class_getInstanceVariable(Class cls, const(char)* name); 16701 id object_getIvar(id object, Ivar ivar); 16702 void object_setIvar(id object, Ivar ivar, id value); 16703 BOOL class_addIvar(Class cls, const(char)* name, 16704 size_t size, ubyte alignment, const(char)* types); 16705 16706 extern __gshared id NSApp; 16707 16708 void CFRelease(CFTypeRef obj); 16709 16710 CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator, 16711 const(char)* bytes, long numBytes, 16712 long encoding, 16713 BOOL isExternalRepresentation); 16714 long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding, 16715 char lossByte, bool isExternalRepresentation, 16716 char* buffer, long maxBufLen, long* usedBufLen); 16717 long CFStringGetLength(CFStringRef theString); 16718 16719 CGContextRef CGBitmapContextCreate(void* data, 16720 size_t width, size_t height, 16721 size_t bitsPerComponent, 16722 size_t bytesPerRow, 16723 CGColorSpaceRef colorspace, 16724 CGBitmapInfo bitmapInfo); 16725 void CGContextRelease(CGContextRef c); 16726 ubyte* CGBitmapContextGetData(CGContextRef c); 16727 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 16728 size_t CGBitmapContextGetWidth(CGContextRef c); 16729 size_t CGBitmapContextGetHeight(CGContextRef c); 16730 16731 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 16732 void CGColorSpaceRelease(CGColorSpaceRef cs); 16733 16734 void CGContextSetRGBStrokeColor(CGContextRef c, 16735 double red, double green, double blue, 16736 double alpha); 16737 void CGContextSetRGBFillColor(CGContextRef c, 16738 double red, double green, double blue, 16739 double alpha); 16740 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 16741 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, 16742 const(char)* str, size_t length); 16743 void CGContextStrokeLineSegments(CGContextRef c, 16744 const(CGPoint)* points, size_t count); 16745 16746 void CGContextBeginPath(CGContextRef c); 16747 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 16748 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 16749 void CGContextAddArc(CGContextRef c, double x, double y, double radius, 16750 double startAngle, double endAngle, long clockwise); 16751 void CGContextAddRect(CGContextRef c, CGRect rect); 16752 void CGContextAddLines(CGContextRef c, 16753 const(CGPoint)* points, size_t count); 16754 void CGContextSaveGState(CGContextRef c); 16755 void CGContextRestoreGState(CGContextRef c); 16756 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, 16757 ulong textEncoding); 16758 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 16759 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 16760 16761 void CGImageRelease(CGImageRef image); 16762 } 16763 16764 private: 16765 // A convenient method to create a CFString (=NSString) from a D string. 16766 CFStringRef createCFString(string str) { 16767 return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length, 16768 kCFStringEncodingUTF8, false); 16769 } 16770 16771 // Objective-C calls. 16772 RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) { 16773 auto _cmd = sel_registerName(selector.ptr); 16774 alias extern(C) RetType function(id, SEL, T) ExpectedType; 16775 return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args); 16776 } 16777 RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) { 16778 auto _cmd = sel_registerName(selector.ptr); 16779 auto cls = objc_getClass(className); 16780 alias extern(C) RetType function(id, SEL, T) ExpectedType; 16781 return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args); 16782 } 16783 RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) { 16784 return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args); 16785 } 16786 16787 alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay; 16788 alias objc_msgSend_classMethod!("alloc", id) alloc; 16789 alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:", 16790 id, NSRect, size_t, size_t, BOOL) initWithContentRect; 16791 alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle; 16792 alias objc_msgSend_specialized!("center", void) center; 16793 alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame; 16794 alias objc_msgSend_specialized!("setContentView:", void, id) setContentView; 16795 alias objc_msgSend_specialized!("release", void) release; 16796 alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor; 16797 alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor; 16798 alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront; 16799 alias objc_msgSend_specialized!("invalidate", void) invalidate; 16800 alias objc_msgSend_specialized!("close", void) close; 16801 alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:", 16802 id, double, id, SEL, id, BOOL) scheduledTimer; 16803 alias objc_msgSend_specialized!("run", void) run; 16804 alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext", 16805 id) currentNSGraphicsContext; 16806 alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort; 16807 alias objc_msgSend_specialized!("characters", CFStringRef) characters; 16808 alias objc_msgSend_specialized!("superclass", Class) superclass; 16809 alias objc_msgSend_specialized!("init", id) init; 16810 alias objc_msgSend_specialized!("addItem:", void, id) addItem; 16811 alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu; 16812 alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:", 16813 id, CFStringRef, SEL, CFStringRef) initWithTitle; 16814 alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu; 16815 alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate; 16816 alias objc_msgSend_specialized!("activateIgnoringOtherApps:", 16817 void, BOOL) activateIgnoringOtherApps; 16818 alias objc_msgSend_classMethod!("NSApplication", "sharedApplication", 16819 id) sharedNSApplication; 16820 alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy; 16821 } else static assert(0, "Unsupported operating system"); 16822 16823 16824 version(OSXCocoa) { 16825 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 16826 // 16827 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 16828 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 16829 // 16830 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 16831 // Probably won't even fully compile right now 16832 16833 import std.math : PI; 16834 import std.algorithm : map; 16835 import std.array : array; 16836 16837 alias SimpleWindow NativeWindowHandle; 16838 alias void delegate(id) NativeEventHandler; 16839 16840 __gshared Ivar simpleWindowIvar; 16841 16842 enum KEY_ESCAPE = 27; 16843 16844 mixin template NativeImageImplementation() { 16845 CGContextRef context; 16846 ubyte* rawData; 16847 final: 16848 16849 void convertToRgbaBytes(ubyte[] where) { 16850 assert(where.length == this.width * this.height * 4); 16851 16852 // if rawData had a length.... 16853 //assert(rawData.length == where.length); 16854 for(long idx = 0; idx < where.length; idx += 4) { 16855 auto alpha = rawData[idx + 3]; 16856 if(alpha == 255) { 16857 where[idx + 0] = rawData[idx + 0]; // r 16858 where[idx + 1] = rawData[idx + 1]; // g 16859 where[idx + 2] = rawData[idx + 2]; // b 16860 where[idx + 3] = rawData[idx + 3]; // a 16861 } else { 16862 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 16863 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 16864 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 16865 where[idx + 3] = rawData[idx + 3]; // a 16866 16867 } 16868 } 16869 } 16870 16871 void setFromRgbaBytes(in ubyte[] where) { 16872 // FIXME: this is probably wrong 16873 assert(where.length == this.width * this.height * 4); 16874 16875 // if rawData had a length.... 16876 //assert(rawData.length == where.length); 16877 for(long idx = 0; idx < where.length; idx += 4) { 16878 auto alpha = rawData[idx + 3]; 16879 if(alpha == 255) { 16880 rawData[idx + 0] = where[idx + 0]; // r 16881 rawData[idx + 1] = where[idx + 1]; // g 16882 rawData[idx + 2] = where[idx + 2]; // b 16883 rawData[idx + 3] = where[idx + 3]; // a 16884 } else { 16885 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 16886 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 16887 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 16888 rawData[idx + 3] = where[idx + 3]; // a 16889 16890 } 16891 } 16892 } 16893 16894 16895 void createImage(int width, int height, bool forcexshm=false) { 16896 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 16897 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 16898 colorSpace, 16899 kCGImageAlphaPremultipliedLast 16900 |kCGBitmapByteOrder32Big); 16901 CGColorSpaceRelease(colorSpace); 16902 rawData = CGBitmapContextGetData(context); 16903 } 16904 void dispose() { 16905 CGContextRelease(context); 16906 } 16907 16908 void setPixel(int x, int y, Color c) { 16909 auto offset = (y * width + x) * 4; 16910 if (c.a == 255) { 16911 rawData[offset + 0] = c.r; 16912 rawData[offset + 1] = c.g; 16913 rawData[offset + 2] = c.b; 16914 rawData[offset + 3] = c.a; 16915 } else { 16916 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 16917 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 16918 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 16919 rawData[offset + 3] = c.a; 16920 } 16921 } 16922 } 16923 16924 mixin template NativeScreenPainterImplementation() { 16925 CGContextRef context; 16926 ubyte[4] _outlineComponents; 16927 id view; 16928 16929 void create(NativeWindowHandle window) { 16930 context = window.drawingContext; 16931 view = window.view; 16932 } 16933 16934 void dispose() { 16935 setNeedsDisplay(view, true); 16936 } 16937 16938 // NotYetImplementedException 16939 Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); } 16940 void rasterOp(RasterOp op) {} 16941 Pen _activePen; 16942 Color _fillColor; 16943 Rectangle _clipRectangle; 16944 void setClipRectangle(int, int, int, int) {} 16945 void setFont(OperatingSystemFont) {} 16946 int fontHeight() { return 14; } 16947 16948 // end 16949 16950 void pen(Pen pen) { 16951 _activePen = pen; 16952 auto color = pen.color; // FIXME 16953 double alphaComponent = color.a/255.0f; 16954 CGContextSetRGBStrokeColor(context, 16955 color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 16956 16957 if (color.a != 255) { 16958 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 16959 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 16960 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 16961 _outlineComponents[3] = color.a; 16962 } else { 16963 _outlineComponents[0] = color.r; 16964 _outlineComponents[1] = color.g; 16965 _outlineComponents[2] = color.b; 16966 _outlineComponents[3] = color.a; 16967 } 16968 } 16969 16970 @property void fillColor(Color color) { 16971 CGContextSetRGBFillColor(context, 16972 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 16973 } 16974 16975 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 16976 // NotYetImplementedException for upper left/width/height 16977 auto cgImage = CGBitmapContextCreateImage(image.context); 16978 auto size = CGSize(CGBitmapContextGetWidth(image.context), 16979 CGBitmapContextGetHeight(image.context)); 16980 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 16981 CGImageRelease(cgImage); 16982 } 16983 16984 version(OSXCocoa) {} else // NotYetImplementedException 16985 void drawPixmap(Sprite image, int x, int y) { 16986 // FIXME: is this efficient? 16987 auto cgImage = CGBitmapContextCreateImage(image.context); 16988 auto size = CGSize(CGBitmapContextGetWidth(image.context), 16989 CGBitmapContextGetHeight(image.context)); 16990 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 16991 CGImageRelease(cgImage); 16992 } 16993 16994 16995 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 16996 // FIXME: alignment 16997 if (_outlineComponents[3] != 0) { 16998 CGContextSaveGState(context); 16999 auto invAlpha = 1.0f/_outlineComponents[3]; 17000 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 17001 _outlineComponents[1]*invAlpha, 17002 _outlineComponents[2]*invAlpha, 17003 _outlineComponents[3]/255.0f); 17004 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 17005 // auto cfstr = cast(id)createCFString(text); 17006 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 17007 // NSPoint(x, y), null); 17008 // CFRelease(cfstr); 17009 CGContextRestoreGState(context); 17010 } 17011 } 17012 17013 void drawPixel(int x, int y) { 17014 auto rawData = CGBitmapContextGetData(context); 17015 auto width = CGBitmapContextGetWidth(context); 17016 auto height = CGBitmapContextGetHeight(context); 17017 auto offset = ((height - y - 1) * width + x) * 4; 17018 rawData[offset .. offset+4] = _outlineComponents; 17019 } 17020 17021 void drawLine(int x1, int y1, int x2, int y2) { 17022 CGPoint[2] linePoints; 17023 linePoints[0] = CGPoint(x1, y1); 17024 linePoints[1] = CGPoint(x2, y2); 17025 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 17026 } 17027 17028 void drawRectangle(int x, int y, int width, int height) { 17029 CGContextBeginPath(context); 17030 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 17031 CGContextAddRect(context, rect); 17032 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17033 } 17034 17035 void drawEllipse(int x1, int y1, int x2, int y2) { 17036 CGContextBeginPath(context); 17037 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 17038 CGContextAddEllipseInRect(context, rect); 17039 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17040 } 17041 17042 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 17043 // @@@BUG@@@ Does not support elliptic arc (width != height). 17044 CGContextBeginPath(context); 17045 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 17046 start*PI/(180*64), finish*PI/(180*64), 0); 17047 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17048 } 17049 17050 void drawPolygon(Point[] intPoints) { 17051 CGContextBeginPath(context); 17052 auto points = array(map!(CGPoint.fromTuple)(intPoints)); 17053 CGContextAddLines(context, points.ptr, points.length); 17054 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17055 } 17056 } 17057 17058 mixin template NativeSimpleWindowImplementation() { 17059 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 17060 synchronized { 17061 if (NSApp == null) initializeApp(); 17062 } 17063 17064 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 17065 17066 // create the window. 17067 window = initWithContentRect(alloc("NSWindow"), 17068 contentRect, 17069 NSTitledWindowMask 17070 |NSClosableWindowMask 17071 |NSMiniaturizableWindowMask 17072 |NSResizableWindowMask, 17073 NSBackingStoreBuffered, 17074 true); 17075 17076 // set the title & move the window to center. 17077 auto windowTitle = createCFString(title); 17078 setTitle(window, windowTitle); 17079 CFRelease(windowTitle); 17080 center(window); 17081 17082 // create area to draw on. 17083 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 17084 drawingContext = CGBitmapContextCreate(null, width, height, 17085 8, 4*width, colorSpace, 17086 kCGImageAlphaPremultipliedLast 17087 |kCGBitmapByteOrder32Big); 17088 CGColorSpaceRelease(colorSpace); 17089 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 17090 auto matrix = CGContextGetTextMatrix(drawingContext); 17091 matrix.c = -matrix.c; 17092 matrix.d = -matrix.d; 17093 CGContextSetTextMatrix(drawingContext, matrix); 17094 17095 // create the subview that things will be drawn on. 17096 view = initWithFrame(alloc("SDGraphicsView"), contentRect); 17097 setContentView(window, view); 17098 object_setIvar(view, simpleWindowIvar, cast(id)this); 17099 release(view); 17100 17101 setBackgroundColor(window, whiteNSColor); 17102 makeKeyAndOrderFront(window, null); 17103 } 17104 void dispose() { 17105 closeWindow(); 17106 release(window); 17107 } 17108 void closeWindow() { 17109 invalidate(timer); 17110 .close(window); 17111 } 17112 17113 ScreenPainter getPainter() { 17114 return ScreenPainter(this, this); 17115 } 17116 17117 id window; 17118 id timer; 17119 id view; 17120 CGContextRef drawingContext; 17121 } 17122 17123 extern(C) { 17124 private: 17125 BOOL returnTrue3(id self, SEL _cmd, id app) { 17126 return true; 17127 } 17128 BOOL returnTrue2(id self, SEL _cmd) { 17129 return true; 17130 } 17131 17132 void pulse(id self, SEL _cmd) { 17133 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 17134 simpleWindow.handlePulse(); 17135 setNeedsDisplay(self, true); 17136 } 17137 void drawRect(id self, SEL _cmd, NSRect rect) { 17138 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 17139 auto curCtx = graphicsPort(currentNSGraphicsContext); 17140 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 17141 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), 17142 CGBitmapContextGetHeight(simpleWindow.drawingContext)); 17143 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 17144 CGImageRelease(cgImage); 17145 } 17146 void keyDown(id self, SEL _cmd, id event) { 17147 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 17148 17149 // the event may have multiple characters, and we send them all at 17150 // once. 17151 if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) { 17152 auto chars = characters(event); 17153 auto range = CFRange(0, CFStringGetLength(chars)); 17154 auto buffer = new char[range.length*3]; 17155 long actualLength; 17156 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false, 17157 buffer.ptr, cast(int) buffer.length, &actualLength); 17158 foreach (dchar dc; buffer[0..actualLength]) { 17159 if (simpleWindow.handleCharEvent) 17160 simpleWindow.handleCharEvent(dc); 17161 // NotYetImplementedException 17162 //if (simpleWindow.handleKeyEvent) 17163 //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp? 17164 } 17165 } 17166 17167 // the event's 'keyCode' is hardware-dependent. I don't think people 17168 // will like it. Let's leave it to the native handler. 17169 17170 // perform the default action. 17171 17172 // so the default action is to make a bomp sound and i dont want that 17173 // sooooooooo yeah not gonna do that. 17174 17175 //auto superData = objc_super(self, superclass(self)); 17176 //alias extern(C) void function(objc_super*, SEL, id) T; 17177 //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event); 17178 } 17179 } 17180 17181 // initialize the app so that it can be interacted with the user. 17182 // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html 17183 private void initializeApp() { 17184 // push an autorelease pool to avoid leaking. 17185 init(alloc("NSAutoreleasePool")); 17186 17187 // create a new NSApp instance 17188 sharedNSApplication; 17189 setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular); 17190 17191 // create the "Quit" menu. 17192 auto menuBar = init(alloc("NSMenu")); 17193 auto appMenuItem = init(alloc("NSMenuItem")); 17194 addItem(menuBar, appMenuItem); 17195 setMainMenu(NSApp, menuBar); 17196 release(appMenuItem); 17197 release(menuBar); 17198 17199 auto appMenu = init(alloc("NSMenu")); 17200 auto quitTitle = createCFString("Quit"); 17201 auto q = createCFString("q"); 17202 auto quitItem = initWithTitle(alloc("NSMenuItem"), 17203 quitTitle, sel_registerName("terminate:"), q); 17204 addItem(appMenu, quitItem); 17205 setSubmenu(appMenuItem, appMenu); 17206 release(quitItem); 17207 release(appMenu); 17208 CFRelease(q); 17209 CFRelease(quitTitle); 17210 17211 // assign a delegate for the application, allow it to quit when the last 17212 // window is closed. 17213 auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), 17214 "SDWindowCloseDelegate", 0); 17215 class_addMethod(delegateClass, 17216 sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"), 17217 &returnTrue3, "c@:@"); 17218 objc_registerClassPair(delegateClass); 17219 17220 auto appDelegate = init(alloc("SDWindowCloseDelegate")); 17221 setDelegate(NSApp, appDelegate); 17222 activateIgnoringOtherApps(NSApp, true); 17223 17224 // create a new view that draws the graphics and respond to keyDown 17225 // events. 17226 auto viewClass = objc_allocateClassPair(objc_getClass("NSView"), 17227 "SDGraphicsView", (void*).sizeof); 17228 class_addIvar(viewClass, "simpledisplay_simpleWindow", 17229 (void*).sizeof, (void*).alignof, "^v"); 17230 class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"), 17231 &pulse, "v@:"); 17232 class_addMethod(viewClass, sel_registerName("drawRect:"), 17233 &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); 17234 class_addMethod(viewClass, sel_registerName("isFlipped"), 17235 &returnTrue2, "c@:"); 17236 class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"), 17237 &returnTrue2, "c@:"); 17238 class_addMethod(viewClass, sel_registerName("keyDown:"), 17239 &keyDown, "v@:@"); 17240 objc_registerClassPair(viewClass); 17241 simpleWindowIvar = class_getInstanceVariable(viewClass, 17242 "simpledisplay_simpleWindow"); 17243 } 17244 } 17245 17246 version(without_opengl) {} else 17247 extern(System) nothrow @nogc { 17248 //enum uint GL_VERSION = 0x1F02; 17249 //const(char)* glGetString (/*GLenum*/uint); 17250 version(X11) { 17251 static if (!SdpyIsUsingIVGLBinds) { 17252 17253 enum GLX_X_RENDERABLE = 0x8012; 17254 enum GLX_DRAWABLE_TYPE = 0x8010; 17255 enum GLX_RENDER_TYPE = 0x8011; 17256 enum GLX_X_VISUAL_TYPE = 0x22; 17257 enum GLX_TRUE_COLOR = 0x8002; 17258 enum GLX_WINDOW_BIT = 0x00000001; 17259 enum GLX_RGBA_BIT = 0x00000001; 17260 enum GLX_COLOR_INDEX_BIT = 0x00000002; 17261 enum GLX_SAMPLE_BUFFERS = 0x186a0; 17262 enum GLX_SAMPLES = 0x186a1; 17263 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 17264 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 17265 } 17266 17267 // GLX_EXT_swap_control 17268 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 17269 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 17270 17271 //k8: ugly code to prevent warnings when sdpy is compiled into .a 17272 extern(System) { 17273 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 17274 } 17275 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 17276 17277 // this made public so we don't have to get it again and again 17278 public bool glXCreateContextAttribsARB_present () { 17279 if (glXCreateContextAttribsARBFn is cast(void*)1) { 17280 // get it 17281 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 17282 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 17283 } 17284 return (glXCreateContextAttribsARBFn !is null); 17285 } 17286 17287 // this made public so we don't have to get it again and again 17288 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 17289 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 17290 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 17291 } 17292 17293 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 17294 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 17295 17296 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 17297 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 17298 if (_glx_swapInterval_fn is null) { 17299 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 17300 if (_glx_swapInterval_fn is null) { 17301 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 17302 return; 17303 } 17304 version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); } 17305 } 17306 17307 if(glXSwapIntervalMESA is null) { 17308 // it seems to require both to actually take effect on many computers 17309 // idk why 17310 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 17311 if(glXSwapIntervalMESA is null) 17312 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 17313 } 17314 17315 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 17316 glXSwapIntervalMESA(wait ? 1 : 0); 17317 17318 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 17319 } 17320 } else version(Windows) { 17321 static if (!SdpyIsUsingIVGLBinds) { 17322 enum GL_TRUE = 1; 17323 enum GL_FALSE = 0; 17324 alias int GLint; 17325 17326 public void* glbindGetProcAddress (const(char)* name) { 17327 void* res = wglGetProcAddress(name); 17328 if (res is null) { 17329 /+ 17330 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 17331 import core.sys.windows.windef, core.sys.windows.winbase; 17332 __gshared HINSTANCE dll = null; 17333 if (dll is null) { 17334 dll = LoadLibraryA("opengl32.dll"); 17335 if (dll is null) return null; // <32, but idc 17336 } 17337 res = GetProcAddress(dll, name); 17338 +/ 17339 res = GetProcAddress(gl.libHandle, name); 17340 } 17341 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 17342 return res; 17343 } 17344 } 17345 17346 17347 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 17348 void wglSetVSync(bool wait) { 17349 if(wglSwapIntervalEXT is null) { 17350 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 17351 if(wglSwapIntervalEXT is null) 17352 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 17353 } 17354 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 17355 return; 17356 17357 wglSwapIntervalEXT(wait ? 1 : 0); 17358 } 17359 17360 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 17361 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 17362 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 17363 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 17364 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 17365 17366 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 17367 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 17368 17369 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 17370 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 17371 17372 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 17373 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 17374 17375 void wglInitOtherFunctions () { 17376 if (wglCreateContextAttribsARB is null) { 17377 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 17378 } 17379 } 17380 } 17381 17382 static if (!SdpyIsUsingIVGLBinds) { 17383 17384 interface GL { 17385 extern(System) @nogc nothrow: 17386 17387 void glGetIntegerv(int, void*); 17388 void glMatrixMode(int); 17389 void glPushMatrix(); 17390 void glLoadIdentity(); 17391 void glOrtho(double, double, double, double, double, double); 17392 void glFrustum(double, double, double, double, double, double); 17393 17394 void glPopMatrix(); 17395 void glEnable(int); 17396 void glDisable(int); 17397 void glClear(int); 17398 void glBegin(int); 17399 void glVertex2f(float, float); 17400 void glVertex3f(float, float, float); 17401 void glEnd(); 17402 void glColor3b(byte, byte, byte); 17403 void glColor3ub(ubyte, ubyte, ubyte); 17404 void glColor4b(byte, byte, byte, byte); 17405 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 17406 void glColor3i(int, int, int); 17407 void glColor3ui(uint, uint, uint); 17408 void glColor4i(int, int, int, int); 17409 void glColor4ui(uint, uint, uint, uint); 17410 void glColor3f(float, float, float); 17411 void glColor4f(float, float, float, float); 17412 void glTranslatef(float, float, float); 17413 void glScalef(float, float, float); 17414 version(X11) { 17415 void glSecondaryColor3b(byte, byte, byte); 17416 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 17417 void glSecondaryColor3i(int, int, int); 17418 void glSecondaryColor3ui(uint, uint, uint); 17419 void glSecondaryColor3f(float, float, float); 17420 } 17421 17422 void glDrawElements(int, int, int, void*); 17423 17424 void glRotatef(float, float, float, float); 17425 17426 uint glGetError(); 17427 17428 void glDeleteTextures(int, uint*); 17429 17430 17431 void glRasterPos2i(int, int); 17432 void glDrawPixels(int, int, uint, uint, void*); 17433 void glClearColor(float, float, float, float); 17434 17435 17436 void glPixelStorei(uint, int); 17437 17438 void glGenTextures(uint, uint*); 17439 void glBindTexture(int, int); 17440 void glTexParameteri(uint, uint, int); 17441 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 17442 void glTexImage2D(int, int, int, int, int, int, int, int, in void*); 17443 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 17444 /*GLsizei*/int width, /*GLsizei*/int height, 17445 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 17446 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 17447 17448 void glLineWidth(int); 17449 17450 17451 void glTexCoord2f(float, float); 17452 void glVertex2i(int, int); 17453 void glBlendFunc (int, int); 17454 void glDepthFunc (int); 17455 void glViewport(int, int, int, int); 17456 17457 void glClearDepth(double); 17458 17459 void glReadBuffer(uint); 17460 void glReadPixels(int, int, int, int, int, int, void*); 17461 17462 void glFlush(); 17463 void glFinish(); 17464 17465 version(Windows) { 17466 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 17467 HGLRC wglCreateContext(HDC); 17468 HGLRC wglCreateLayerContext(HDC, int); 17469 BOOL wglDeleteContext(HGLRC); 17470 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 17471 HGLRC wglGetCurrentContext(); 17472 HDC wglGetCurrentDC(); 17473 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 17474 PROC wglGetProcAddress(LPCSTR); 17475 BOOL wglMakeCurrent(HDC, HGLRC); 17476 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 17477 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 17478 BOOL wglShareLists(HGLRC, HGLRC); 17479 BOOL wglSwapLayerBuffers(HDC, UINT); 17480 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 17481 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 17482 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 17483 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 17484 } 17485 17486 } 17487 17488 interface GL3 { 17489 extern(System) @nogc nothrow: 17490 17491 void glGenVertexArrays(GLsizei, GLuint*); 17492 void glBindVertexArray(GLuint); 17493 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 17494 void glGenerateMipmap(GLenum); 17495 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 17496 void glStencilMask(GLuint); 17497 void glStencilFunc(GLenum, GLint, GLuint); 17498 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 17499 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 17500 GLuint glCreateProgram(); 17501 GLuint glCreateShader(GLenum); 17502 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 17503 void glCompileShader(GLuint); 17504 void glGetShaderiv(GLuint, GLenum, GLint*); 17505 void glAttachShader(GLuint, GLuint); 17506 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 17507 void glLinkProgram(GLuint); 17508 void glGetProgramiv(GLuint, GLenum, GLint*); 17509 void glDeleteProgram(GLuint); 17510 void glDeleteShader(GLuint); 17511 GLint glGetUniformLocation(GLuint, const(GLchar)*); 17512 void glGenBuffers(GLsizei, GLuint*); 17513 void glUniform4fv(GLint, GLsizei, const(GLfloat)*); 17514 void glUniform1f(GLint, float); 17515 void glUniform2f(GLint, float, float); 17516 void glUniform4f(GLint, float, float, float, float); 17517 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 17518 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 17519 void glDrawArrays(GLenum, GLint, GLsizei); 17520 void glStencilOp(GLenum, GLenum, GLenum); 17521 void glUseProgram(GLuint); 17522 void glCullFace(GLenum); 17523 void glFrontFace(GLenum); 17524 void glActiveTexture(GLenum); 17525 void glBindBuffer(GLenum, GLuint); 17526 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 17527 void glEnableVertexAttribArray(GLuint); 17528 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 17529 void glUniform1i(GLint, GLint); 17530 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 17531 void glDisableVertexAttribArray(GLuint); 17532 void glDeleteBuffers(GLsizei, const(GLuint)*); 17533 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 17534 void glLogicOp (GLenum opcode); 17535 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 17536 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 17537 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 17538 GLenum glCheckFramebufferStatus (GLenum target); 17539 void glBindFramebuffer (GLenum target, GLuint framebuffer); 17540 } 17541 17542 interface GL4 { 17543 extern(System) @nogc nothrow: 17544 17545 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 17546 /*GLsizei*/int width, /*GLsizei*/int height, 17547 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 17548 } 17549 17550 interface GLU { 17551 extern(System) @nogc nothrow: 17552 17553 void gluLookAt(double, double, double, double, double, double, double, double, double); 17554 void gluPerspective(double, double, double, double); 17555 17556 char* gluErrorString(uint); 17557 } 17558 17559 17560 enum GL_RED = 0x1903; 17561 enum GL_ALPHA = 0x1906; 17562 17563 enum uint GL_FRONT = 0x0404; 17564 17565 enum uint GL_BLEND = 0x0be2; 17566 enum uint GL_LEQUAL = 0x0203; 17567 17568 17569 enum uint GL_RGB = 0x1907; 17570 enum uint GL_BGRA = 0x80e1; 17571 enum uint GL_RGBA = 0x1908; 17572 enum uint GL_TEXTURE_2D = 0x0DE1; 17573 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 17574 enum uint GL_NEAREST = 0x2600; 17575 enum uint GL_LINEAR = 0x2601; 17576 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 17577 enum uint GL_TEXTURE_WRAP_S = 0x2802; 17578 enum uint GL_TEXTURE_WRAP_T = 0x2803; 17579 enum uint GL_REPEAT = 0x2901; 17580 enum uint GL_CLAMP = 0x2900; 17581 enum uint GL_CLAMP_TO_EDGE = 0x812F; 17582 enum uint GL_CLAMP_TO_BORDER = 0x812D; 17583 enum uint GL_DECAL = 0x2101; 17584 enum uint GL_MODULATE = 0x2100; 17585 enum uint GL_TEXTURE_ENV = 0x2300; 17586 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 17587 enum uint GL_REPLACE = 0x1E01; 17588 enum uint GL_LIGHTING = 0x0B50; 17589 enum uint GL_DITHER = 0x0BD0; 17590 17591 enum uint GL_NO_ERROR = 0; 17592 17593 17594 17595 enum int GL_VIEWPORT = 0x0BA2; 17596 enum int GL_MODELVIEW = 0x1700; 17597 enum int GL_TEXTURE = 0x1702; 17598 enum int GL_PROJECTION = 0x1701; 17599 enum int GL_DEPTH_TEST = 0x0B71; 17600 17601 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 17602 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 17603 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 17604 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 17605 17606 enum int GL_POINTS = 0x0000; 17607 enum int GL_LINES = 0x0001; 17608 enum int GL_LINE_LOOP = 0x0002; 17609 enum int GL_LINE_STRIP = 0x0003; 17610 enum int GL_TRIANGLES = 0x0004; 17611 enum int GL_TRIANGLE_STRIP = 5; 17612 enum int GL_TRIANGLE_FAN = 6; 17613 enum int GL_QUADS = 7; 17614 enum int GL_QUAD_STRIP = 8; 17615 enum int GL_POLYGON = 9; 17616 17617 alias GLvoid = void; 17618 alias GLboolean = ubyte; 17619 alias GLuint = uint; 17620 alias GLenum = uint; 17621 alias GLchar = char; 17622 alias GLsizei = int; 17623 alias GLfloat = float; 17624 alias GLintptr = size_t; 17625 alias GLsizeiptr = ptrdiff_t; 17626 17627 17628 enum uint GL_INVALID_ENUM = 0x0500; 17629 17630 enum uint GL_ZERO = 0; 17631 enum uint GL_ONE = 1; 17632 17633 enum uint GL_BYTE = 0x1400; 17634 enum uint GL_UNSIGNED_BYTE = 0x1401; 17635 enum uint GL_SHORT = 0x1402; 17636 enum uint GL_UNSIGNED_SHORT = 0x1403; 17637 enum uint GL_INT = 0x1404; 17638 enum uint GL_UNSIGNED_INT = 0x1405; 17639 enum uint GL_FLOAT = 0x1406; 17640 enum uint GL_2_BYTES = 0x1407; 17641 enum uint GL_3_BYTES = 0x1408; 17642 enum uint GL_4_BYTES = 0x1409; 17643 enum uint GL_DOUBLE = 0x140A; 17644 17645 enum uint GL_STREAM_DRAW = 0x88E0; 17646 17647 enum uint GL_CCW = 0x0901; 17648 17649 enum uint GL_STENCIL_TEST = 0x0B90; 17650 enum uint GL_SCISSOR_TEST = 0x0C11; 17651 17652 enum uint GL_EQUAL = 0x0202; 17653 enum uint GL_NOTEQUAL = 0x0205; 17654 17655 enum uint GL_ALWAYS = 0x0207; 17656 enum uint GL_KEEP = 0x1E00; 17657 17658 enum uint GL_INCR = 0x1E02; 17659 17660 enum uint GL_INCR_WRAP = 0x8507; 17661 enum uint GL_DECR_WRAP = 0x8508; 17662 17663 enum uint GL_CULL_FACE = 0x0B44; 17664 enum uint GL_BACK = 0x0405; 17665 17666 enum uint GL_FRAGMENT_SHADER = 0x8B30; 17667 enum uint GL_VERTEX_SHADER = 0x8B31; 17668 17669 enum uint GL_COMPILE_STATUS = 0x8B81; 17670 enum uint GL_LINK_STATUS = 0x8B82; 17671 17672 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 17673 17674 enum uint GL_STATIC_DRAW = 0x88E4; 17675 17676 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 17677 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 17678 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 17679 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 17680 17681 enum uint GL_GENERATE_MIPMAP = 0x8191; 17682 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 17683 17684 enum uint GL_TEXTURE0 = 0x84C0U; 17685 enum uint GL_TEXTURE1 = 0x84C1U; 17686 17687 enum uint GL_ARRAY_BUFFER = 0x8892; 17688 17689 enum uint GL_SRC_COLOR = 0x0300; 17690 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 17691 enum uint GL_SRC_ALPHA = 0x0302; 17692 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 17693 enum uint GL_DST_ALPHA = 0x0304; 17694 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 17695 enum uint GL_DST_COLOR = 0x0306; 17696 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 17697 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 17698 17699 enum uint GL_INVERT = 0x150AU; 17700 17701 enum uint GL_DEPTH_STENCIL = 0x84F9U; 17702 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 17703 17704 enum uint GL_FRAMEBUFFER = 0x8D40U; 17705 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 17706 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 17707 17708 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 17709 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 17710 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 17711 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 17712 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 17713 17714 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 17715 enum uint GL_CLEAR = 0x1500U; 17716 enum uint GL_COPY = 0x1503U; 17717 enum uint GL_XOR = 0x1506U; 17718 17719 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 17720 17721 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 17722 17723 } 17724 } 17725 17726 /++ 17727 History: 17728 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. 17729 +/ 17730 __gshared bool gluSuccessfullyLoaded = true; 17731 17732 version(without_opengl) {} else { 17733 static if(!SdpyIsUsingIVGLBinds) { 17734 version(Windows) { 17735 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 17736 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 17737 } else { 17738 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 17739 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 17740 } 17741 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 17742 17743 17744 shared static this() { 17745 gl.loadDynamicLibrary(); 17746 17747 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 17748 // unless those functions are actually used 17749 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 17750 glu.loadDynamicLibrary(); 17751 } 17752 } 17753 } 17754 17755 /++ 17756 Convenience method for converting D arrays to opengl buffer data 17757 17758 I would LOVE to overload it with the original glBufferData, but D won't 17759 let me since glBufferData is a function pointer :( 17760 17761 Added: August 25, 2020 (version 8.5) 17762 +/ 17763 version(without_opengl) {} else 17764 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 17765 glBufferData(target, data.length, data.ptr, usage); 17766 } 17767 17768 /+ 17769 /++ 17770 A matrix for simple uses that easily integrates with [OpenGlShader]. 17771 17772 Might not be useful to you since it only as some simple functions and 17773 probably isn't that fast. 17774 17775 Note it uses an inline static array for its storage, so copying it 17776 may be expensive. 17777 +/ 17778 struct BasicMatrix(int columns, int rows, T = float) { 17779 import core.stdc.math; 17780 17781 T[columns * rows] data = 0.0; 17782 17783 /++ 17784 Basic operations that operate *in place*. 17785 +/ 17786 void translate() { 17787 17788 } 17789 17790 /// ditto 17791 void scale() { 17792 17793 } 17794 17795 /// ditto 17796 void rotate() { 17797 17798 } 17799 17800 /++ 17801 17802 +/ 17803 static if(columns == rows) 17804 static BasicMatrix identity() { 17805 BasicMatrix m; 17806 foreach(i; 0 .. columns) 17807 data[0 + i + i * columns] = 1.0; 17808 return m; 17809 } 17810 17811 static BasicMatrix ortho() { 17812 return BasicMatrix.init; 17813 } 17814 } 17815 +/ 17816 17817 /++ 17818 Convenience class for using opengl shaders. 17819 17820 Ensure that you've loaded opengl 3+ and set your active 17821 context before trying to use this. 17822 17823 Added: August 25, 2020 (version 8.5) 17824 +/ 17825 version(without_opengl) {} else 17826 final class OpenGlShader { 17827 private int shaderProgram_; 17828 private @property void shaderProgram(int a) { 17829 shaderProgram_ = a; 17830 } 17831 /// Get the program ID for use in OpenGL functions. 17832 public @property int shaderProgram() { 17833 return shaderProgram_; 17834 } 17835 17836 /++ 17837 17838 +/ 17839 static struct Source { 17840 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 17841 string code; /// 17842 } 17843 17844 /++ 17845 Helper method to just compile some shader code and check for errors 17846 while you do glCreateShader, etc. on the outside yourself. 17847 17848 This just does `glShaderSource` and `glCompileShader` for the given code. 17849 17850 If you the OpenGlShader class constructor, you never need to call this yourself. 17851 +/ 17852 static void compile(int sid, Source code) { 17853 const(char)*[1] buffer; 17854 int[1] lengthBuffer; 17855 17856 buffer[0] = code.code.ptr; 17857 lengthBuffer[0] = cast(int) code.code.length; 17858 17859 glShaderSource(sid, 1, buffer.ptr, lengthBuffer.ptr); 17860 glCompileShader(sid); 17861 17862 int success; 17863 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 17864 if(!success) { 17865 char[512] info; 17866 int len; 17867 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 17868 17869 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 17870 } 17871 } 17872 17873 /++ 17874 Calls `glLinkProgram` and throws if error a occurs. 17875 17876 If you the OpenGlShader class constructor, you never need to call this yourself. 17877 +/ 17878 static void link(int shaderProgram) { 17879 glLinkProgram(shaderProgram); 17880 int success; 17881 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 17882 if(!success) { 17883 char[512] info; 17884 int len; 17885 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 17886 17887 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 17888 } 17889 } 17890 17891 /++ 17892 Constructs the shader object by calling `glCreateProgram`, then 17893 compiling each given [Source], and finally, linking them together. 17894 17895 Throws: on compile or link failure. 17896 +/ 17897 this(Source[] codes...) { 17898 shaderProgram = glCreateProgram(); 17899 17900 int[16] shadersBufferStack; 17901 17902 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 17903 shadersBufferStack[0 .. codes.length] : 17904 new int[](codes.length); 17905 17906 foreach(idx, code; codes) { 17907 shadersBuffer[idx] = glCreateShader(code.type); 17908 17909 compile(shadersBuffer[idx], code); 17910 17911 glAttachShader(shaderProgram, shadersBuffer[idx]); 17912 } 17913 17914 link(shaderProgram); 17915 17916 foreach(s; shadersBuffer) 17917 glDeleteShader(s); 17918 } 17919 17920 /// Calls `glUseProgram(this.shaderProgram)` 17921 void use() { 17922 glUseProgram(this.shaderProgram); 17923 } 17924 17925 /// Deletes the program. 17926 void delete_() { 17927 glDeleteProgram(shaderProgram); 17928 shaderProgram = 0; 17929 } 17930 17931 /++ 17932 [OpenGlShader.uniforms].name gives you one of these. 17933 17934 You can get the id out of it or just assign 17935 +/ 17936 static struct Uniform { 17937 /// the id passed to glUniform* 17938 int id; 17939 17940 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 17941 void opAssign(float x, float y, float z, float w) { 17942 if(id != -1) 17943 glUniform4f(id, x, y, z, w); 17944 } 17945 17946 void opAssign(float x) { 17947 if(id != -1) 17948 glUniform1f(id, x); 17949 } 17950 17951 void opAssign(float x, float y) { 17952 if(id != -1) 17953 glUniform2f(id, x, y); 17954 } 17955 } 17956 17957 static struct UniformsHelper { 17958 OpenGlShader _shader; 17959 17960 @property Uniform opDispatch(string name)() { 17961 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 17962 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 17963 //if(i == -1) 17964 //throw new Exception("Could not find uniform " ~ name); 17965 return Uniform(i); 17966 } 17967 } 17968 17969 /++ 17970 Gives access to the uniforms through dot access. 17971 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 17972 +/ 17973 @property UniformsHelper uniforms() { return UniformsHelper(this); } 17974 } 17975 17976 version(linux) { 17977 version(with_eventloop) {} else { 17978 private int epollFd = -1; 17979 void prepareEventLoop() { 17980 if(epollFd != -1) 17981 return; // already initialized, no need to do it again 17982 import ep = core.sys.linux.epoll; 17983 17984 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 17985 if(epollFd == -1) 17986 throw new Exception("epoll create failure"); 17987 } 17988 } 17989 } else version(Posix) { 17990 void prepareEventLoop() {} 17991 } 17992 17993 version(X11) { 17994 import core.stdc.locale : LC_ALL; // rdmd fix 17995 __gshared bool sdx_isUTF8Locale; 17996 17997 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 17998 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 17999 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 18000 // anal magic is here. I (Ketmar) hope you like it. 18001 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 18002 // always return correct unicode symbols. The detection is here 'cause user can change locale 18003 // later. 18004 18005 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 18006 shared static this () { 18007 if(!librariesSuccessfullyLoaded) 18008 return; 18009 18010 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 18011 18012 // this doesn't hurt; it may add some locking, but the speed is still 18013 // allows doing 60 FPS videogames; also, ignore the result, as most 18014 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 18015 // never seen this failing). 18016 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 18017 18018 setlocale(LC_ALL, ""); 18019 // check if out locale is UTF-8 18020 auto lct = setlocale(LC_CTYPE, null); 18021 if (lct is null) { 18022 sdx_isUTF8Locale = false; 18023 } else { 18024 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 18025 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 18026 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 18027 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 18028 { 18029 sdx_isUTF8Locale = true; 18030 break; 18031 } 18032 } 18033 } 18034 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 18035 } 18036 } 18037 18038 class ExperimentalTextComponent2 { 18039 /+ 18040 Stage 1: get it working monospace 18041 Stage 2: use proportional font 18042 Stage 3: allow changes in inline style 18043 Stage 4: allow new fonts and sizes in the middle 18044 Stage 5: optimize gap buffer 18045 Stage 6: optimize layout 18046 Stage 7: word wrap 18047 Stage 8: justification 18048 Stage 9: editing, selection, etc. 18049 18050 Operations: 18051 insert text 18052 overstrike text 18053 select 18054 cut 18055 modify 18056 +/ 18057 18058 /++ 18059 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. 18060 +/ 18061 this(SimpleWindow window) { 18062 this.window = window; 18063 } 18064 18065 private SimpleWindow window; 18066 18067 18068 /++ 18069 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 18070 representing the internal parts. The first pass is focused on the x parameter, then the 18071 renderer is responsible for going back to the parts in the current line and calling 18072 adjustDownForAscent to change the y params. 18073 +/ 18074 static interface ComponentRenderHelper { 18075 18076 /+ 18077 When you do an edit, possibly stuff on the same line previously need to move (to adjust 18078 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 18079 to move (adjust y to make room for new line) until you get back to the same position, 18080 then you can stop - if one thing is unchanged, nothing after it is changed too. 18081 18082 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 18083 once you reach something that is unchanged, you can stop. 18084 +/ 18085 18086 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 18087 18088 int ascent() const; 18089 int descent() const; 18090 18091 int advance() const; 18092 18093 bool endsWithExplititLineBreak() const; 18094 } 18095 18096 static interface RenderResult { 18097 /++ 18098 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. 18099 +/ 18100 void popFront(); 18101 @property bool empty() const; 18102 @property ComponentRenderHelper front() const; 18103 18104 void repositionForNextLine(Point baseline, int availableWidth); 18105 } 18106 18107 static interface ComponentInFlow { 18108 void draw(ScreenPainter painter); 18109 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 18110 18111 bool startsWithExplicitLineBreak() const; 18112 } 18113 18114 static class TextFlowComponent : ComponentInFlow { 18115 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 18116 18117 Color foreground; 18118 Color background; 18119 18120 OperatingSystemFont font; // should NEVER be null 18121 18122 ubyte attributes; // underline, strike through, display on new block 18123 18124 version(Windows) 18125 const(wchar)[] content; 18126 else 18127 const(char)[] content; // this should NEVER have a newline, except at the end 18128 18129 RenderedComponent[] rendered; // entirely controlled by [rerender] 18130 18131 // could prolly put some spacing around it too like margin / padding 18132 18133 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 18134 in { assert(font !is null); 18135 assert(!font.isNull); } 18136 do 18137 { 18138 this.foreground = f; 18139 this.background = b; 18140 this.font = font; 18141 18142 this.attributes = attr; 18143 version(Windows) { 18144 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 18145 auto sz = sizeOfConvertedWstring(c, conversionFlags); 18146 auto buffer = new wchar[](sz); 18147 this.content = makeWindowsString(c, buffer, conversionFlags); 18148 } else { 18149 this.content = c.dup; 18150 } 18151 } 18152 18153 void draw(ScreenPainter painter) { 18154 painter.setFont(this.font); 18155 painter.outlineColor = this.foreground; 18156 painter.fillColor = Color.transparent; 18157 foreach(rendered; this.rendered) { 18158 // the component works in term of baseline, 18159 // but the painter works in term of upper left bounding box 18160 // so need to translate that 18161 18162 if(this.background.a) { 18163 painter.fillColor = this.background; 18164 painter.outlineColor = this.background; 18165 18166 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 18167 18168 painter.outlineColor = this.foreground; 18169 painter.fillColor = Color.transparent; 18170 } 18171 18172 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 18173 18174 // FIXME: strike through, underline, highlight selection, etc. 18175 } 18176 } 18177 } 18178 18179 // I could split the parts into words on render 18180 // for easier word-wrap, each one being an unbreakable "inline-block" 18181 private TextFlowComponent[] parts; 18182 private int needsRerenderFrom; 18183 18184 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 18185 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 18186 parts ~= new TextFlowComponent(f, b, font, attr, c); 18187 } 18188 18189 static struct RenderedComponent { 18190 int startX; 18191 int startY; 18192 short width; 18193 // 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! 18194 // for individual chars in here you've gotta process on demand 18195 version(Windows) 18196 const(wchar)[] slice; 18197 else 18198 const(char)[] slice; 18199 } 18200 18201 18202 void rerender(Rectangle boundingBox) { 18203 Point baseline = boundingBox.upperLeft; 18204 18205 this.boundingBox.left = boundingBox.left; 18206 this.boundingBox.top = boundingBox.top; 18207 18208 auto remainingParts = parts; 18209 18210 int largestX; 18211 18212 18213 foreach(part; parts) 18214 part.font.prepareContext(window); 18215 scope(exit) 18216 foreach(part; parts) 18217 part.font.releaseContext(); 18218 18219 calculateNextLine: 18220 18221 int nextLineHeight = 0; 18222 int nextBiggestDescent = 0; 18223 18224 foreach(part; remainingParts) { 18225 auto height = part.font.ascent; 18226 if(height > nextLineHeight) 18227 nextLineHeight = height; 18228 if(part.font.descent > nextBiggestDescent) 18229 nextBiggestDescent = part.font.descent; 18230 if(part.content.length && part.content[$-1] == '\n') 18231 break; 18232 } 18233 18234 baseline.y += nextLineHeight; 18235 auto lineStart = baseline; 18236 18237 while(remainingParts.length) { 18238 remainingParts[0].rendered = null; 18239 18240 bool eol; 18241 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 18242 eol = true; 18243 18244 // FIXME: word wrap 18245 auto font = remainingParts[0].font; 18246 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 18247 auto width = font.stringWidth(slice, window); 18248 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 18249 18250 remainingParts = remainingParts[1 .. $]; 18251 baseline.x += width; 18252 18253 if(eol) { 18254 baseline.y += nextBiggestDescent; 18255 if(baseline.x > largestX) 18256 largestX = baseline.x; 18257 baseline.x = lineStart.x; 18258 goto calculateNextLine; 18259 } 18260 } 18261 18262 if(baseline.x > largestX) 18263 largestX = baseline.x; 18264 18265 this.boundingBox.right = largestX; 18266 this.boundingBox.bottom = baseline.y; 18267 } 18268 18269 // you must call rerender first! 18270 void draw(ScreenPainter painter) { 18271 foreach(part; parts) { 18272 part.draw(painter); 18273 } 18274 } 18275 18276 struct IdentifyResult { 18277 TextFlowComponent part; 18278 int charIndexInPart; 18279 int totalCharIndex = -1; // if this is -1, it just means the end 18280 18281 Rectangle boundingBox; 18282 } 18283 18284 IdentifyResult identify(Point pt, bool exact = false) { 18285 if(parts.length == 0) 18286 return IdentifyResult(null, 0); 18287 18288 if(pt.y < boundingBox.top) { 18289 if(exact) 18290 return IdentifyResult(null, 1); 18291 return IdentifyResult(parts[0], 0); 18292 } 18293 if(pt.y > boundingBox.bottom) { 18294 if(exact) 18295 return IdentifyResult(null, 2); 18296 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 18297 } 18298 18299 int tci = 0; 18300 18301 // I should probably like binary search this or something... 18302 foreach(ref part; parts) { 18303 foreach(rendered; part.rendered) { 18304 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 18305 if(rect.contains(pt)) { 18306 auto x = pt.x - rendered.startX; 18307 auto estimatedIdx = x / part.font.averageWidth; 18308 18309 if(estimatedIdx < 0) 18310 estimatedIdx = 0; 18311 18312 if(estimatedIdx > rendered.slice.length) 18313 estimatedIdx = cast(int) rendered.slice.length; 18314 18315 int idx; 18316 int x1, x2; 18317 if(part.font.isMonospace) { 18318 auto w = part.font.averageWidth; 18319 if(!exact && x > (estimatedIdx + 1) * w) 18320 return IdentifyResult(null, 4); 18321 idx = estimatedIdx; 18322 x1 = idx * w; 18323 x2 = (idx + 1) * w; 18324 } else { 18325 idx = estimatedIdx; 18326 18327 part.font.prepareContext(window); 18328 scope(exit) part.font.releaseContext(); 18329 18330 // int iterations; 18331 18332 while(true) { 18333 // iterations++; 18334 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 18335 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 18336 18337 x1 += rendered.startX; 18338 x2 += rendered.startX; 18339 18340 if(pt.x < x1) { 18341 if(idx == 0) { 18342 if(exact) 18343 return IdentifyResult(null, 6); 18344 else 18345 break; 18346 } 18347 idx--; 18348 } else if(pt.x > x2) { 18349 idx++; 18350 if(idx > rendered.slice.length) { 18351 if(exact) 18352 return IdentifyResult(null, 5); 18353 else 18354 break; 18355 } 18356 } else if(pt.x >= x1 && pt.x <= x2) { 18357 if(idx) 18358 idx--; // point it at the original index 18359 break; // we fit 18360 } 18361 } 18362 18363 // import std.stdio; writeln(iterations) 18364 } 18365 18366 18367 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 18368 } 18369 } 18370 tci += cast(int) part.content.length; // FIXME: utf-8? 18371 } 18372 return IdentifyResult(null, 3); 18373 } 18374 18375 Rectangle boundingBox; // only set after [rerender] 18376 18377 // text will be positioned around the exclusion zone 18378 static struct ExclusionZone { 18379 18380 } 18381 18382 ExclusionZone[] exclusionZones; 18383 } 18384 18385 18386 // Don't use this yet. When I'm happy with it, I will move it to the 18387 // regular module namespace. 18388 mixin template ExperimentalTextComponent() { 18389 18390 static: 18391 18392 alias Rectangle = arsd.color.Rectangle; 18393 18394 struct ForegroundColor { 18395 Color color; 18396 alias color this; 18397 18398 this(Color c) { 18399 color = c; 18400 } 18401 18402 this(int r, int g, int b, int a = 255) { 18403 color = Color(r, g, b, a); 18404 } 18405 18406 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 18407 return ForegroundColor(mixin("Color." ~ s)); 18408 } 18409 } 18410 18411 struct BackgroundColor { 18412 Color color; 18413 alias color this; 18414 18415 this(Color c) { 18416 color = c; 18417 } 18418 18419 this(int r, int g, int b, int a = 255) { 18420 color = Color(r, g, b, a); 18421 } 18422 18423 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 18424 return BackgroundColor(mixin("Color." ~ s)); 18425 } 18426 } 18427 18428 static class InlineElement { 18429 string text; 18430 18431 BlockElement containingBlock; 18432 18433 Color color = Color.black; 18434 Color backgroundColor = Color.transparent; 18435 ushort styles; 18436 18437 string font; 18438 int fontSize; 18439 18440 int lineHeight; 18441 18442 void* identifier; 18443 18444 Rectangle boundingBox; 18445 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 18446 18447 bool isMergeCompatible(InlineElement other) { 18448 return 18449 containingBlock is other.containingBlock && 18450 color == other.color && 18451 backgroundColor == other.backgroundColor && 18452 styles == other.styles && 18453 font == other.font && 18454 fontSize == other.fontSize && 18455 lineHeight == other.lineHeight && 18456 true; 18457 } 18458 18459 int xOfIndex(size_t index) { 18460 if(index < letterXs.length) 18461 return letterXs[index]; 18462 else 18463 return boundingBox.right; 18464 } 18465 18466 InlineElement clone() { 18467 auto ie = new InlineElement(); 18468 ie.tupleof = this.tupleof; 18469 return ie; 18470 } 18471 18472 InlineElement getPreviousInlineElement() { 18473 InlineElement prev = null; 18474 foreach(ie; this.containingBlock.parts) { 18475 if(ie is this) 18476 break; 18477 prev = ie; 18478 } 18479 if(prev is null) { 18480 BlockElement pb; 18481 BlockElement cb = this.containingBlock; 18482 moar: 18483 foreach(ie; this.containingBlock.containingLayout.blocks) { 18484 if(ie is cb) 18485 break; 18486 pb = ie; 18487 } 18488 if(pb is null) 18489 return null; 18490 if(pb.parts.length == 0) { 18491 cb = pb; 18492 goto moar; 18493 } 18494 18495 prev = pb.parts[$-1]; 18496 18497 } 18498 return prev; 18499 } 18500 18501 InlineElement getNextInlineElement() { 18502 InlineElement next = null; 18503 foreach(idx, ie; this.containingBlock.parts) { 18504 if(ie is this) { 18505 if(idx + 1 < this.containingBlock.parts.length) 18506 next = this.containingBlock.parts[idx + 1]; 18507 break; 18508 } 18509 } 18510 if(next is null) { 18511 BlockElement n; 18512 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 18513 if(ie is this.containingBlock) { 18514 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 18515 n = this.containingBlock.containingLayout.blocks[idx + 1]; 18516 break; 18517 } 18518 } 18519 if(n is null) 18520 return null; 18521 18522 if(n.parts.length) 18523 next = n.parts[0]; 18524 else {} // FIXME 18525 18526 } 18527 return next; 18528 } 18529 18530 } 18531 18532 // Block elements are used entirely for positioning inline elements, 18533 // which are the things that are actually drawn. 18534 class BlockElement { 18535 InlineElement[] parts; 18536 uint alignment; 18537 18538 int whiteSpace; // pre, pre-wrap, wrap 18539 18540 TextLayout containingLayout; 18541 18542 // inputs 18543 Point where; 18544 Size minimumSize; 18545 Size maximumSize; 18546 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 18547 void* identifier; 18548 18549 Rectangle margin; 18550 Rectangle padding; 18551 18552 // outputs 18553 Rectangle[] boundingBoxes; 18554 } 18555 18556 struct TextIdentifyResult { 18557 InlineElement element; 18558 int offset; 18559 18560 private TextIdentifyResult fixupNewline() { 18561 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 18562 offset--; 18563 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 18564 offset--; 18565 } 18566 return this; 18567 } 18568 } 18569 18570 class TextLayout { 18571 BlockElement[] blocks; 18572 Rectangle boundingBox_; 18573 Rectangle boundingBox() { return boundingBox_; } 18574 void boundingBox(Rectangle r) { 18575 if(r != boundingBox_) { 18576 boundingBox_ = r; 18577 layoutInvalidated = true; 18578 } 18579 } 18580 18581 Rectangle contentBoundingBox() { 18582 Rectangle r; 18583 foreach(block; blocks) 18584 foreach(ie; block.parts) { 18585 if(ie.boundingBox.right > r.right) 18586 r.right = ie.boundingBox.right; 18587 if(ie.boundingBox.bottom > r.bottom) 18588 r.bottom = ie.boundingBox.bottom; 18589 } 18590 return r; 18591 } 18592 18593 BlockElement[] getBlocks() { 18594 return blocks; 18595 } 18596 18597 InlineElement[] getTexts() { 18598 InlineElement[] elements; 18599 foreach(block; blocks) 18600 elements ~= block.parts; 18601 return elements; 18602 } 18603 18604 string getPlainText() { 18605 string text; 18606 foreach(block; blocks) 18607 foreach(part; block.parts) 18608 text ~= part.text; 18609 return text; 18610 } 18611 18612 string getHtml() { 18613 return null; // FIXME 18614 } 18615 18616 this(Rectangle boundingBox) { 18617 this.boundingBox = boundingBox; 18618 } 18619 18620 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 18621 auto be = new BlockElement(); 18622 be.containingLayout = this; 18623 if(after is null) 18624 blocks ~= be; 18625 else { 18626 foreach(idx, b; blocks) { 18627 if(b is after.containingBlock) { 18628 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 18629 break; 18630 } 18631 } 18632 } 18633 return be; 18634 } 18635 18636 void clear() { 18637 blocks = null; 18638 selectionStart = selectionEnd = caret = Caret.init; 18639 } 18640 18641 void addText(Args...)(Args args) { 18642 if(blocks.length == 0) 18643 addBlock(); 18644 18645 InlineElement ie = new InlineElement(); 18646 foreach(idx, arg; args) { 18647 static if(is(typeof(arg) == ForegroundColor)) 18648 ie.color = arg; 18649 else static if(is(typeof(arg) == TextFormat)) { 18650 if(arg & 0x8000) // ~TextFormat.something turns it off 18651 ie.styles &= arg; 18652 else 18653 ie.styles |= arg; 18654 } else static if(is(typeof(arg) == string)) { 18655 static if(idx == 0 && args.length > 1) 18656 static assert(0, "Put styles before the string."); 18657 size_t lastLineIndex; 18658 foreach(cidx, char a; arg) { 18659 if(a == '\n') { 18660 ie.text = arg[lastLineIndex .. cidx + 1]; 18661 lastLineIndex = cidx + 1; 18662 ie.containingBlock = blocks[$-1]; 18663 blocks[$-1].parts ~= ie.clone; 18664 ie.text = null; 18665 } else { 18666 18667 } 18668 } 18669 18670 ie.text = arg[lastLineIndex .. $]; 18671 ie.containingBlock = blocks[$-1]; 18672 blocks[$-1].parts ~= ie.clone; 18673 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 18674 } 18675 } 18676 18677 invalidateLayout(); 18678 } 18679 18680 void tryMerge(InlineElement into, InlineElement what) { 18681 if(!into.isMergeCompatible(what)) { 18682 return; // cannot merge, different configs 18683 } 18684 18685 // cool, can merge, bring text together... 18686 into.text ~= what.text; 18687 18688 // and remove what 18689 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 18690 if(what.containingBlock.parts[a] is what) { 18691 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 18692 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 18693 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 18694 18695 } 18696 } 18697 18698 // FIXME: ensure no other carets have a reference to it 18699 } 18700 18701 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 18702 TextIdentifyResult identify(int x, int y, bool exact = false) { 18703 TextIdentifyResult inexactMatch; 18704 foreach(block; blocks) { 18705 foreach(part; block.parts) { 18706 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 18707 18708 // FIXME binary search 18709 int tidx; 18710 int lastX; 18711 foreach_reverse(idxo, lx; part.letterXs) { 18712 int idx = cast(int) idxo; 18713 if(lx <= x) { 18714 if(lastX && lastX - x < x - lx) 18715 tidx = idx + 1; 18716 else 18717 tidx = idx; 18718 break; 18719 } 18720 lastX = lx; 18721 } 18722 18723 return TextIdentifyResult(part, tidx).fixupNewline; 18724 } else if(!exact) { 18725 // we're not in the box, but are we on the same line? 18726 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 18727 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 18728 } 18729 } 18730 } 18731 18732 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 18733 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 18734 18735 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 18736 } 18737 18738 void moveCaretToPixelCoordinates(int x, int y) { 18739 auto result = identify(x, y); 18740 caret.inlineElement = result.element; 18741 caret.offset = result.offset; 18742 } 18743 18744 void selectToPixelCoordinates(int x, int y) { 18745 auto result = identify(x, y); 18746 18747 if(y < caretLastDrawnY1) { 18748 // on a previous line, carat is selectionEnd 18749 selectionEnd = caret; 18750 18751 selectionStart = Caret(this, result.element, result.offset); 18752 } else if(y > caretLastDrawnY2) { 18753 // on a later line 18754 selectionStart = caret; 18755 18756 selectionEnd = Caret(this, result.element, result.offset); 18757 } else { 18758 // on the same line... 18759 if(x <= caretLastDrawnX) { 18760 selectionEnd = caret; 18761 selectionStart = Caret(this, result.element, result.offset); 18762 } else { 18763 selectionStart = caret; 18764 selectionEnd = Caret(this, result.element, result.offset); 18765 } 18766 18767 } 18768 } 18769 18770 18771 /// Call this if the inputs change. It will reflow everything 18772 void redoLayout(ScreenPainter painter) { 18773 //painter.setClipRectangle(boundingBox); 18774 auto pos = Point(boundingBox.left, boundingBox.top); 18775 18776 int lastHeight; 18777 void nl() { 18778 pos.x = boundingBox.left; 18779 pos.y += lastHeight; 18780 } 18781 foreach(block; blocks) { 18782 nl(); 18783 foreach(part; block.parts) { 18784 part.letterXs = null; 18785 18786 auto size = painter.textSize(part.text); 18787 version(Windows) 18788 if(part.text.length && part.text[$-1] == '\n') 18789 size.height /= 2; // windows counts the new line at the end, but we don't want that 18790 18791 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 18792 18793 foreach(idx, char c; part.text) { 18794 // FIXME: unicode 18795 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 18796 } 18797 18798 pos.x += size.width; 18799 if(pos.x >= boundingBox.right) { 18800 pos.y += size.height; 18801 pos.x = boundingBox.left; 18802 lastHeight = 0; 18803 } else { 18804 lastHeight = size.height; 18805 } 18806 18807 if(part.text.length && part.text[$-1] == '\n') 18808 nl(); 18809 } 18810 } 18811 18812 layoutInvalidated = false; 18813 } 18814 18815 bool layoutInvalidated = true; 18816 void invalidateLayout() { 18817 layoutInvalidated = true; 18818 } 18819 18820 // FIXME: caret can remain sometimes when inserting 18821 // FIXME: inserting at the beginning once you already have something can eff it up. 18822 void drawInto(ScreenPainter painter, bool focused = false) { 18823 if(layoutInvalidated) 18824 redoLayout(painter); 18825 foreach(block; blocks) { 18826 foreach(part; block.parts) { 18827 painter.outlineColor = part.color; 18828 painter.fillColor = part.backgroundColor; 18829 18830 auto pos = part.boundingBox.upperLeft; 18831 auto size = part.boundingBox.size; 18832 18833 painter.drawText(pos, part.text); 18834 if(part.styles & TextFormat.underline) 18835 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 18836 if(part.styles & TextFormat.strikethrough) 18837 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 18838 } 18839 } 18840 18841 // on every redraw, I will force the caret to be 18842 // redrawn too, in order to eliminate perceived lag 18843 // when moving around with the mouse. 18844 eraseCaret(painter); 18845 18846 if(focused) { 18847 highlightSelection(painter); 18848 drawCaret(painter); 18849 } 18850 } 18851 18852 Color selectionXorColor = Color(255, 255, 127); 18853 18854 void highlightSelection(ScreenPainter painter) { 18855 if(selectionStart is selectionEnd) 18856 return; // no selection 18857 18858 if(selectionStart.inlineElement is null) return; 18859 if(selectionEnd.inlineElement is null) return; 18860 18861 assert(selectionStart.inlineElement !is null); 18862 assert(selectionEnd.inlineElement !is null); 18863 18864 painter.rasterOp = RasterOp.xor; 18865 painter.outlineColor = Color.transparent; 18866 painter.fillColor = selectionXorColor; 18867 18868 auto at = selectionStart.inlineElement; 18869 auto atOffset = selectionStart.offset; 18870 bool done; 18871 while(at) { 18872 auto box = at.boundingBox; 18873 if(atOffset < at.letterXs.length) 18874 box.left = at.letterXs[atOffset]; 18875 18876 if(at is selectionEnd.inlineElement) { 18877 if(selectionEnd.offset < at.letterXs.length) 18878 box.right = at.letterXs[selectionEnd.offset]; 18879 done = true; 18880 } 18881 18882 painter.drawRectangle(box.upperLeft, box.width, box.height); 18883 18884 if(done) 18885 break; 18886 18887 at = at.getNextInlineElement(); 18888 atOffset = 0; 18889 } 18890 } 18891 18892 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 18893 bool caretShowingOnScreen = false; 18894 void drawCaret(ScreenPainter painter) { 18895 //painter.setClipRectangle(boundingBox); 18896 int x, y1, y2; 18897 if(caret.inlineElement is null) { 18898 x = boundingBox.left; 18899 y1 = boundingBox.top + 2; 18900 y2 = boundingBox.top + painter.fontHeight; 18901 } else { 18902 x = caret.inlineElement.xOfIndex(caret.offset); 18903 y1 = caret.inlineElement.boundingBox.top + 2; 18904 y2 = caret.inlineElement.boundingBox.bottom - 2; 18905 } 18906 18907 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 18908 eraseCaret(painter); 18909 18910 painter.pen = Pen(Color.white, 1); 18911 painter.rasterOp = RasterOp.xor; 18912 painter.drawLine( 18913 Point(x, y1), 18914 Point(x, y2) 18915 ); 18916 painter.rasterOp = RasterOp.normal; 18917 caretShowingOnScreen = !caretShowingOnScreen; 18918 18919 if(caretShowingOnScreen) { 18920 caretLastDrawnX = x; 18921 caretLastDrawnY1 = y1; 18922 caretLastDrawnY2 = y2; 18923 } 18924 } 18925 18926 Rectangle caretBoundingBox() { 18927 int x, y1, y2; 18928 if(caret.inlineElement is null) { 18929 x = boundingBox.left; 18930 y1 = boundingBox.top + 2; 18931 y2 = boundingBox.top + 16; 18932 } else { 18933 x = caret.inlineElement.xOfIndex(caret.offset); 18934 y1 = caret.inlineElement.boundingBox.top + 2; 18935 y2 = caret.inlineElement.boundingBox.bottom - 2; 18936 } 18937 18938 return Rectangle(x, y1, x + 1, y2); 18939 } 18940 18941 void eraseCaret(ScreenPainter painter) { 18942 //painter.setClipRectangle(boundingBox); 18943 if(!caretShowingOnScreen) return; 18944 painter.pen = Pen(Color.white, 1); 18945 painter.rasterOp = RasterOp.xor; 18946 painter.drawLine( 18947 Point(caretLastDrawnX, caretLastDrawnY1), 18948 Point(caretLastDrawnX, caretLastDrawnY2) 18949 ); 18950 18951 caretShowingOnScreen = false; 18952 painter.rasterOp = RasterOp.normal; 18953 } 18954 18955 /// Caret movement api 18956 /// These should give the user a logical result based on what they see on screen... 18957 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 18958 void moveUp() { 18959 if(caret.inlineElement is null) return; 18960 auto x = caret.inlineElement.xOfIndex(caret.offset); 18961 auto y = caret.inlineElement.boundingBox.top + 2; 18962 18963 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 18964 if(y < 0) 18965 return; 18966 18967 auto i = identify(x, y); 18968 18969 if(i.element) { 18970 caret.inlineElement = i.element; 18971 caret.offset = i.offset; 18972 } 18973 } 18974 void moveDown() { 18975 if(caret.inlineElement is null) return; 18976 auto x = caret.inlineElement.xOfIndex(caret.offset); 18977 auto y = caret.inlineElement.boundingBox.bottom - 2; 18978 18979 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 18980 18981 auto i = identify(x, y); 18982 if(i.element) { 18983 caret.inlineElement = i.element; 18984 caret.offset = i.offset; 18985 } 18986 } 18987 void moveLeft() { 18988 if(caret.inlineElement is null) return; 18989 if(caret.offset) 18990 caret.offset--; 18991 else { 18992 auto p = caret.inlineElement.getPreviousInlineElement(); 18993 if(p) { 18994 caret.inlineElement = p; 18995 if(p.text.length && p.text[$-1] == '\n') 18996 caret.offset = cast(int) p.text.length - 1; 18997 else 18998 caret.offset = cast(int) p.text.length; 18999 } 19000 } 19001 } 19002 void moveRight() { 19003 if(caret.inlineElement is null) return; 19004 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 19005 caret.offset++; 19006 } else { 19007 auto p = caret.inlineElement.getNextInlineElement(); 19008 if(p) { 19009 caret.inlineElement = p; 19010 caret.offset = 0; 19011 } 19012 } 19013 } 19014 void moveHome() { 19015 if(caret.inlineElement is null) return; 19016 auto x = 0; 19017 auto y = caret.inlineElement.boundingBox.top + 2; 19018 19019 auto i = identify(x, y); 19020 19021 if(i.element) { 19022 caret.inlineElement = i.element; 19023 caret.offset = i.offset; 19024 } 19025 } 19026 void moveEnd() { 19027 if(caret.inlineElement is null) return; 19028 auto x = int.max; 19029 auto y = caret.inlineElement.boundingBox.top + 2; 19030 19031 auto i = identify(x, y); 19032 19033 if(i.element) { 19034 caret.inlineElement = i.element; 19035 caret.offset = i.offset; 19036 } 19037 19038 } 19039 void movePageUp(ref Caret caret) {} 19040 void movePageDown(ref Caret caret) {} 19041 19042 void moveDocumentStart(ref Caret caret) { 19043 if(blocks.length && blocks[0].parts.length) 19044 caret = Caret(this, blocks[0].parts[0], 0); 19045 else 19046 caret = Caret.init; 19047 } 19048 19049 void moveDocumentEnd(ref Caret caret) { 19050 if(blocks.length) { 19051 auto parts = blocks[$-1].parts; 19052 if(parts.length) { 19053 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 19054 } else { 19055 caret = Caret.init; 19056 } 19057 } else 19058 caret = Caret.init; 19059 } 19060 19061 void deleteSelection() { 19062 if(selectionStart is selectionEnd) 19063 return; 19064 19065 if(selectionStart.inlineElement is null) return; 19066 if(selectionEnd.inlineElement is null) return; 19067 19068 assert(selectionStart.inlineElement !is null); 19069 assert(selectionEnd.inlineElement !is null); 19070 19071 auto at = selectionStart.inlineElement; 19072 19073 if(selectionEnd.inlineElement is at) { 19074 // same element, need to chop out 19075 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 19076 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 19077 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 19078 } else { 19079 // different elements, we can do it with slicing 19080 at.text = at.text[0 .. selectionStart.offset]; 19081 if(selectionStart.offset < at.letterXs.length) 19082 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 19083 19084 at = at.getNextInlineElement(); 19085 19086 while(at) { 19087 if(at is selectionEnd.inlineElement) { 19088 at.text = at.text[selectionEnd.offset .. $]; 19089 if(selectionEnd.offset < at.letterXs.length) 19090 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 19091 selectionEnd.offset = 0; 19092 break; 19093 } else { 19094 auto cfd = at; 19095 cfd.text = null; // delete the whole thing 19096 19097 at = at.getNextInlineElement(); 19098 19099 if(cfd.text.length == 0) { 19100 // and remove cfd 19101 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 19102 if(cfd.containingBlock.parts[a] is cfd) { 19103 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 19104 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 19105 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 19106 19107 } 19108 } 19109 } 19110 } 19111 } 19112 } 19113 19114 caret = selectionEnd; 19115 selectNone(); 19116 19117 invalidateLayout(); 19118 19119 } 19120 19121 /// Plain text editing api. These work at the current caret inside the selected inline element. 19122 void insert(in char[] text) { 19123 foreach(dchar ch; text) 19124 insert(ch); 19125 } 19126 /// ditto 19127 void insert(dchar ch) { 19128 19129 bool selectionDeleted = false; 19130 if(selectionStart !is selectionEnd) { 19131 deleteSelection(); 19132 selectionDeleted = true; 19133 } 19134 19135 if(ch == 127) { 19136 delete_(); 19137 return; 19138 } 19139 if(ch == 8) { 19140 if(!selectionDeleted) 19141 backspace(); 19142 return; 19143 } 19144 19145 invalidateLayout(); 19146 19147 if(ch == 13) ch = 10; 19148 auto e = caret.inlineElement; 19149 if(e is null) { 19150 addText("" ~ cast(char) ch) ; // FIXME 19151 return; 19152 } 19153 19154 if(caret.offset == e.text.length) { 19155 e.text ~= cast(char) ch; // FIXME 19156 caret.offset++; 19157 if(ch == 10) { 19158 auto c = caret.inlineElement.clone; 19159 c.text = null; 19160 c.letterXs = null; 19161 insertPartAfter(c,e); 19162 caret = Caret(this, c, 0); 19163 } 19164 } else { 19165 // FIXME cast char sucks 19166 if(ch == 10) { 19167 auto c = caret.inlineElement.clone; 19168 c.text = e.text[caret.offset .. $]; 19169 if(caret.offset < c.letterXs.length) 19170 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 19171 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 19172 if(caret.offset <= e.letterXs.length) { 19173 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 19174 } 19175 insertPartAfter(c,e); 19176 caret = Caret(this, c, 0); 19177 } else { 19178 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 19179 caret.offset++; 19180 } 19181 } 19182 } 19183 19184 void insertPartAfter(InlineElement what, InlineElement where) { 19185 foreach(idx, p; where.containingBlock.parts) { 19186 if(p is where) { 19187 if(idx + 1 == where.containingBlock.parts.length) 19188 where.containingBlock.parts ~= what; 19189 else 19190 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 19191 return; 19192 } 19193 } 19194 } 19195 19196 void cleanupStructures() { 19197 for(size_t i = 0; i < blocks.length; i++) { 19198 auto block = blocks[i]; 19199 for(size_t a = 0; a < block.parts.length; a++) { 19200 auto part = block.parts[a]; 19201 if(part.text.length == 0) { 19202 for(size_t b = a; b < block.parts.length - 1; b++) 19203 block.parts[b] = block.parts[b+1]; 19204 block.parts = block.parts[0 .. $-1]; 19205 } 19206 } 19207 if(block.parts.length == 0) { 19208 for(size_t a = i; a < blocks.length - 1; a++) 19209 blocks[a] = blocks[a+1]; 19210 blocks = blocks[0 .. $-1]; 19211 } 19212 } 19213 } 19214 19215 void backspace() { 19216 try_again: 19217 auto e = caret.inlineElement; 19218 if(e is null) 19219 return; 19220 if(caret.offset == 0) { 19221 auto prev = e.getPreviousInlineElement(); 19222 if(prev is null) 19223 return; 19224 auto newOffset = cast(int) prev.text.length; 19225 tryMerge(prev, e); 19226 caret.inlineElement = prev; 19227 caret.offset = prev is null ? 0 : newOffset; 19228 19229 goto try_again; 19230 } else if(caret.offset == e.text.length) { 19231 e.text = e.text[0 .. $-1]; 19232 caret.offset--; 19233 } else { 19234 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 19235 caret.offset--; 19236 } 19237 //cleanupStructures(); 19238 19239 invalidateLayout(); 19240 } 19241 void delete_() { 19242 if(selectionStart !is selectionEnd) 19243 deleteSelection(); 19244 else { 19245 auto before = caret; 19246 moveRight(); 19247 if(caret != before) { 19248 backspace(); 19249 } 19250 } 19251 19252 invalidateLayout(); 19253 } 19254 void overstrike() {} 19255 19256 /// Selection API. See also: caret movement. 19257 void selectAll() { 19258 moveDocumentStart(selectionStart); 19259 moveDocumentEnd(selectionEnd); 19260 } 19261 bool selectNone() { 19262 if(selectionStart != selectionEnd) { 19263 selectionStart = selectionEnd = Caret.init; 19264 return true; 19265 } 19266 return false; 19267 } 19268 19269 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 19270 /// They will modify the current selection if there is one and will splice one in if needed. 19271 void changeAttributes() {} 19272 19273 19274 /// Text search api. They manipulate the selection and/or caret. 19275 void findText(string text) {} 19276 void findIndex(size_t textIndex) {} 19277 19278 // sample event handlers 19279 19280 void handleEvent(KeyEvent event) { 19281 //if(event.type == KeyEvent.Type.KeyPressed) { 19282 19283 //} 19284 } 19285 19286 void handleEvent(dchar ch) { 19287 19288 } 19289 19290 void handleEvent(MouseEvent event) { 19291 19292 } 19293 19294 bool contentEditable; // can it be edited? 19295 bool contentCaretable; // is there a caret/cursor that moves around in there? 19296 bool contentSelectable; // selectable? 19297 19298 Caret caret; 19299 Caret selectionStart; 19300 Caret selectionEnd; 19301 19302 bool insertMode; 19303 } 19304 19305 struct Caret { 19306 TextLayout layout; 19307 InlineElement inlineElement; 19308 int offset; 19309 } 19310 19311 enum TextFormat : ushort { 19312 // decorations 19313 underline = 1, 19314 strikethrough = 2, 19315 19316 // font selectors 19317 19318 bold = 0x4000 | 1, // weight 700 19319 light = 0x4000 | 2, // weight 300 19320 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 19321 // bold | light is really invalid but should give weight 500 19322 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 19323 19324 italic = 0x4000 | 8, 19325 smallcaps = 0x4000 | 16, 19326 } 19327 19328 void* findFont(string family, int weight, TextFormat formats) { 19329 return null; 19330 } 19331 19332 } 19333 19334 /++ 19335 $(PITFALL This is not yet stable and may break in future versions without notice.) 19336 19337 History: 19338 Added February 19, 2021 19339 +/ 19340 /// Group: drag_and_drop 19341 interface DropHandler { 19342 /++ 19343 Called when the drag enters the handler's area. 19344 +/ 19345 DragAndDropAction dragEnter(DropPackage*); 19346 /++ 19347 Called when the drag leaves the handler's area or is 19348 cancelled. You should free your resources when this is called. 19349 +/ 19350 void dragLeave(); 19351 /++ 19352 Called continually as the drag moves over the handler's area. 19353 19354 Returns: feedback to the dragger 19355 +/ 19356 DropParameters dragOver(Point pt); 19357 /++ 19358 The user dropped the data and you should process it now. You can 19359 access the data through the given [DropPackage]. 19360 +/ 19361 void drop(scope DropPackage*); 19362 /++ 19363 Called when the drop is complete. You should free whatever temporary 19364 resources you were using. It is often reasonable to simply forward 19365 this call to [dragLeave]. 19366 +/ 19367 void finish(); 19368 19369 /++ 19370 Parameters returned by [DropHandler.drop]. 19371 +/ 19372 static struct DropParameters { 19373 /++ 19374 Acceptable action over this area. 19375 +/ 19376 DragAndDropAction action; 19377 /++ 19378 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 19379 19380 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 19381 +/ 19382 Rectangle consistentWithin; 19383 } 19384 } 19385 19386 /++ 19387 History: 19388 Added February 19, 2021 19389 +/ 19390 /// Group: drag_and_drop 19391 enum DragAndDropAction { 19392 none = 0, 19393 copy, 19394 move, 19395 link, 19396 ask, 19397 custom 19398 } 19399 19400 /++ 19401 An opaque structure representing dropped data. It contains 19402 private, platform-specific data that your `drop` function 19403 should simply forward. 19404 19405 $(PITFALL This is not yet stable and may break in future versions without notice.) 19406 19407 History: 19408 Added February 19, 2021 19409 +/ 19410 /// Group: drag_and_drop 19411 struct DropPackage { 19412 /++ 19413 Lists the available formats as magic numbers. You should compare these 19414 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 19415 understand the passed data. 19416 +/ 19417 DraggableData.FormatId[] availableFormats() { 19418 version(X11) { 19419 return xFormats; 19420 } else version(Windows) { 19421 if(pDataObj is null) 19422 return null; 19423 19424 typeof(return) ret; 19425 19426 IEnumFORMATETC ef; 19427 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 19428 FORMATETC fmt; 19429 ULONG fetched; 19430 while(ef.Next(1, &fmt, &fetched) == S_OK) { 19431 if(fetched == 0) 19432 break; 19433 19434 if(fmt.lindex != -1) 19435 continue; 19436 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 19437 continue; 19438 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 19439 continue; 19440 19441 ret ~= fmt.cfFormat; 19442 } 19443 } 19444 19445 return ret; 19446 } 19447 } 19448 19449 /++ 19450 Gets data from the drop and optionally accepts it. 19451 19452 Returns: 19453 void because the data is fed asynchronously through the `dg` parameter. 19454 19455 Params: 19456 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. 19457 19458 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. 19459 19460 Calling `getData` again after accepting a drop is not permitted. 19461 19462 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 19463 19464 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. 19465 19466 Throws: 19467 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 19468 19469 History: 19470 Included in first release of [DropPackage]. 19471 +/ 19472 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 19473 version(X11) { 19474 19475 auto display = XDisplayConnection.get(); 19476 auto selectionAtom = GetAtom!"XdndSelection"(display); 19477 auto best = format; 19478 19479 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 19480 19481 XDisplay* display; 19482 Atom selectionAtom; 19483 DraggableData.FormatId best; 19484 DraggableData.FormatId format; 19485 void delegate(scope ubyte[] data) dg; 19486 DragAndDropAction acceptedAction; 19487 Window sourceWindow; 19488 SimpleWindow win; 19489 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 19490 this.display = display; 19491 this.win = win; 19492 this.sourceWindow = sourceWindow; 19493 this.format = format; 19494 this.selectionAtom = selectionAtom; 19495 this.best = best; 19496 this.dg = dg; 19497 this.acceptedAction = acceptedAction; 19498 } 19499 19500 19501 mixin X11GetSelectionHandler_Basics; 19502 19503 void handleData(Atom target, in ubyte[] data) { 19504 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 19505 19506 dg(cast(ubyte[]) data); 19507 19508 if(acceptedAction != DragAndDropAction.none) { 19509 auto display = XDisplayConnection.get; 19510 19511 XClientMessageEvent xclient; 19512 19513 xclient.type = EventType.ClientMessage; 19514 xclient.window = sourceWindow; 19515 xclient.message_type = GetAtom!"XdndFinished"(display); 19516 xclient.format = 32; 19517 xclient.data.l[0] = win.impl.window; 19518 xclient.data.l[1] = 1; // drop successful 19519 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 19520 19521 XSendEvent( 19522 display, 19523 sourceWindow, 19524 false, 19525 EventMask.NoEventMask, 19526 cast(XEvent*) &xclient 19527 ); 19528 19529 XFlush(display); 19530 } 19531 } 19532 19533 Atom findBestFormat(Atom[] answer) { 19534 Atom best = None; 19535 foreach(option; answer) { 19536 if(option == format) { 19537 best = option; 19538 break; 19539 } 19540 /* 19541 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 19542 best = option; 19543 break; 19544 } else if(option == XA_STRING) { 19545 best = option; 19546 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 19547 best = option; 19548 } 19549 */ 19550 } 19551 return best; 19552 } 19553 } 19554 19555 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 19556 19557 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 19558 19559 } else version(Windows) { 19560 19561 // clean up like DragLeave 19562 // pass effect back up 19563 19564 FORMATETC t; 19565 assert(format >= 0 && format <= ushort.max); 19566 t.cfFormat = cast(ushort) format; 19567 t.lindex = -1; 19568 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 19569 t.tymed = TYMED.TYMED_HGLOBAL; 19570 19571 STGMEDIUM m; 19572 19573 if(pDataObj.GetData(&t, &m) != S_OK) { 19574 // fail 19575 } else { 19576 // succeed, take the data and clean up 19577 19578 // FIXME: ensure it is legit HGLOBAL 19579 auto handle = m.hGlobal; 19580 19581 if(handle) { 19582 auto sz = GlobalSize(handle); 19583 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 19584 scope(exit) GlobalUnlock(handle); 19585 scope(exit) GlobalFree(handle); 19586 19587 auto data = ptr[0 .. sz]; 19588 19589 dg(data); 19590 } 19591 } 19592 } 19593 } 19594 } 19595 19596 private: 19597 19598 version(X11) { 19599 SimpleWindow win; 19600 Window sourceWindow; 19601 Time dataTimestamp; 19602 19603 Atom[] xFormats; 19604 } 19605 version(Windows) { 19606 IDataObject pDataObj; 19607 } 19608 } 19609 19610 /++ 19611 A generic helper base class for making a drop handler with a preference list of custom types. 19612 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 19613 droppers too. 19614 19615 It assumes the whole window it used, but you can subclass to change that. 19616 19617 $(PITFALL This is not yet stable and may break in future versions without notice.) 19618 19619 History: 19620 Added February 19, 2021 19621 +/ 19622 /// Group: drag_and_drop 19623 class GenericDropHandlerBase : DropHandler { 19624 // no fancy state here so no need to do anything here 19625 void finish() { } 19626 void dragLeave() { } 19627 19628 private DragAndDropAction acceptedAction; 19629 private DraggableData.FormatId acceptedFormat; 19630 private void delegate(scope ubyte[]) acceptedHandler; 19631 19632 struct FormatHandler { 19633 DraggableData.FormatId format; 19634 void delegate(scope ubyte[]) handler; 19635 } 19636 19637 protected abstract FormatHandler[] formatHandlers(); 19638 19639 DragAndDropAction dragEnter(DropPackage* pkg) { 19640 debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 19641 foreach(fmt; formatHandlers()) 19642 foreach(f; pkg.availableFormats()) 19643 if(f == fmt.format) { 19644 acceptedFormat = f; 19645 acceptedHandler = fmt.handler; 19646 return acceptedAction = DragAndDropAction.copy; 19647 } 19648 return acceptedAction = DragAndDropAction.none; 19649 } 19650 DropParameters dragOver(Point pt) { 19651 return DropParameters(acceptedAction); 19652 } 19653 19654 void drop(scope DropPackage* dropPackage) { 19655 if(!acceptedFormat || acceptedHandler is null) { 19656 debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 19657 return; // prolly shouldn't happen anyway... 19658 } 19659 19660 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 19661 } 19662 } 19663 19664 /++ 19665 A simple handler for making your window accept drops of plain text. 19666 19667 $(PITFALL This is not yet stable and may break in future versions without notice.) 19668 19669 History: 19670 Added February 22, 2021 19671 +/ 19672 /// Group: drag_and_drop 19673 class TextDropHandler : GenericDropHandlerBase { 19674 private void delegate(in char[] text) dg; 19675 19676 /++ 19677 19678 +/ 19679 this(void delegate(in char[] text) dg) { 19680 this.dg = dg; 19681 } 19682 19683 protected override FormatHandler[] formatHandlers() { 19684 version(X11) 19685 return [ 19686 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 19687 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 19688 ]; 19689 else version(Windows) 19690 return [ 19691 FormatHandler(CF_UNICODETEXT, &translator), 19692 ]; 19693 } 19694 19695 private void translator(scope ubyte[] data) { 19696 version(X11) 19697 dg(cast(char[]) data); 19698 else version(Windows) 19699 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 19700 } 19701 } 19702 19703 /++ 19704 A simple handler for making your window accept drops of files, issued to you as file names. 19705 19706 $(PITFALL This is not yet stable and may break in future versions without notice.) 19707 19708 History: 19709 Added February 22, 2021 19710 +/ 19711 /// Group: drag_and_drop 19712 19713 class FilesDropHandler : GenericDropHandlerBase { 19714 private void delegate(in char[][]) dg; 19715 19716 /++ 19717 19718 +/ 19719 this(void delegate(in char[][] fileNames) dg) { 19720 this.dg = dg; 19721 } 19722 19723 protected override FormatHandler[] formatHandlers() { 19724 version(X11) 19725 return [ 19726 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 19727 ]; 19728 else version(Windows) 19729 return [ 19730 FormatHandler(CF_HDROP, &translator), 19731 ]; 19732 } 19733 19734 private void translator(scope ubyte[] data) { 19735 version(X11) { 19736 char[] listString = cast(char[]) data; 19737 char[][16] buffer; 19738 int count; 19739 char[][] result = buffer[]; 19740 19741 void commit(char[] s) { 19742 if(count == result.length) 19743 result.length += 16; 19744 if(s.length > 7 && s[0 ..7] == "file://") 19745 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 19746 result[count++] = s; 19747 } 19748 19749 size_t last; 19750 foreach(idx, char c; listString) { 19751 if(c == '\n') { 19752 commit(listString[last .. idx - 1]); // a \r 19753 last = idx + 1; // a \n 19754 } 19755 } 19756 19757 if(last < listString.length) { 19758 commit(listString[last .. $]); 19759 } 19760 19761 // FIXME: they are uris now, should I translate it to local file names? 19762 // of course the host name is supposed to be there cuz of X rokking... 19763 19764 dg(result[0 .. count]); 19765 } else version(Windows) { 19766 19767 static struct DROPFILES { 19768 DWORD pFiles; 19769 POINT pt; 19770 BOOL fNC; 19771 BOOL fWide; 19772 } 19773 19774 19775 const(char)[][16] buffer; 19776 int count; 19777 const(char)[][] result = buffer[]; 19778 size_t last; 19779 19780 void commitA(in char[] stuff) { 19781 if(count == result.length) 19782 result.length += 16; 19783 result[count++] = stuff; 19784 } 19785 19786 void commitW(in wchar[] stuff) { 19787 commitA(makeUtf8StringFromWindowsString(stuff)); 19788 } 19789 19790 void magic(T)(T chars) { 19791 size_t idx; 19792 while(chars[idx]) { 19793 last = idx; 19794 while(chars[idx]) { 19795 idx++; 19796 } 19797 static if(is(T == char*)) 19798 commitA(chars[last .. idx]); 19799 else 19800 commitW(chars[last .. idx]); 19801 idx++; 19802 } 19803 } 19804 19805 auto df = cast(DROPFILES*) data.ptr; 19806 if(df.fWide) { 19807 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 19808 magic(chars); 19809 } else { 19810 char* chars = cast(char*) (data.ptr + df.pFiles); 19811 magic(chars); 19812 } 19813 dg(result[0 .. count]); 19814 } 19815 } 19816 } 19817 19818 /++ 19819 Interface to describe data being dragged. See also [draggable] helper function. 19820 19821 $(PITFALL This is not yet stable and may break in future versions without notice.) 19822 19823 History: 19824 Added February 19, 2021 19825 +/ 19826 interface DraggableData { 19827 version(X11) 19828 alias FormatId = Atom; 19829 else 19830 alias FormatId = uint; 19831 /++ 19832 Gets the platform-specific FormatId associated with the given named format. 19833 19834 This may be a MIME type, but may also be other various strings defined by the 19835 programs you want to interoperate with. 19836 19837 FIXME: sdpy needs to offer data adapter things that look for compatible formats 19838 and convert it to some particular type for you. 19839 +/ 19840 static FormatId getFormatId(string name)() { 19841 version(X11) 19842 return GetAtom!name(XDisplayConnection.get); 19843 else version(Windows) { 19844 static UINT cache; 19845 if(!cache) 19846 cache = RegisterClipboardFormatA(name); 19847 return cache; 19848 } else 19849 throw new NotYetImplementedException(); 19850 } 19851 19852 /++ 19853 Looks up a string to represent the name for the given format, if there is one. 19854 19855 You should avoid using this function because it is slow. It is provided more for 19856 debugging than for primary use. 19857 +/ 19858 static string getFormatName(FormatId format) { 19859 version(X11) { 19860 if(format == 0) 19861 return "None"; 19862 else 19863 return getAtomName(format, XDisplayConnection.get); 19864 } else version(Windows) { 19865 switch(format) { 19866 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 19867 case CF_DIBV5: return "CF_DIBV5"; 19868 case CF_RIFF: return "CF_RIFF"; 19869 case CF_WAVE: return "CF_WAVE"; 19870 case CF_HDROP: return "CF_HDROP"; 19871 default: 19872 char[1024] name; 19873 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 19874 return name[0 .. count].idup; 19875 } 19876 } 19877 } 19878 19879 FormatId[] availableFormats(); 19880 // Return the slice of data you filled, empty slice if done. 19881 // this is to support the incremental thing 19882 ubyte[] getData(FormatId format, return scope ubyte[] data); 19883 19884 size_t dataLength(FormatId format); 19885 } 19886 19887 /++ 19888 $(PITFALL This is not yet stable and may break in future versions without notice.) 19889 19890 History: 19891 Added February 19, 2021 19892 +/ 19893 DraggableData draggable(string s) { 19894 version(X11) 19895 return new class X11SetSelectionHandler_Text, DraggableData { 19896 this() { 19897 super(s); 19898 } 19899 19900 override FormatId[] availableFormats() { 19901 return X11SetSelectionHandler_Text.availableFormats(); 19902 } 19903 19904 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 19905 return X11SetSelectionHandler_Text.getData(format, data); 19906 } 19907 19908 size_t dataLength(FormatId format) { 19909 return s.length; 19910 } 19911 }; 19912 version(Windows) 19913 return new class DraggableData { 19914 FormatId[] availableFormats() { 19915 return [CF_UNICODETEXT]; 19916 } 19917 19918 ubyte[] getData(FormatId format, return scope ubyte[] data) { 19919 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 19920 } 19921 19922 size_t dataLength(FormatId format) { 19923 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 19924 } 19925 }; 19926 } 19927 19928 /++ 19929 $(PITFALL This is not yet stable and may break in future versions without notice.) 19930 19931 History: 19932 Added February 19, 2021 19933 +/ 19934 /// Group: drag_and_drop 19935 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 19936 in { 19937 assert(window !is null); 19938 assert(handler !is null); 19939 } 19940 do 19941 { 19942 version(X11) { 19943 auto sh = cast(X11SetSelectionHandler) handler; 19944 if(sh is null) { 19945 // gotta make my own adapter. 19946 sh = new class X11SetSelectionHandler { 19947 mixin X11SetSelectionHandler_Basics; 19948 19949 Atom[] availableFormats() { return handler.availableFormats(); } 19950 ubyte[] getData(Atom format, return scope ubyte[] data) { 19951 return handler.getData(format, data); 19952 } 19953 19954 // since the drop selection is only ever used once it isn't important 19955 // to reset it. 19956 void done() {} 19957 }; 19958 } 19959 return doDragDropX11(window, sh, action); 19960 } else version(Windows) { 19961 return doDragDropWindows(window, handler, action); 19962 } else throw new NotYetImplementedException(); 19963 } 19964 19965 version(Windows) 19966 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 19967 IDataObject obj = new class IDataObject { 19968 ULONG refCount; 19969 ULONG AddRef() { 19970 return ++refCount; 19971 } 19972 ULONG Release() { 19973 return --refCount; 19974 } 19975 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 19976 if (IID_IUnknown == *riid) { 19977 *ppv = cast(void*) cast(IUnknown) this; 19978 } 19979 else if (IID_IDataObject == *riid) { 19980 *ppv = cast(void*) cast(IDataObject) this; 19981 } 19982 else { 19983 *ppv = null; 19984 return E_NOINTERFACE; 19985 } 19986 19987 AddRef(); 19988 return NOERROR; 19989 } 19990 19991 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 19992 // import std.stdio; writeln("Advise"); 19993 return E_NOTIMPL; 19994 } 19995 HRESULT DUnadvise(DWORD dwConnection) { 19996 return E_NOTIMPL; 19997 } 19998 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 19999 // import std.stdio; writeln("EnumDAdvise"); 20000 return OLE_E_ADVISENOTSUPPORTED; 20001 } 20002 // tell what formats it supports 20003 20004 FORMATETC[] types; 20005 this() { 20006 FORMATETC t; 20007 foreach(ty; handler.availableFormats()) { 20008 assert(ty <= ushort.max && ty >= 0); 20009 t.cfFormat = cast(ushort) ty; 20010 t.lindex = -1; 20011 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 20012 t.tymed = TYMED.TYMED_HGLOBAL; 20013 } 20014 types ~= t; 20015 } 20016 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 20017 if(dwDirection == DATADIR.DATADIR_GET) { 20018 *ppenumFormatEtc = new class IEnumFORMATETC { 20019 ULONG refCount; 20020 ULONG AddRef() { 20021 return ++refCount; 20022 } 20023 ULONG Release() { 20024 return --refCount; 20025 } 20026 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 20027 if (IID_IUnknown == *riid) { 20028 *ppv = cast(void*) cast(IUnknown) this; 20029 } 20030 else if (IID_IEnumFORMATETC == *riid) { 20031 *ppv = cast(void*) cast(IEnumFORMATETC) this; 20032 } 20033 else { 20034 *ppv = null; 20035 return E_NOINTERFACE; 20036 } 20037 20038 AddRef(); 20039 return NOERROR; 20040 } 20041 20042 20043 int pos; 20044 this() { 20045 pos = 0; 20046 } 20047 20048 HRESULT Clone(IEnumFORMATETC* ppenum) { 20049 // import std.stdio; writeln("clone"); 20050 return E_NOTIMPL; // FIXME 20051 } 20052 20053 // Caller is responsible for freeing memory 20054 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 20055 // fetched may be null if celt is one 20056 if(celt != 1) 20057 return E_NOTIMPL; // FIXME 20058 20059 if(celt + pos > types.length) 20060 return S_FALSE; 20061 20062 *rgelt = types[pos++]; 20063 20064 if(pceltFetched !is null) 20065 *pceltFetched = 1; 20066 20067 // import std.stdio; writeln("ok celt ", celt); 20068 return S_OK; 20069 } 20070 20071 HRESULT Reset() { 20072 pos = 0; 20073 return S_OK; 20074 } 20075 20076 HRESULT Skip(ULONG celt) { 20077 if(celt + pos <= types.length) { 20078 pos += celt; 20079 return S_OK; 20080 } 20081 return S_FALSE; 20082 } 20083 }; 20084 20085 return S_OK; 20086 } else 20087 return E_NOTIMPL; 20088 } 20089 // given a format, return the format you'd prefer to use cuz it is identical 20090 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 20091 // FIXME: prolly could be better but meh 20092 // import std.stdio; writeln("gcf: ", *pformatectIn); 20093 *pformatetcOut = *pformatectIn; 20094 return S_OK; 20095 } 20096 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 20097 foreach(ty; types) { 20098 if(ty == *pformatetcIn) { 20099 auto format = ty.cfFormat; 20100 // import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty); 20101 STGMEDIUM medium; 20102 medium.tymed = TYMED.TYMED_HGLOBAL; 20103 20104 auto sz = handler.dataLength(format); 20105 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 20106 if(handle is null) throw new Exception("GlobalAlloc"); 20107 if(auto data = cast(wchar*) GlobalLock(handle)) { 20108 auto slice = data[0 .. sz]; 20109 scope(exit) 20110 GlobalUnlock(handle); 20111 20112 handler.getData(format, cast(ubyte[]) slice[]); 20113 } 20114 20115 20116 medium.hGlobal = handle; // FIXME 20117 *pmedium = medium; 20118 return S_OK; 20119 } 20120 } 20121 return DV_E_FORMATETC; 20122 } 20123 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 20124 // import std.stdio; writeln("GDH: ", *pformatetcIn); 20125 return E_NOTIMPL; // FIXME 20126 } 20127 HRESULT QueryGetData(FORMATETC* pformatetc) { 20128 auto search = *pformatetc; 20129 search.tymed &= TYMED.TYMED_HGLOBAL; 20130 foreach(ty; types) 20131 if(ty == search) { 20132 // import std.stdio; writeln("QueryGetData ", search, " ", types[0]); 20133 return S_OK; 20134 } 20135 if(pformatetc.cfFormat==CF_UNICODETEXT) { 20136 //import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]); 20137 } 20138 return S_FALSE; 20139 } 20140 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 20141 // import std.stdio; writeln("SetData: "); 20142 return E_NOTIMPL; 20143 } 20144 }; 20145 20146 20147 IDropSource src = new class IDropSource { 20148 ULONG refCount; 20149 ULONG AddRef() { 20150 return ++refCount; 20151 } 20152 ULONG Release() { 20153 return --refCount; 20154 } 20155 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 20156 if (IID_IUnknown == *riid) { 20157 *ppv = cast(void*) cast(IUnknown) this; 20158 } 20159 else if (IID_IDropSource == *riid) { 20160 *ppv = cast(void*) cast(IDropSource) this; 20161 } 20162 else { 20163 *ppv = null; 20164 return E_NOINTERFACE; 20165 } 20166 20167 AddRef(); 20168 return NOERROR; 20169 } 20170 20171 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 20172 if(fEscapePressed) 20173 return DRAGDROP_S_CANCEL; 20174 if(!(grfKeyState & MK_LBUTTON)) 20175 return DRAGDROP_S_DROP; 20176 return S_OK; 20177 } 20178 20179 int GiveFeedback(uint dwEffect) { 20180 return DRAGDROP_S_USEDEFAULTCURSORS; 20181 } 20182 }; 20183 20184 DWORD effect; 20185 20186 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 20187 20188 DROPEFFECT de = win32DragAndDropAction(action); 20189 20190 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 20191 // but still prolly a FIXME 20192 20193 auto ret = DoDragDrop(obj, src, de, &effect); 20194 /+ 20195 import std.stdio; 20196 if(ret == DRAGDROP_S_DROP) 20197 writeln("drop ", effect); 20198 else if(ret == DRAGDROP_S_CANCEL) 20199 writeln("cancel"); 20200 else if(ret == S_OK) 20201 writeln("ok"); 20202 else writeln(ret); 20203 +/ 20204 20205 return ret; 20206 } 20207 20208 version(Windows) 20209 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 20210 DROPEFFECT de; 20211 20212 with(DragAndDropAction) 20213 with(DROPEFFECT) 20214 final switch(action) { 20215 case none: de = DROPEFFECT_NONE; break; 20216 case copy: de = DROPEFFECT_COPY; break; 20217 case move: de = DROPEFFECT_MOVE; break; 20218 case link: de = DROPEFFECT_LINK; break; 20219 case ask: throw new Exception("ask not implemented yet"); 20220 case custom: throw new Exception("custom not implemented yet"); 20221 } 20222 20223 return de; 20224 } 20225 20226 20227 /++ 20228 History: 20229 Added February 19, 2021 20230 +/ 20231 /// Group: drag_and_drop 20232 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 20233 version(X11) { 20234 auto display = XDisplayConnection.get; 20235 20236 Atom atom = 5; // right??? 20237 20238 XChangeProperty( 20239 display, 20240 window.impl.window, 20241 GetAtom!"XdndAware"(display), 20242 XA_ATOM, 20243 32 /* bits */, 20244 PropModeReplace, 20245 &atom, 20246 1); 20247 20248 window.dropHandler = handler; 20249 } else version(Windows) { 20250 20251 initDnd(); 20252 20253 auto dropTarget = new class (handler) IDropTarget { 20254 DropHandler handler; 20255 this(DropHandler handler) { 20256 this.handler = handler; 20257 } 20258 ULONG refCount; 20259 ULONG AddRef() { 20260 return ++refCount; 20261 } 20262 ULONG Release() { 20263 return --refCount; 20264 } 20265 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 20266 if (IID_IUnknown == *riid) { 20267 *ppv = cast(void*) cast(IUnknown) this; 20268 } 20269 else if (IID_IDropTarget == *riid) { 20270 *ppv = cast(void*) cast(IDropTarget) this; 20271 } 20272 else { 20273 *ppv = null; 20274 return E_NOINTERFACE; 20275 } 20276 20277 AddRef(); 20278 return NOERROR; 20279 } 20280 20281 20282 // /////////////////// 20283 20284 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 20285 DropPackage dropPackage = DropPackage(pDataObj); 20286 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 20287 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 20288 } 20289 20290 HRESULT DragLeave() { 20291 handler.dragLeave(); 20292 // release the IDataObject if needed 20293 return S_OK; 20294 } 20295 20296 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 20297 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 20298 20299 *pdwEffect = win32DragAndDropAction(res.action); 20300 // same as DragEnter basically 20301 return S_OK; 20302 } 20303 20304 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 20305 DropPackage pkg = DropPackage(pDataObj); 20306 handler.drop(&pkg); 20307 20308 return S_OK; 20309 } 20310 }; 20311 // Windows can hold on to the handler and try to call it 20312 // during which time the GC can't see it. so important to 20313 // manually manage this. At some point i'll FIXME and make 20314 // all my com instances manually managed since they supposed 20315 // to respect the refcount. 20316 import core.memory; 20317 GC.addRoot(cast(void*) dropTarget); 20318 20319 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 20320 throw new Exception("register"); 20321 20322 window.dropHandler = handler; 20323 } else throw new NotYetImplementedException(); 20324 } 20325 20326 20327 20328 static if(UsingSimpledisplayX11) { 20329 20330 enum _NET_WM_STATE_ADD = 1; 20331 enum _NET_WM_STATE_REMOVE = 0; 20332 enum _NET_WM_STATE_TOGGLE = 2; 20333 20334 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 20335 void demandAttention(SimpleWindow window, bool needs = true) { 20336 demandAttention(window.impl.window, needs); 20337 } 20338 20339 /// ditto 20340 void demandAttention(Window window, bool needs = true) { 20341 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 20342 } 20343 20344 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 20345 auto display = XDisplayConnection.get(); 20346 if(atom == None) 20347 return; // non-failure error 20348 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 20349 20350 XClientMessageEvent xclient; 20351 20352 xclient.type = EventType.ClientMessage; 20353 xclient.window = window; 20354 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 20355 xclient.format = 32; 20356 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 20357 xclient.data.l[1] = atom; 20358 xclient.data.l[2] = atom2; 20359 xclient.data.l[3] = 1; 20360 // [3] == source. 0 == unknown, 1 == app, 2 == else 20361 20362 XSendEvent( 20363 display, 20364 RootWindow(display, DefaultScreen(display)), 20365 false, 20366 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 20367 cast(XEvent*) &xclient 20368 ); 20369 20370 /+ 20371 XChangeProperty( 20372 display, 20373 window.impl.window, 20374 GetAtom!"_NET_WM_STATE"(display), 20375 XA_ATOM, 20376 32 /* bits */, 20377 PropModeAppend, 20378 &atom, 20379 1); 20380 +/ 20381 } 20382 20383 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 20384 Atom actionAtom; 20385 with(DragAndDropAction) 20386 final switch(action) { 20387 case none: actionAtom = None; break; 20388 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 20389 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 20390 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 20391 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 20392 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 20393 } 20394 20395 return actionAtom; 20396 } 20397 20398 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 20399 // FIXME: I need to show user feedback somehow. 20400 auto display = XDisplayConnection.get; 20401 20402 auto actionAtom = dndActionAtom(display, action); 20403 assert(actionAtom, "Don't use action none to accept a drop"); 20404 20405 setX11Selection!"XdndSelection"(window, handler, null); 20406 20407 auto oldKeyHandler = window.handleKeyEvent; 20408 scope(exit) window.handleKeyEvent = oldKeyHandler; 20409 20410 auto oldCharHandler = window.handleCharEvent; 20411 scope(exit) window.handleCharEvent = oldCharHandler; 20412 20413 auto oldMouseHandler = window.handleMouseEvent; 20414 scope(exit) window.handleMouseEvent = oldMouseHandler; 20415 20416 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 20417 20418 import core.sys.posix.sys.time; 20419 timeval tv; 20420 gettimeofday(&tv, null); 20421 20422 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 20423 20424 Time lastMouseTimestamp; 20425 20426 bool dnding = true; 20427 Window lastIn = None; 20428 20429 void leave() { 20430 if(lastIn == None) 20431 return; 20432 20433 XEvent ev; 20434 ev.xclient.type = EventType.ClientMessage; 20435 ev.xclient.window = lastIn; 20436 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 20437 ev.xclient.format = 32; 20438 ev.xclient.data.l[0] = window.impl.window; 20439 20440 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 20441 XFlush(display); 20442 20443 lastIn = None; 20444 } 20445 20446 void enter(Window w) { 20447 assert(lastIn == None); 20448 20449 lastIn = w; 20450 20451 XEvent ev; 20452 ev.xclient.type = EventType.ClientMessage; 20453 ev.xclient.window = lastIn; 20454 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 20455 ev.xclient.format = 32; 20456 ev.xclient.data.l[0] = window.impl.window; 20457 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 20458 20459 auto types = handler.availableFormats(); 20460 assert(types.length > 0); 20461 20462 ev.xclient.data.l[2] = types[0]; 20463 if(types.length > 1) 20464 ev.xclient.data.l[3] = types[1]; 20465 if(types.length > 2) 20466 ev.xclient.data.l[4] = types[2]; 20467 20468 // FIXME: other types?!?!? and make sure we skip TARGETS 20469 20470 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 20471 XFlush(display); 20472 } 20473 20474 void position(int rootX, int rootY) { 20475 assert(lastIn != None); 20476 20477 XEvent ev; 20478 ev.xclient.type = EventType.ClientMessage; 20479 ev.xclient.window = lastIn; 20480 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 20481 ev.xclient.format = 32; 20482 ev.xclient.data.l[0] = window.impl.window; 20483 ev.xclient.data.l[1] = 0; // reserved 20484 ev.xclient.data.l[2] = (rootX << 16) | rootY; 20485 ev.xclient.data.l[3] = dataTimestamp; 20486 ev.xclient.data.l[4] = actionAtom; 20487 20488 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 20489 XFlush(display); 20490 20491 } 20492 20493 void drop() { 20494 XEvent ev; 20495 ev.xclient.type = EventType.ClientMessage; 20496 ev.xclient.window = lastIn; 20497 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 20498 ev.xclient.format = 32; 20499 ev.xclient.data.l[0] = window.impl.window; 20500 ev.xclient.data.l[1] = 0; // reserved 20501 ev.xclient.data.l[2] = dataTimestamp; 20502 20503 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 20504 XFlush(display); 20505 20506 lastIn = None; 20507 dnding = false; 20508 } 20509 20510 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 20511 // but idk if i should... 20512 20513 window.setEventHandlers( 20514 delegate(KeyEvent ev) { 20515 if(ev.pressed == true && ev.key == Key.Escape) { 20516 // cancel 20517 dnding = false; 20518 } 20519 }, 20520 delegate(MouseEvent ev) { 20521 if(ev.timestamp < lastMouseTimestamp) 20522 return; 20523 20524 lastMouseTimestamp = ev.timestamp; 20525 20526 if(ev.type == MouseEventType.motion) { 20527 auto display = XDisplayConnection.get; 20528 auto root = RootWindow(display, DefaultScreen(display)); 20529 20530 Window topWindow; 20531 int rootX, rootY; 20532 20533 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 20534 20535 if(topWindow == None) 20536 return; 20537 20538 top: 20539 if(auto result = topWindow in eligibility) { 20540 auto dropWindow = *result; 20541 if(dropWindow == None) { 20542 leave(); 20543 return; 20544 } 20545 20546 if(dropWindow != lastIn) { 20547 leave(); 20548 enter(dropWindow); 20549 position(rootX, rootY); 20550 } else { 20551 position(rootX, rootY); 20552 } 20553 } else { 20554 // determine eligibility 20555 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 20556 if(data.length == 1) { 20557 // in case there is no WM or it isn't reparenting 20558 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 20559 } else { 20560 20561 Window tryScanChildren(Window search, int maxRecurse) { 20562 // could be reparenting window manager, so gotta check the next few children too 20563 Window child; 20564 int x; 20565 int y; 20566 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 20567 20568 if(child == None) 20569 return None; 20570 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 20571 if(data.length == 1) { 20572 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 20573 } else { 20574 if(maxRecurse) 20575 return tryScanChildren(child, maxRecurse - 1); 20576 else 20577 return None; 20578 } 20579 20580 } 20581 20582 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 20583 auto topResult = tryScanChildren(topWindow, 3); 20584 // it is easy to have a false negative due to the mouse going over a WM 20585 // child window like the close button if separate from the frame... so I 20586 // can't really cache negatives, :( 20587 if(topResult != None) { 20588 eligibility[topWindow] = topResult; 20589 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 20590 } 20591 } 20592 20593 } 20594 20595 } else if(ev.type == MouseEventType.buttonReleased) { 20596 drop(); 20597 dnding = false; 20598 } 20599 } 20600 ); 20601 20602 window.grabInput(); 20603 scope(exit) 20604 window.releaseInputGrab(); 20605 20606 20607 EventLoop.get.run(() => dnding); 20608 20609 return 0; 20610 } 20611 20612 /// X-specific 20613 TrueColorImage getWindowNetWmIcon(Window window) { 20614 try { 20615 auto display = XDisplayConnection.get; 20616 20617 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 20618 20619 if (data.length > arch_ulong.sizeof * 2) { 20620 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 20621 // these are an array of rgba images that we have to convert into pixmaps ourself 20622 20623 int width = cast(int) meta[0]; 20624 int height = cast(int) meta[1]; 20625 20626 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 20627 20628 static if(arch_ulong.sizeof == 4) { 20629 bytes = bytes[0 .. width * height * 4]; 20630 alias imageData = bytes; 20631 } else static if(arch_ulong.sizeof == 8) { 20632 bytes = bytes[0 .. width * height * 8]; 20633 auto imageData = new ubyte[](4 * width * height); 20634 } else static assert(0); 20635 20636 20637 20638 // this returns ARGB. Remember it is little-endian so 20639 // we have BGRA 20640 // our thing uses RGBA, which in little endian, is ABGR 20641 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 20642 auto r = bytes[idx + 2]; 20643 auto g = bytes[idx + 1]; 20644 auto b = bytes[idx + 0]; 20645 auto a = bytes[idx + 3]; 20646 20647 imageData[idx2 + 0] = r; 20648 imageData[idx2 + 1] = g; 20649 imageData[idx2 + 2] = b; 20650 imageData[idx2 + 3] = a; 20651 } 20652 20653 return new TrueColorImage(width, height, imageData); 20654 } 20655 20656 return null; 20657 } catch(Exception e) { 20658 return null; 20659 } 20660 } 20661 20662 } /* UsingSimpledisplayX11 */ 20663 20664 20665 void loadBinNameToWindowClassName () { 20666 import core.stdc.stdlib : realloc; 20667 version(linux) { 20668 // args[0] MAY be empty, so we'll just use this 20669 import core.sys.posix.unistd : readlink; 20670 char[1024] ebuf = void; // 1KB should be enough for everyone! 20671 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 20672 if (len < 1) return; 20673 } else /*version(Windows)*/ { 20674 import core.runtime : Runtime; 20675 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 20676 auto ebuf = Runtime.args[0]; 20677 auto len = ebuf.length; 20678 } 20679 auto pos = len; 20680 while (pos > 0 && ebuf[pos-1] != '/') --pos; 20681 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 20682 if (sdpyWindowClassStr is null) return; // oops 20683 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 20684 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 20685 } 20686 20687 /++ 20688 An interface representing a font. 20689 20690 This is still MAJOR work in progress. 20691 +/ 20692 interface DrawableFont { 20693 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 20694 } 20695 20696 /++ 20697 Loads a true type font using [arsd.ttf]. That module must be compiled 20698 in if you choose to use this function. 20699 20700 Be warned: this can be slow and memory hungry, especially on remote connections 20701 to the X server. 20702 20703 This is still MAJOR work in progress. 20704 +/ 20705 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 20706 import arsd.ttf; 20707 static class ArsdTtfFont : DrawableFont { 20708 TtfFont font; 20709 int size; 20710 this(in ubyte[] data, int size) { 20711 font = TtfFont(data); 20712 this.size = size; 20713 } 20714 20715 Sprite[string] cache; 20716 20717 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 20718 Sprite sprite = (text in cache) ? *(text in cache) : null; 20719 20720 auto fg = painter.impl._outlineColor; 20721 auto bg = painter.impl._fillColor; 20722 20723 if(sprite is null) { 20724 int width, height; 20725 auto data = font.renderString(text, size, width, height); 20726 auto image = new TrueColorImage(width, height); 20727 int pos = 0; 20728 foreach(y; 0 .. height) 20729 foreach(x; 0 .. width) { 20730 fg.a = data[0]; 20731 bg.a = 255; 20732 auto color = alphaBlend(fg, bg); 20733 image.imageData.bytes[pos++] = color.r; 20734 image.imageData.bytes[pos++] = color.g; 20735 image.imageData.bytes[pos++] = color.b; 20736 image.imageData.bytes[pos++] = data[0]; 20737 data = data[1 .. $]; 20738 } 20739 assert(data.length == 0); 20740 20741 sprite = new Sprite(painter.window, Image.fromMemoryImage(image)); 20742 cache[text.idup] = sprite; 20743 } 20744 20745 sprite.drawAt(painter, upperLeft); 20746 } 20747 } 20748 20749 return new ArsdTtfFont(data, size); 20750 } 20751 20752 class NotYetImplementedException : Exception { 20753 this(string file = __FILE__, size_t line = __LINE__) { 20754 super("Not yet implemented", file, line); 20755 } 20756 } 20757 20758 /// 20759 __gshared bool librariesSuccessfullyLoaded = true; 20760 /// 20761 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 20762 20763 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 20764 mixin(staticForeachReplacement!Iface); 20765 20766 void loadDynamicLibrary() @nogc { 20767 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 20768 } 20769 20770 void loadDynamicLibraryForReal() { 20771 foreach(name; __traits(derivedMembers, Iface)) { 20772 mixin("alias tmp = " ~ name ~ ";"); 20773 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 20774 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 20775 } 20776 } 20777 } 20778 20779 private const(char)[] staticForeachReplacement(Iface)() pure { 20780 /* 20781 // just this for gdc 9.... 20782 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 20783 20784 static foreach(name; __traits(derivedMembers, Iface)) 20785 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 20786 */ 20787 20788 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 20789 size_t pos; 20790 20791 void append(in char[] what) { 20792 if(pos + what.length > code.length) 20793 code.length = (code.length * 3) / 2; 20794 code[pos .. pos + what.length] = what[]; 20795 pos += what.length; 20796 } 20797 20798 foreach(name; __traits(derivedMembers, Iface)) { 20799 append(`__gshared typeof(&__traits(getMember, Iface, "`); 20800 append(name); 20801 append(`")) `); 20802 append(name); 20803 append(";"); 20804 } 20805 20806 return code[0 .. pos]; 20807 } 20808 20809 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 20810 mixin(staticForeachReplacement!Iface); 20811 20812 private __gshared void* libHandle; 20813 private __gshared bool attempted; 20814 20815 void loadDynamicLibrary() @nogc { 20816 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 20817 } 20818 20819 bool loadAttempted() { 20820 return attempted; 20821 } 20822 bool loadSuccessful() { 20823 return libHandle !is null; 20824 } 20825 20826 void loadDynamicLibraryForReal() { 20827 attempted = true; 20828 version(Posix) { 20829 import core.sys.posix.dlfcn; 20830 version(OSX) { 20831 version(X11) 20832 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 20833 else 20834 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 20835 } else { 20836 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 20837 if(libHandle is null) 20838 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 20839 } 20840 20841 static void* loadsym(void* l, const char* name) { 20842 import core.stdc.stdlib; 20843 if(l is null) 20844 return &abort; 20845 return dlsym(l, name); 20846 } 20847 } else version(Windows) { 20848 import core.sys.windows.windows; 20849 libHandle = LoadLibrary(library ~ ".dll"); 20850 static void* loadsym(void* l, const char* name) { 20851 import core.stdc.stdlib; 20852 if(l is null) 20853 return &abort; 20854 return GetProcAddress(l, name); 20855 } 20856 } 20857 if(libHandle is null) { 20858 success = false; 20859 //throw new Exception("load failure of library " ~ library); 20860 } 20861 foreach(name; __traits(derivedMembers, Iface)) { 20862 mixin("alias tmp = " ~ name ~ ";"); 20863 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 20864 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 20865 } 20866 } 20867 20868 void unloadDynamicLibrary() { 20869 version(Posix) { 20870 import core.sys.posix.dlfcn; 20871 dlclose(libHandle); 20872 } else version(Windows) { 20873 import core.sys.windows.windows; 20874 FreeLibrary(libHandle); 20875 } 20876 foreach(name; __traits(derivedMembers, Iface)) 20877 mixin(name ~ " = null;"); 20878 } 20879 } 20880 20881 void guiAbortProcess(string msg) { 20882 import core.stdc.stdlib; 20883 version(Windows) { 20884 WCharzBuffer t = WCharzBuffer(msg); 20885 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 20886 } else { 20887 import std.stdio; 20888 stderr.writeln(msg); 20889 stderr.flush(); 20890 } 20891 20892 abort(); 20893 } 20894 20895 private int minInternal(int a, int b) { 20896 return (a < b) ? a : b; 20897 } 20898 20899 private alias scriptable = arsd_jsvar_compatible;