1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 // Search for: FIXME: leaks if multithreaded gc 4 5 // https://freedesktop.org/wiki/Specifications/XDND/ 6 7 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format 8 9 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html 10 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html 11 12 13 // on Mac with X11: -L-L/usr/X11/lib 14 15 /+ 16 17 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works 18 19 Progress bar in taskbar 20 - i can probably just set a property on the window... 21 it sets that prop to an integer 0 .. 100. Taskbar 22 deletes it or window deletes it when it is handled. 23 - prolly display it as a nice little line at the bottom. 24 25 26 from gtk: 27 28 #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" 29 #define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" 30 31 >+ if (cardinal > 0) 32 >+ { 33 >+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display), 34 >+ xid, 35 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name), 36 >+ XA_CARDINAL, 32, 37 >+ PropModeReplace, 38 >+ (guchar *) &cardinal, 1); 39 >+ } 40 >+ else 41 >+ { 42 >+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), 43 >+ xid, 44 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name)); 45 >+ } 46 47 from Windows: 48 49 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3 50 51 interface 52 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 53 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 54 listen for msg, return TRUE 55 interface->SetProgressState(hwnd, TBPF_NORMAL); 56 interface->SetProgressValue(hwnd, 40, 100); 57 58 59 My new notification system. 60 - use a unix socket? or a x property? or a udp port? 61 - could of course also get on the dbus train but ugh. 62 - it could also reply with the info as a string for easy remote examination. 63 64 +/ 65 66 /* 67 Event Loop would be nices: 68 69 * add on idle - runs when nothing else happens 70 * which can specify how long to yield for 71 * send messages without a recipient window 72 * setTimeout 73 * setInterval 74 */ 75 76 /* 77 Classic games I want to add: 78 * my tetris clone 79 * pac man 80 */ 81 82 /* 83 Text layout needs a lot of work. Plain drawText is useful but too 84 limited. It will need some kind of text context thing which it will 85 update and you can pass it on and get more details out of it. 86 87 It will need a bounding box, a current cursor location that is updated 88 as drawing continues, and various changable facts (which can also be 89 changed on the painter i guess) like font, color, size, background, 90 etc. 91 92 We can also fetch the caret location from it somehow. 93 94 Should prolly be an overload of drawText 95 96 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 97 98 WS_EX_NOACTIVATE 99 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 100 full screen windows. Can just set the atom on X. Windows will be harder. 101 102 moving windows. resizing windows. 103 104 hide cursor, capture cursor, change cursor. 105 106 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 107 sure the pieces are there to do its job easily and make other jobs possible. 108 */ 109 110 /++ 111 simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality, 112 including creating windows, drawing on them, working with the clipboard, 113 timers, OpenGL, and more. However, it does NOT provide high level GUI 114 widgets. See my minigui.d, an extension to this module, for that 115 functionality. 116 117 simpledisplay provides cross-platform wrapping for Windows and Linux 118 (and perhaps other OSes that use X11), but also does not prevent you 119 from using the underlying facilities if you need them. It has a goal 120 of working efficiently over a remote X link (at least as far as Xlib 121 reasonably allows.) 122 123 simpledisplay depends on [arsd.color|color.d], which should be available from the 124 same place where you got this file. Other than that, however, it has 125 very few dependencies and ones that don't come with the OS and/or the 126 compiler are all opt-in. 127 128 simpledisplay.d's home base is on my arsd repo on Github. The file is: 129 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 130 131 simpledisplay is basically stable. I plan to refactor the internals, 132 and may add new features and fix bugs, but It do not expect to 133 significantly change the API. It has been stable a few years already now. 134 135 Installation_instructions: 136 137 `simpledisplay.d` does not have any dependencies outside the 138 operating system and `color.d`, so it should just work most the 139 time, but there are a few caveats on some systems: 140 141 On Win32, you can pass `-L/subsystem:windows` if you don't want a 142 console to be automatically allocated. 143 144 Please note when compiling on Win64, you need to explicitly list 145 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 146 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 147 148 If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`; 149 note the "w". 150 151 I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you, 152 but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi). 153 See [EnableWindowsSubsystem] for more information. 154 155 $(PITFALL 156 With the Windows subsystem, there is no console, so standard writeln will throw! 157 You can use [sdpyPrintDebugString] instead of stdio writeln instead which will 158 create a console as needed. 159 ) 160 161 On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command. 162 163 On Ubuntu, you might need to install X11 development libraries to 164 successfully link. 165 166 $(CONSOLE 167 $ sudo apt-get install libglc-dev 168 $ sudo apt-get install libx11-dev 169 ) 170 171 172 Jump_list: 173 174 Don't worry, you don't have to read this whole documentation file! 175 176 Check out the [#event-example] and [#Pong-example] to get started quickly. 177 178 The main classes you may want to create are [SimpleWindow], [Timer], 179 [Image], and [Sprite]. 180 181 The main functions you'll want are [setClipboardText] and [getClipboardText]. 182 183 There are also platform-specific functions available such as [XDisplayConnection] 184 and [GetAtom] for X11, among others. 185 186 See the examples and topics list below to learn more. 187 188 $(WARNING 189 There should only be one GUI thread per application, 190 and all windows should be created in it and your 191 event loop should run there. 192 193 To do otherwise is undefined behavior and has no 194 cross platform guarantees. 195 ) 196 197 $(H2 About this documentation) 198 199 The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow. 200 201 Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples! 202 203 All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them. 204 205 To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example. 206 207 If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file. 208 209 At points, I will talk about implementation details in the documentation. These are sometimes 210 subject to change, but nevertheless useful to understand what is really going on. You can learn 211 more about some of the referenced things by searching the web for info about using them from C. 212 You can always look at the source of simpledisplay.d too for the most authoritative source on 213 its specific implementation. If you disagree with how I did something, please contact me so we 214 can discuss it! 215 216 $(H2 Using with fibers) 217 218 simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor). 219 220 $(H2 Topics) 221 222 $(H3 $(ID topic-windows) Windows) 223 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 224 window on the user's screen. 225 226 You may create multiple windows, if the underlying platform supports it. You may check 227 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 228 SimpleWindow's constructor at runtime to handle those cases. 229 230 A single running event loop will handle as many windows as needed. 231 232 $(H3 $(ID topic-event-loops) Event loops) 233 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 234 235 The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there: 236 237 --- 238 // dmd example.d simpledisplay.d color.d 239 import arsd.simpledisplay; 240 void main() { 241 auto window = new SimpleWindow(200, 200); 242 window.eventLoop(0, 243 delegate (dchar) { /* got a character key press */ } 244 ); 245 } 246 --- 247 248 $(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.) 249 250 On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run. 251 252 On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 253 254 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 255 256 You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway. 257 258 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 259 Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like. 260 261 See the [NotificationAreaIcon] class. 262 263 $(H3 $(ID topic-input-handling) Input handling) 264 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 265 266 See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent]. 267 268 $(H3 $(ID topic-2d-drawing) 2d Drawing) 269 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 270 271 Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example: 272 273 --- 274 // dmd example.d simpledisplay.d color.d 275 import arsd.simpledisplay; 276 void main() { 277 auto window = new SimpleWindow(200, 200); 278 { // introduce sub-scope 279 auto painter = window.draw(); // begin drawing 280 /* draw here */ 281 painter.outlineColor = Color.red; 282 painter.fillColor = Color.black; 283 painter.drawRectangle(Point(0, 0), 200, 200); 284 } // end scope, calling `painter`'s destructor, drawing to the screen. 285 window.eventLoop(0); // handle events 286 } 287 --- 288 289 Painting is done based on two color properties, a pen and a brush. 290 291 At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead. 292 293 FIXME Add example of 2d opengl drawing here. 294 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 295 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 296 297 Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it. 298 299 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 300 301 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 302 303 To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon]. 304 305 simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program. 306 307 This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color: 308 309 --- 310 import arsd.simpledisplay; 311 312 void main() { 313 auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing); 314 315 float otherColor = 0.0; 316 float colorDelta = 0.05; 317 318 window.redrawOpenGlScene = delegate() { 319 glLoadIdentity(); 320 glBegin(GL_QUADS); 321 322 glColor3f(1.0, otherColor, 0); 323 glVertex3f(-0.8, -0.8, 0); 324 325 glColor3f(1.0, otherColor, 1.0); 326 glVertex3f(0.8, -0.8, 0); 327 328 glColor3f(0, 1.0, otherColor); 329 glVertex3f(0.8, 0.8, 0); 330 331 glColor3f(otherColor, 0, 1.0); 332 glVertex3f(-0.8, 0.8, 0); 333 334 glEnd(); 335 }; 336 337 window.eventLoop(50, () { 338 otherColor += colorDelta; 339 if(otherColor > 1.0) { 340 otherColor = 1.0; 341 colorDelta = -0.05; 342 } 343 if(otherColor < 0) { 344 otherColor = 0; 345 colorDelta = 0.05; 346 } 347 // at the end of the timer, we have to request a redraw 348 // or we won't see the changes. 349 window.redrawOpenGlSceneSoon(); 350 }); 351 } 352 --- 353 354 My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow]. 355 $(H3 $(ID topic-modern-opengl) Modern OpenGL) 356 simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too. 357 358 This example program shows how you can set up a shader to draw a rectangle: 359 360 --- 361 module opengl3test; 362 import arsd.simpledisplay; 363 364 // based on https://learnopengl.com/Getting-started/Hello-Triangle 365 366 void main() { 367 // First thing we do, before creating the window, is declare what version we want. 368 setOpenGLContextVersion(3, 3); 369 // turning off legacy compat is required to use version 3.3 and newer 370 openGLContextCompatible = false; 371 372 uint VAO; 373 OpenGlShader shader; 374 375 // then we can create the window. 376 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 377 378 // additional setup needs to be done when it is visible, simpledisplay offers a property 379 // for exactly that: 380 window.visibleForTheFirstTime = delegate() { 381 // now with the window loaded, we can start loading the modern opengl functions. 382 383 // you MUST set the context first. 384 window.setAsCurrentOpenGlContext; 385 // then load the remainder of the library 386 gl3.loadDynamicLibrary(); 387 388 // now you can create the shaders, etc. 389 shader = new OpenGlShader( 390 OpenGlShader.Source(GL_VERTEX_SHADER, ` 391 #version 330 core 392 layout (location = 0) in vec3 aPos; 393 void main() { 394 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 395 } 396 `), 397 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 398 #version 330 core 399 out vec4 FragColor; 400 uniform vec4 mycolor; 401 void main() { 402 FragColor = mycolor; 403 } 404 `), 405 ); 406 407 // and do whatever other setup you want. 408 409 float[] vertices = [ 410 0.5f, 0.5f, 0.0f, // top right 411 0.5f, -0.5f, 0.0f, // bottom right 412 -0.5f, -0.5f, 0.0f, // bottom left 413 -0.5f, 0.5f, 0.0f // top left 414 ]; 415 uint[] indices = [ // note that we start from 0! 416 0, 1, 3, // first Triangle 417 1, 2, 3 // second Triangle 418 ]; 419 uint VBO, EBO; 420 glGenVertexArrays(1, &VAO); 421 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 422 glBindVertexArray(VAO); 423 424 glGenBuffers(1, &VBO); 425 glGenBuffers(1, &EBO); 426 427 glBindBuffer(GL_ARRAY_BUFFER, VBO); 428 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 429 430 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 431 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 432 433 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 434 glEnableVertexAttribArray(0); 435 436 // the library will set the initial viewport and trigger our first draw, 437 // so these next two lines are NOT needed. they are just here as comments 438 // to show what would happen next. 439 440 // glViewport(0, 0, window.width, window.height); 441 // window.redrawOpenGlSceneNow(); 442 }; 443 444 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 445 // it is our render method. 446 window.redrawOpenGlScene = delegate() { 447 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 448 glClear(GL_COLOR_BUFFER_BIT); 449 450 glUseProgram(shader.shaderProgram); 451 452 // the shader helper class has methods to set uniforms too 453 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 454 455 glBindVertexArray(VAO); 456 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 457 }; 458 459 window.eventLoop(0); 460 } 461 --- 462 463 This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread. 464 465 466 $(H3 $(ID topic-images) Displaying images) 467 You can also load PNG images using [arsd.png]. 468 469 --- 470 // dmd example.d simpledisplay.d color.d png.d 471 import arsd.simpledisplay; 472 import arsd.png; 473 474 void main() { 475 auto image = Image.fromMemoryImage(readPng("image.png")); 476 displayImage(image); 477 } 478 --- 479 480 Compile with `dmd example.d simpledisplay.d png.d`. 481 482 If you find an image file which is a valid png that [arsd.png] fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel. 483 484 $(H3 $(ID topic-sprites) Sprites) 485 The [Sprite] class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link. 486 487 [Sprite] is also the only facility that currently supports alpha blending without using OpenGL . 488 489 $(H3 $(ID topic-clipboard) Clipboard) 490 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 491 492 It also has helpers for handling X-specific events. 493 494 $(H3 $(ID topic-dnd) Drag and Drop) 495 See [enableDragAndDrop] and [draggable]. 496 497 $(H3 $(ID topic-timers) Timers) 498 There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, [Timer]. 499 500 The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse. 501 502 --- 503 import arsd.simpledisplay; 504 505 void main() { 506 auto window = new SimpleWindow(400, 400); 507 // every 100 ms, it will draw a random line 508 // on the window. 509 window.eventLoop(100, { 510 auto painter = window.draw(); 511 512 import std.random; 513 // random color 514 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 515 // random line 516 painter.drawLine( 517 Point(uniform(0, window.width), uniform(0, window.height)), 518 Point(uniform(0, window.width), uniform(0, window.height))); 519 520 }); 521 } 522 --- 523 524 The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish. 525 526 The pulse timer and instances of the [Timer] class may be combined at will. 527 528 --- 529 import arsd.simpledisplay; 530 531 void main() { 532 auto window = new SimpleWindow(400, 400); 533 auto timer = new Timer(1000, delegate { 534 auto painter = window.draw(); 535 painter.clear(); 536 }); 537 538 window.eventLoop(0); 539 } 540 --- 541 542 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 543 544 $(H3 $(ID topic-os-helpers) OS-specific helpers) 545 simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself. 546 547 See also: `xwindows.d` from my github. 548 549 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 550 `handleNativeEvent` and `handleNativeGlobalEvent`. 551 552 $(H3 $(ID topic-integration) Integration with other libraries) 553 Integration with a third-party event loop is possible. 554 555 On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d. 556 557 $(H3 $(ID topic-guis) GUI widgets) 558 simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you! 559 560 Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes. 561 562 Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.) 563 564 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 565 566 $(H2 Platform-specific tips and tricks) 567 568 X_tips: 569 570 On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release. 571 572 Windows_tips: 573 574 You can add icons or manifest files to your exe using a resource file. 575 576 To create a Windows .ico file, use the gimp or something. I'll write a helper 577 program later. 578 579 Create `yourapp.rc`: 580 581 ```rc 582 1 ICON filename.ico 583 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 584 ``` 585 586 And `yourapp.exe.manifest`: 587 588 ```xml 589 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 590 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 591 <assemblyIdentity 592 version="1.0.0.0" 593 processorArchitecture="*" 594 name="CompanyName.ProductName.YourApplication" 595 type="win32" 596 /> 597 <description>Your application description here.</description> 598 <dependency> 599 <dependentAssembly> 600 <assemblyIdentity 601 type="win32" 602 name="Microsoft.Windows.Common-Controls" 603 version="6.0.0.0" 604 processorArchitecture="*" 605 publicKeyToken="6595b64144ccf1df" 606 language="*" 607 /> 608 </dependentAssembly> 609 </dependency> 610 <application xmlns="urn:schemas-microsoft-com:asm.v3"> 611 <windowsSettings> 612 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style --> 613 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style --> 614 <!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text --> 615 <!-- to render crisply in DPI-unaware contexts --> 616 <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>--> 617 </windowsSettings> 618 </application> 619 </assembly> 620 ``` 621 622 You can also just distribute yourapp.exe.manifest as a separate file alongside yourapp.exe, or link it in to the exe with linker command lines `/manifest:embed` and `/manifestinput:yourfile.exe.manifest`. 623 624 Doing this lets you opt into various new things since Windows XP. 625 626 See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests 627 628 $(H2 Tips) 629 630 $(H3 Name conflicts) 631 632 simpledisplay has a lot of symbols and more are liable to be added without notice, since it contains its own bindings as needed to accomplish its goals. Some of these may conflict with other bindings you use. If so, you can use a static import in D, possibly combined with a selective import: 633 634 --- 635 static import sdpy = arsd.simpledisplay; 636 import arsd.simpledisplay : SimpleWindow; 637 638 void main() { 639 auto window = new SimpleWindow(); 640 sdpy.EventLoop.get.run(); 641 } 642 --- 643 644 $(H2 $(ID developer-notes) Developer notes) 645 646 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 647 implementation though. 648 649 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 650 suck. If I was rewriting it, I wouldn't do it that way again. 651 652 This file must not have any more required dependencies. If you need bindings, add 653 them right to this file. Once it gets into druntime and is there for a while, remove 654 bindings from here to avoid conflicts (or put them in an appropriate version block 655 so it continues to just work on old dmd), but wait a couple releases before making the 656 transition so this module remains usable with older versions of dmd. 657 658 You may have optional dependencies if needed by putting them in version blocks or 659 template functions. You may also extend the module with other modules with UFCS without 660 actually editing this - that is nice to do if you can. 661 662 Try to make functions work the same way across operating systems. I typically make 663 it thinly wrap Windows, then emulate that on Linux. 664 665 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 666 Phobos! So try to avoid it. 667 668 See more comments throughout the source. 669 670 I realize this file is fairly large, but over half that is just bindings at the bottom 671 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 672 to understand. I suggest you jump around the source by looking for a particular 673 declaration you're interested in, like `class SimpleWindow` using your editor's search 674 function, then look at one piece at a time. 675 676 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 677 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 678 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 679 680 I live in the eastern United States, so I will most likely not be around at night in 681 that US east timezone. 682 683 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 684 685 Building documentation: use my adrdox generator, `dub run adrdox`. 686 687 Examples: 688 689 $(DIV $(ID Event-example)) 690 $(H3 $(ID event-example) Event example) 691 This program creates a window and draws events inside them as they 692 happen, scrolling the text in the window as needed. Run this program 693 and experiment to get a feel for where basic input events take place 694 in the library. 695 696 --- 697 // dmd example.d simpledisplay.d color.d 698 import arsd.simpledisplay; 699 import std.conv; 700 701 void main() { 702 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 703 704 int y = 0; 705 706 void addLine(string text) { 707 auto painter = window.draw(); 708 709 if(y + painter.fontHeight >= window.height) { 710 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 711 y -= painter.fontHeight; 712 } 713 714 painter.outlineColor = Color.red; 715 painter.fillColor = Color.black; 716 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 717 718 painter.outlineColor = Color.white; 719 720 painter.drawText(Point(10, y), text); 721 722 y += painter.fontHeight; 723 } 724 725 window.eventLoop(1000, 726 () { 727 addLine("Timer went off!"); 728 }, 729 (KeyEvent event) { 730 addLine(to!string(event)); 731 }, 732 (MouseEvent event) { 733 addLine(to!string(event)); 734 }, 735 (dchar ch) { 736 addLine(to!string(ch)); 737 } 738 ); 739 } 740 --- 741 742 If you are interested in more game writing with D, check out my gamehelpers.d which builds upon simpledisplay, and its other stand-alone support modules, simpleaudio.d and joystick.d, too. 743 744 $(COMMENT 745 This program displays a pie chart. Clicking on a color will increase its share of the pie. 746 747 --- 748 749 --- 750 ) 751 752 History: 753 simpledisplay was stand alone until about 2015. It then added a dependency on [arsd.color] and changed its name to `arsd.simpledisplay`. 754 755 On March 4, 2023 (dub v11.0), it started importing [arsd.core] as well, making that a build-time requirement. 756 +/ 757 module arsd.simpledisplay; 758 759 import arsd.core; 760 761 // FIXME: tetris demo 762 // FIXME: space invaders demo 763 // FIXME: asteroids demo 764 765 /++ $(ID Pong-example) 766 $(H3 Pong) 767 768 This program creates a little Pong-like game. Player one is controlled 769 with the keyboard. Player two is controlled with the mouse. It demos 770 the pulse timer, event handling, and some basic drawing. 771 +/ 772 version(demos) 773 unittest { 774 // dmd example.d simpledisplay.d color.d 775 import arsd.simpledisplay; 776 777 enum paddleMovementSpeed = 8; 778 enum paddleHeight = 48; 779 780 void main() { 781 auto window = new SimpleWindow(600, 400, "Pong game!"); 782 783 int playerOnePosition, playerTwoPosition; 784 int playerOneMovement, playerTwoMovement; 785 int playerOneScore, playerTwoScore; 786 787 int ballX, ballY; 788 int ballDx, ballDy; 789 790 void serve() { 791 import std.random; 792 793 ballX = window.width / 2; 794 ballY = window.height / 2; 795 ballDx = uniform(-4, 4) * 3; 796 ballDy = uniform(-4, 4) * 3; 797 if(ballDx == 0) 798 ballDx = uniform(0, 2) == 0 ? 3 : -3; 799 } 800 801 serve(); 802 803 window.eventLoop(50, // set a 50 ms timer pulls 804 // This runs once per timer pulse 805 delegate () { 806 auto painter = window.draw(); 807 808 painter.clear(); 809 810 // Update everyone's motion 811 playerOnePosition += playerOneMovement; 812 playerTwoPosition += playerTwoMovement; 813 814 ballX += ballDx; 815 ballY += ballDy; 816 817 // Bounce off the top and bottom edges of the window 818 if(ballY + 7 >= window.height) 819 ballDy = -ballDy; 820 if(ballY - 8 <= 0) 821 ballDy = -ballDy; 822 823 // Bounce off the paddle, if it is in position 824 if(ballX - 8 <= 16) { 825 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 826 ballDx = -ballDx + 1; // add some speed to keep it interesting 827 ballDy += playerOneMovement; // and y movement based on your controls too 828 ballX = 24; // move it past the paddle so it doesn't wiggle inside 829 } else { 830 // Missed it 831 playerTwoScore ++; 832 serve(); 833 } 834 } 835 836 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 837 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 838 ballDx = -ballDx - 1; 839 ballDy += playerTwoMovement; 840 ballX = window.width - 24; 841 } else { 842 // Missed it 843 playerOneScore ++; 844 serve(); 845 } 846 } 847 848 // Draw the paddles 849 painter.outlineColor = Color.black; 850 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 851 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 852 853 // Draw the ball 854 painter.fillColor = Color.red; 855 painter.outlineColor = Color.yellow; 856 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 857 858 // Draw the score 859 painter.outlineColor = Color.blue; 860 import std.conv; 861 painter.drawText(Point(64, 4), to!string(playerOneScore)); 862 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 863 864 }, 865 delegate (KeyEvent event) { 866 // Player 1's controls are the arrow keys on the keyboard 867 if(event.key == Key.Down) 868 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 869 if(event.key == Key.Up) 870 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 871 872 }, 873 delegate (MouseEvent event) { 874 // Player 2's controls are mouse movement while the left button is held down 875 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 876 if(event.dy > 0) 877 playerTwoMovement = paddleMovementSpeed; 878 else if(event.dy < 0) 879 playerTwoMovement = -paddleMovementSpeed; 880 } else { 881 playerTwoMovement = 0; 882 } 883 } 884 ); 885 } 886 } 887 888 /++ $(H3 $(ID example-minesweeper) Minesweeper) 889 890 This minesweeper demo shows how we can implement another classic 891 game with simpledisplay and shows some mouse input and basic output 892 code. 893 +/ 894 version(demos) 895 unittest { 896 import arsd.simpledisplay; 897 898 enum GameSquare { 899 mine = 0, 900 clear, 901 m1, m2, m3, m4, m5, m6, m7, m8 902 } 903 904 enum UserSquare { 905 unknown, 906 revealed, 907 flagged, 908 questioned 909 } 910 911 enum GameState { 912 inProgress, 913 lose, 914 win 915 } 916 917 GameSquare[] board; 918 UserSquare[] userState; 919 GameState gameState; 920 int boardWidth; 921 int boardHeight; 922 923 bool isMine(int x, int y) { 924 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 925 return false; 926 return board[y * boardWidth + x] == GameSquare.mine; 927 } 928 929 GameState reveal(int x, int y) { 930 if(board[y * boardWidth + x] == GameSquare.clear) { 931 floodFill(userState, boardWidth, boardHeight, 932 UserSquare.unknown, UserSquare.revealed, 933 x, y, 934 (x, y) { 935 if(board[y * boardWidth + x] == GameSquare.clear) 936 return true; 937 else { 938 userState[y * boardWidth + x] = UserSquare.revealed; 939 return false; 940 } 941 }); 942 } else { 943 userState[y * boardWidth + x] = UserSquare.revealed; 944 if(isMine(x, y)) 945 return GameState.lose; 946 } 947 948 foreach(state; userState) { 949 if(state == UserSquare.unknown || state == UserSquare.questioned) 950 return GameState.inProgress; 951 } 952 953 return GameState.win; 954 } 955 956 void initializeBoard(int width, int height, int numberOfMines) { 957 boardWidth = width; 958 boardHeight = height; 959 board.length = width * height; 960 961 userState.length = width * height; 962 userState[] = UserSquare.unknown; 963 964 import std.algorithm, std.random, std.range; 965 966 board[] = GameSquare.clear; 967 968 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 969 board[minePosition] = GameSquare.mine; 970 971 int x; 972 int y; 973 foreach(idx, ref square; board) { 974 if(square == GameSquare.clear) { 975 int danger = 0; 976 danger += isMine(x-1, y-1)?1:0; 977 danger += isMine(x-1, y)?1:0; 978 danger += isMine(x-1, y+1)?1:0; 979 danger += isMine(x, y-1)?1:0; 980 danger += isMine(x, y+1)?1:0; 981 danger += isMine(x+1, y-1)?1:0; 982 danger += isMine(x+1, y)?1:0; 983 danger += isMine(x+1, y+1)?1:0; 984 985 square = cast(GameSquare) (danger + 1); 986 } 987 988 x++; 989 if(x == width) { 990 x = 0; 991 y++; 992 } 993 } 994 } 995 996 void redraw(SimpleWindow window) { 997 import std.conv; 998 999 auto painter = window.draw(); 1000 1001 painter.clear(); 1002 1003 final switch(gameState) with(GameState) { 1004 case inProgress: 1005 break; 1006 case win: 1007 painter.fillColor = Color.green; 1008 painter.drawRectangle(Point(0, 0), window.width, window.height); 1009 return; 1010 case lose: 1011 painter.fillColor = Color.red; 1012 painter.drawRectangle(Point(0, 0), window.width, window.height); 1013 return; 1014 } 1015 1016 int x = 0; 1017 int y = 0; 1018 1019 foreach(idx, square; board) { 1020 auto state = userState[idx]; 1021 1022 final switch(state) with(UserSquare) { 1023 case unknown: 1024 painter.outlineColor = Color.black; 1025 painter.fillColor = Color(128,128,128); 1026 1027 painter.drawRectangle( 1028 Point(x * 20, y * 20), 1029 20, 20 1030 ); 1031 break; 1032 case revealed: 1033 if(square == GameSquare.clear) { 1034 painter.outlineColor = Color.white; 1035 painter.fillColor = Color.white; 1036 1037 painter.drawRectangle( 1038 Point(x * 20, y * 20), 1039 20, 20 1040 ); 1041 } else { 1042 painter.outlineColor = Color.black; 1043 painter.fillColor = Color.white; 1044 1045 painter.drawText( 1046 Point(x * 20, y * 20), 1047 to!string(square)[1..2], 1048 Point(x * 20 + 20, y * 20 + 20), 1049 TextAlignment.Center | TextAlignment.VerticalCenter); 1050 } 1051 break; 1052 case flagged: 1053 painter.outlineColor = Color.black; 1054 painter.fillColor = Color.red; 1055 painter.drawRectangle( 1056 Point(x * 20, y * 20), 1057 20, 20 1058 ); 1059 break; 1060 case questioned: 1061 painter.outlineColor = Color.black; 1062 painter.fillColor = Color.yellow; 1063 painter.drawRectangle( 1064 Point(x * 20, y * 20), 1065 20, 20 1066 ); 1067 break; 1068 } 1069 1070 x++; 1071 if(x == boardWidth) { 1072 x = 0; 1073 y++; 1074 } 1075 } 1076 1077 } 1078 1079 void main() { 1080 auto window = new SimpleWindow(200, 200); 1081 1082 initializeBoard(10, 10, 10); 1083 1084 redraw(window); 1085 window.eventLoop(0, 1086 delegate (MouseEvent me) { 1087 if(me.type != MouseEventType.buttonPressed) 1088 return; 1089 auto x = me.x / 20; 1090 auto y = me.y / 20; 1091 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1092 if(me.button == MouseButton.left) { 1093 gameState = reveal(x, y); 1094 } else { 1095 userState[y*boardWidth+x] = UserSquare.flagged; 1096 } 1097 redraw(window); 1098 } 1099 } 1100 ); 1101 } 1102 } 1103 1104 /* 1105 version(OSX) { 1106 version=without_opengl; 1107 version=allow_unimplemented_features; 1108 version=OSXCocoa; 1109 pragma(linkerDirective, "-framework Cocoa"); 1110 } 1111 */ 1112 1113 version(without_opengl) { 1114 enum SdpyIsUsingIVGLBinds = false; 1115 } else /*version(Posix)*/ { 1116 static if (__traits(compiles, (){import iv.glbinds;})) { 1117 enum SdpyIsUsingIVGLBinds = true; 1118 public import iv.glbinds; 1119 //pragma(msg, "SDPY: using iv.glbinds"); 1120 } else { 1121 enum SdpyIsUsingIVGLBinds = false; 1122 } 1123 //} else { 1124 // enum SdpyIsUsingIVGLBinds = false; 1125 } 1126 1127 1128 version(Windows) { 1129 //import core.sys.windows.windows; 1130 import core.sys.windows.winnls; 1131 import core.sys.windows.windef; 1132 import core.sys.windows.basetyps; 1133 import core.sys.windows.winbase; 1134 import core.sys.windows.winuser; 1135 import core.sys.windows.shellapi; 1136 import core.sys.windows.wingdi; 1137 static import gdi = core.sys.windows.wingdi; // so i 1138 1139 pragma(lib, "gdi32"); 1140 pragma(lib, "user32"); 1141 1142 // for AlphaBlend... a breaking change.... 1143 version(CRuntime_DigitalMars) { } else 1144 pragma(lib, "msimg32"); 1145 } else version (linux) { 1146 //k8: this is hack for rdmd. sorry. 1147 static import core.sys.linux.epoll; 1148 static import core.sys.linux.timerfd; 1149 } 1150 1151 1152 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1153 1154 // http://wiki.dlang.org/Simpledisplay.d 1155 1156 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1157 1158 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1159 // but can i control the scroll lock led 1160 1161 1162 // Note: if you are using Image on X, you might want to do: 1163 /* 1164 static if(UsingSimpledisplayX11) { 1165 if(!Image.impl.xshmAvailable) { 1166 // the images will use the slower XPutImage, you might 1167 // want to consider an alternative method to get better speed 1168 } 1169 } 1170 1171 If the shared memory extension is available though, simpledisplay uses it 1172 for a significant speed boost whenever you draw large Images. 1173 */ 1174 1175 // 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. 1176 1177 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1178 1179 /* 1180 Biggest FIXME: 1181 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1182 1183 clean up opengl contexts when their windows close 1184 1185 fix resizing the bitmaps/pixmaps 1186 */ 1187 1188 // BTW on Windows: 1189 // -L/SUBSYSTEM:WINDOWS:5.0 1190 // to dmd will make a nice windows binary w/o a console if you want that. 1191 1192 /* 1193 Stuff to add: 1194 1195 use multibyte functions everywhere we can 1196 1197 OpenGL windows 1198 more event stuff 1199 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1200 1201 1202 resizeEvent 1203 and make the windows non-resizable by default, 1204 or perhaps stretched (if I can find something in X like StretchBlt) 1205 1206 take a screenshot function! 1207 1208 Pens and brushes? 1209 Maybe a global event loop? 1210 1211 Mouse deltas 1212 Key items 1213 */ 1214 1215 /* 1216 From MSDN: 1217 1218 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1219 1220 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. 1221 1222 */ 1223 1224 version(linux) { 1225 version = X11; 1226 version(without_libnotify) { 1227 // we cool 1228 } 1229 else 1230 version = libnotify; 1231 } 1232 1233 version(libnotify) { 1234 pragma(lib, "dl"); 1235 import core.sys.posix.dlfcn; 1236 1237 void delegate()[int] libnotify_action_delegates; 1238 int libnotify_action_delegates_count; 1239 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1240 auto idx = cast(int) user_data; 1241 if(auto dgptr = idx in libnotify_action_delegates) { 1242 (*dgptr)(); 1243 libnotify_action_delegates.remove(idx); 1244 } 1245 } 1246 1247 struct C_DynamicLibrary { 1248 void* handle; 1249 this(string name) { 1250 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1251 if(handle is null) 1252 throw new Exception("dlopen"); 1253 } 1254 1255 void close() { 1256 dlclose(handle); 1257 } 1258 1259 ~this() { 1260 // close 1261 } 1262 1263 // FIXME: this looks up by name every time.... 1264 template call(string func, Ret, Args...) { 1265 extern(C) Ret function(Args) fptr; 1266 typeof(fptr) call() { 1267 fptr = cast(typeof(fptr)) dlsym(handle, func); 1268 return fptr; 1269 } 1270 } 1271 } 1272 1273 C_DynamicLibrary* libnotify; 1274 } 1275 1276 version(OSX) { 1277 version(OSXCocoa) {} 1278 else { version = X11; } 1279 } 1280 //version = OSXCocoa; // this was written by KennyTM 1281 version(FreeBSD) 1282 version = X11; 1283 version(Solaris) 1284 version = X11; 1285 1286 version(X11) { 1287 version(without_xft) {} 1288 else version=with_xft; 1289 } 1290 1291 void featureNotImplemented()() { 1292 version(allow_unimplemented_features) 1293 throw new NotYetImplementedException(); 1294 else 1295 static assert(0); 1296 } 1297 1298 // these are so the static asserts don't trigger unless you want to 1299 // add support to it for an OS 1300 version(Windows) 1301 version = with_timer; 1302 version(linux) 1303 version = with_timer; 1304 1305 version(with_timer) 1306 enum bool SimpledisplayTimerAvailable = true; 1307 else 1308 enum bool SimpledisplayTimerAvailable = false; 1309 1310 /// 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. 1311 version(Windows) 1312 enum bool UsingSimpledisplayWindows = true; 1313 else 1314 enum bool UsingSimpledisplayWindows = false; 1315 1316 /// 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. 1317 version(X11) 1318 enum bool UsingSimpledisplayX11 = true; 1319 else 1320 enum bool UsingSimpledisplayX11 = false; 1321 1322 /// 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. 1323 version(OSXCocoa) 1324 enum bool UsingSimpledisplayCocoa = true; 1325 else 1326 enum bool UsingSimpledisplayCocoa = false; 1327 1328 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1329 version(Windows) 1330 enum multipleWindowsSupported = true; 1331 else version(X11) 1332 enum multipleWindowsSupported = true; 1333 else version(OSXCocoa) 1334 enum multipleWindowsSupported = true; 1335 else 1336 static assert(0); 1337 1338 version(without_opengl) 1339 enum bool OpenGlEnabled = false; 1340 else 1341 enum bool OpenGlEnabled = true; 1342 1343 /++ 1344 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1345 If you mix this in above your `main` function, you no longer need to use the linker 1346 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1347 1348 It does nothing if not compiling for Windows, so you need not version it out yourself. 1349 1350 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1351 stderr writeln. It will fail and throw an exception. 1352 1353 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1354 1355 History: 1356 Added November 24, 2021 (dub v10.4) 1357 +/ 1358 mixin template EnableWindowsSubsystem() { 1359 version(Windows) 1360 version(CRuntime_Microsoft) { 1361 pragma(linkerDirective, "/subsystem:windows"); 1362 version(LDC) 1363 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1364 else 1365 pragma(linkerDirective, "/entry:mainCRTStartup"); 1366 } 1367 } 1368 1369 1370 /++ 1371 After selecting a type from [WindowTypes], you may further customize 1372 its behavior by setting one or more of these flags. 1373 1374 1375 The different window types have different meanings of `normal`. If the 1376 window type already is a good match for what you want to do, you should 1377 just use [WindowFlags.normal], the default, which will do the right thing 1378 for your users. 1379 1380 The window flags will not always be honored by the operating system 1381 and window managers; they are hints, not commands. 1382 +/ 1383 enum WindowFlags : int { 1384 normal = 0, /// 1385 skipTaskbar = 1, /// 1386 alwaysOnTop = 2, /// 1387 alwaysOnBottom = 4, /// 1388 cannotBeActivated = 8, /// 1389 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. 1390 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. 1391 /++ 1392 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1393 it is still a top-level window. This should NOT be set separately for most window types. 1394 1395 A transient window will not keep the application open if its main window closes. 1396 1397 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1398 1399 1400 From the ICCM: 1401 1402 $(BLOCKQUOTE 1403 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. 1404 1405 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1406 ) 1407 1408 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1409 1410 History: 1411 Added February 23, 2021 but not yet stabilized. 1412 +/ 1413 transient = 64, 1414 /++ 1415 This indicates that the window manages its own platform-specific child window input focus. You must use a delegate, [SimpleWindow.setRequestedInputFocus], to set the input when requested. This delegate returns the handle to the window that should receive the focus. 1416 1417 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1418 1419 History: 1420 Added April 1, 2022 1421 +/ 1422 managesChildWindowFocus = 128, 1423 1424 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1425 } 1426 1427 /++ 1428 When creating a window, you can pass a type to SimpleWindow's constructor, 1429 then further customize the window by changing `WindowFlags`. 1430 1431 1432 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1433 use. The others are there to build a foundation for a higher level GUI toolkit, 1434 but are themselves not as high level as you might think from their names. 1435 1436 This list is based on the EMWH spec for X11. 1437 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1438 +/ 1439 enum WindowTypes : int { 1440 /// An ordinary application window. 1441 normal, 1442 /// 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. 1443 undecorated, 1444 /// 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. 1445 eventOnly, 1446 /// A drop down menu, such as from a menu bar 1447 dropdownMenu, 1448 /// A popup menu, such as from a right click 1449 popupMenu, 1450 /// A popup bubble notification 1451 notification, 1452 /* 1453 menu, /// a tearable menu bar 1454 splashScreen, /// a loading splash screen for your application 1455 tooltip, /// A tiny window showing temporary help text or something. 1456 comboBoxDropdown, 1457 dialog, 1458 toolbar 1459 */ 1460 /// a child nested inside the parent. You must pass a parent window to the ctor 1461 nestedChild, 1462 1463 /++ 1464 The type you get when you pass in an existing browser handle, which means most 1465 of simpledisplay's fancy things will not be done since they were never set up. 1466 1467 Using this to the main SimpleWindow constructor explicitly will trigger an assertion 1468 failure; you should use the existing handle constructor. 1469 1470 History: 1471 Added November 17, 2022 (previously it would have type `normal`) 1472 +/ 1473 minimallyWrapped 1474 } 1475 1476 1477 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1478 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1479 private __gshared char* sdpyWindowClassStr = null; 1480 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1481 1482 /** 1483 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1484 You may want to change context version if you want to use advanced shaders or 1485 other modern OpenGL techinques. This setting doesn't affect already created 1486 windows. You may use version 2.1 as your default, which should be supported 1487 by any box since 2006, so seems to be a reasonable choice. 1488 1489 Note that by default version is set to `0`, which forces SimpleDisplay to use 1490 old context creation code without any version specified. This is the safest 1491 way to init OpenGL, but it may not give you access to advanced features. 1492 1493 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1494 */ 1495 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1496 1497 /** 1498 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1499 pipeline functions, and without "compatible" mode you won't be able to use 1500 your old non-shader-based code with such contexts. By default SimpleDisplay 1501 creates compatible context, so you can gradually upgrade your OpenGL code if 1502 you want to (or leave it as is, as it should "just work"). 1503 */ 1504 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1505 1506 /** 1507 Set to `true` to allow creating OpenGL context with lower version than requested 1508 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1509 `openGLContextFallbackActivated()` will return `true`. 1510 */ 1511 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1512 1513 /** 1514 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1515 */ 1516 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1517 1518 1519 /** 1520 Set window class name for all following `new SimpleWindow()` calls. 1521 1522 WARNING! For Windows, you should set your class name before creating any 1523 window, and NEVER change it after that! 1524 */ 1525 void sdpyWindowClass (const(char)[] v) { 1526 import core.stdc.stdlib : realloc; 1527 if (v.length == 0) v = "SimpleDisplayWindow"; 1528 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1529 if (sdpyWindowClassStr is null) return; // oops 1530 sdpyWindowClassStr[0..v.length+1] = 0; 1531 sdpyWindowClassStr[0..v.length] = v[]; 1532 } 1533 1534 /** 1535 Get current window class name. 1536 */ 1537 string sdpyWindowClass () { 1538 if (sdpyWindowClassStr is null) return null; 1539 foreach (immutable idx; 0..size_t.max-1) { 1540 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1541 } 1542 return null; 1543 } 1544 1545 /++ 1546 Returns the logical DPI of the default monitor. [0] is width, [1] is height (they are usually the same though). You may wish to round the numbers off. This isn't necessarily related to the physical side of the screen; it is associated with a user-defined scaling factor. 1547 1548 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1549 +/ 1550 float[2] getDpi() { 1551 float[2] dpi; 1552 version(Windows) { 1553 HDC screen = GetDC(null); 1554 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1555 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1556 } else version(X11) { 1557 auto display = XDisplayConnection.get; 1558 auto screen = DefaultScreen(display); 1559 1560 void fallback() { 1561 /+ 1562 // 25.4 millimeters in an inch... 1563 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1564 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1565 +/ 1566 1567 // the physical size isn't actually as important as the logical size since this is 1568 // all about scaling really 1569 dpi[0] = 96; 1570 dpi[1] = 96; 1571 } 1572 1573 auto xft = getXftDpi(); 1574 if(xft is float.init) 1575 fallback(); 1576 else { 1577 dpi[0] = xft; 1578 dpi[1] = xft; 1579 } 1580 } 1581 1582 return dpi; 1583 } 1584 1585 version(X11) 1586 float getXftDpi() { 1587 auto display = XDisplayConnection.get; 1588 1589 char* resourceString = XResourceManagerString(display); 1590 XrmInitialize(); 1591 1592 if (resourceString) { 1593 auto db = XrmGetStringDatabase(resourceString); 1594 XrmValue value; 1595 char* type; 1596 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1597 if (value.addr) { 1598 import core.stdc.stdlib; 1599 return atof(cast(char*) value.addr); 1600 } 1601 } 1602 } 1603 1604 return float.init; 1605 } 1606 1607 /++ 1608 Implementation used by [SimpleWindow.takeScreenshot]. 1609 1610 Params: 1611 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1612 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1613 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1614 x = the x-offset of the image to capture, from the left. 1615 y = the y-offset of the image to capture, from the top. 1616 1617 History: 1618 Added on March 14, 2021 1619 1620 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1621 1622 +/ 1623 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1624 TrueColorImage got; 1625 version(X11) { 1626 auto display = XDisplayConnection.get; 1627 if(handle == 0) 1628 handle = RootWindow(display, DefaultScreen(display)); 1629 1630 if(width == 0 || height == 0) { 1631 Window root; 1632 int xpos, ypos; 1633 uint widthret, heightret, borderret, depthret; 1634 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1635 1636 if(width == 0) 1637 width = widthret; 1638 if(height == 0) 1639 height = heightret; 1640 } 1641 1642 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1643 1644 // https://github.com/adamdruppe/arsd/issues/98 1645 1646 auto i = new Image(image); 1647 got = i.toTrueColorImage(); 1648 1649 XDestroyImage(image); 1650 } else version(Windows) { 1651 auto hdc = GetDC(handle); 1652 scope(exit) ReleaseDC(handle, hdc); 1653 1654 if(width == 0 || height == 0) { 1655 BITMAP bmHeader; 1656 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1657 GetObject(bm, BITMAP.sizeof, &bmHeader); 1658 if(width == 0) 1659 width = bmHeader.bmWidth; 1660 if(height == 0) 1661 height = bmHeader.bmHeight; 1662 } 1663 1664 auto i = new Image(width, height); 1665 HDC hdcMem = CreateCompatibleDC(hdc); 1666 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1667 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1668 SelectObject(hdcMem, hbmOld); 1669 DeleteDC(hdcMem); 1670 1671 got = i.toTrueColorImage(); 1672 } else featureNotImplemented(); 1673 1674 return got; 1675 } 1676 1677 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1678 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1679 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1680 1681 version(Windows) 1682 shared static this() { 1683 auto lib = LoadLibrary("User32.dll"); 1684 if(lib is null) 1685 return; 1686 //scope(exit) 1687 //FreeLibrary(lib); 1688 1689 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1690 1691 if(SetProcessDpiAwarenessContext is null) 1692 return; 1693 1694 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1695 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1696 //writeln(GetLastError()); 1697 } 1698 1699 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1700 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1701 } 1702 1703 /++ 1704 Blocking mode for event loop calls associated with a window instance. 1705 1706 History: 1707 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1708 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1709 is, all would block until the application quit. 1710 1711 That behavior can still be achieved here with `untilApplicationQuits`, 1712 or explicitly calling the top-level `EventLoop.get.run` function. 1713 +/ 1714 enum BlockingMode { 1715 /++ 1716 The event loop call will block until the whole application is ready 1717 to quit if it is the only one running, but if it is nested inside 1718 another one, it will only block until the window you're calling it on 1719 closes. 1720 +/ 1721 automatic = 0x00, 1722 /++ 1723 The event loop call will only return when the whole application 1724 is ready to quit. This usually means all windows have been closed. 1725 1726 This is appropriate for your main application event loop. 1727 +/ 1728 untilApplicationQuits = 0x01, 1729 /++ 1730 The event loop will return when the window you're calling it on 1731 closes. If there are other windows still open, they may be destroyed 1732 unless you have another event loop running later. 1733 1734 This might be appropriate for a modal dialog box loop. Remember that 1735 other windows are still processing input though, so you can end up 1736 with a lengthy call stack if this happens in a loop, similar to a 1737 recursive function (well, it literally is a recursive function, just 1738 not an obvious looking one). 1739 +/ 1740 untilWindowCloses = 0x02, 1741 /++ 1742 If an event loop is already running, this call will immediately 1743 return, allowing the existing loop to handle it. If not, this call 1744 will block until the condition you bitwise-or into the flag. 1745 1746 The default is to block until the application quits, same as with 1747 the `automatic` setting (since if it were nested, which triggers until 1748 window closes in automatic, this flag would instead not block at all), 1749 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1750 it will only nest until the window closes. You might want that if you are 1751 going to open two windows simultaneously and want closing just one of them 1752 to trigger the event loop return. 1753 +/ 1754 onlyIfNotNested = 0x10, 1755 } 1756 1757 /++ 1758 The flagship window class. 1759 1760 1761 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1762 out of more advanced or complex features of the underlying windowing system. 1763 1764 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1765 and get a suitable window to work with. 1766 1767 From there, you can opt into additional features, like custom resizability and OpenGL support 1768 with the next two constructor arguments. Or, if you need even more, you can set a window type 1769 and customization flags with the final two constructor arguments. 1770 1771 If none of that works for you, you can also create a window using native function calls, then 1772 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1773 though, if you do this, managing the window is still your own responsibility! Notably, you 1774 will need to destroy it yourself. 1775 +/ 1776 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1777 1778 /++ 1779 Copies the window's current state into a [TrueColorImage]. 1780 1781 Be warned: this can be a very slow operation 1782 1783 History: 1784 Actually implemented on March 14, 2021 1785 +/ 1786 TrueColorImage takeScreenshot() { 1787 version(Windows) 1788 return trueColorImageFromNativeHandle(impl.hwnd, _width, _height); 1789 else version(OSXCocoa) 1790 throw new NotYetImplementedException(); 1791 else 1792 return trueColorImageFromNativeHandle(impl.window, _width, _height); 1793 } 1794 1795 /++ 1796 Returns the actual logical DPI for the window on its current display monitor. If the window 1797 straddles monitors, it will return the value of one or the other in a platform-defined manner. 1798 1799 Please note this function may return zero if it doesn't know the answer! 1800 1801 1802 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 1803 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 1804 1805 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 1806 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 1807 window primarily resides on by checking the center point of the window against the monitor map. 1808 1809 Returns: 1810 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 1811 assumes the X and Y dpi are the same. 1812 1813 History: 1814 Added November 26, 2021 (dub v10.4) 1815 1816 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 1817 always a logical value on Windows and usually one on Linux too, so now the docs reflect 1818 that. 1819 1820 Bugs: 1821 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 1822 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 1823 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 1824 and 1.5 on the secondary monitor. 1825 1826 The local dpi is not necessarily related to the physical dpi of the monitor. The name 1827 is a historical misnomer - the real thing of interest is the scale factor and due to 1828 compatibility concerns the scale would modify dpi values to trick applications. But since 1829 that's the terminology common out there, I used it too. 1830 1831 See_Also: 1832 [getDpi] gives the value provided for the default monitor. Not necessarily the same 1833 as this since the window many be on a different monitor, but it is a reasonable fallback 1834 to use if `actualDpi` returns 0. 1835 1836 [onDpiChanged] is changed when `actualDpi` has changed. 1837 +/ 1838 int actualDpi() { 1839 if(!actualDpiLoadAttempted) { 1840 // FIXME: do the actual monitor we are on 1841 // and on X this is a good chance to load the monitor map. 1842 version(Windows) { 1843 if(GetDpiForWindow) 1844 actualDpi_ = GetDpiForWindow(impl.hwnd); 1845 } else version(X11) { 1846 if(!xRandrInfoLoadAttemped) { 1847 xRandrInfoLoadAttemped = true; 1848 if(!XRandrLibrary.attempted) { 1849 XRandrLibrary.loadDynamicLibrary(); 1850 } 1851 1852 if(XRandrLibrary.loadSuccessful) { 1853 auto display = XDisplayConnection.get; 1854 int scratch; 1855 int major, minor; 1856 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 1857 goto fallback; 1858 1859 XRRQueryVersion(display, &major, &minor); 1860 if(major <= 1 && minor < 5) 1861 goto fallback; 1862 1863 int count; 1864 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 1865 if(monitors is null) 1866 goto fallback; 1867 scope(exit) XRRFreeMonitors(monitors); 1868 1869 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 1870 MonitorInfo.info.assumeSafeAppend(); 1871 foreach(idx, monitor; monitors[0 .. count]) { 1872 MonitorInfo.info ~= MonitorInfo( 1873 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1874 Size(monitor.mwidth, monitor.mheight), 1875 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 1876 ); 1877 1878 /+ 1879 if(monitor.mwidth == 0 || monitor.mheight == 0) 1880 // unknown physical size, just guess 96 to avoid divide by zero 1881 MonitorInfo.info ~= MonitorInfo( 1882 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1883 Size(monitor.mwidth, monitor.mheight), 1884 96 1885 ); 1886 else 1887 // and actual thing 1888 MonitorInfo.info ~= MonitorInfo( 1889 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1890 Size(monitor.mwidth, monitor.mheight), 1891 minInternal( 1892 // millimeter to int then rounding up. 1893 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 1894 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 1895 ) 1896 ); 1897 +/ 1898 } 1899 // import std.stdio; writeln("Here", MonitorInfo.info); 1900 } 1901 } 1902 1903 if(XRandrLibrary.loadSuccessful) { 1904 updateActualDpi(true); 1905 //import std.stdio; writeln("updated"); 1906 1907 if(!requestedInput) { 1908 // this is what requests live updates should the configuration change 1909 // each time you select input, it sends an initial event, so very important 1910 // to not get into a loop of selecting input, getting event, updating data, 1911 // and reselecting input... 1912 requestedInput = true; 1913 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 1914 //import std.stdio; writeln("requested input"); 1915 } 1916 } else { 1917 fallback: 1918 // make sure we disable events that aren't coming 1919 xrrEventBase = -1; 1920 // best guess... respect the custom scaling user command to some extent at least though 1921 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 1922 } 1923 } 1924 actualDpiLoadAttempted = true; 1925 } 1926 return actualDpi_; 1927 } 1928 1929 private int actualDpi_; 1930 private bool actualDpiLoadAttempted; 1931 1932 version(X11) private { 1933 bool requestedInput; 1934 static bool xRandrInfoLoadAttemped; 1935 struct MonitorInfo { 1936 Rectangle position; 1937 Size size; 1938 int dpi; 1939 1940 static MonitorInfo[] info; 1941 } 1942 bool screenPositionKnown; 1943 int screenPositionX; 1944 int screenPositionY; 1945 void updateActualDpi(bool loadingNow = false) { 1946 if(!loadingNow && !actualDpiLoadAttempted) 1947 actualDpi(); // just to make it do the load 1948 foreach(idx, m; MonitorInfo.info) { 1949 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 1950 bool changed = actualDpi_ && actualDpi_ != m.dpi; 1951 actualDpi_ = m.dpi; 1952 //import std.stdio; writeln("monitor ", idx); 1953 if(changed && onDpiChanged) 1954 onDpiChanged(); 1955 break; 1956 } 1957 } 1958 } 1959 } 1960 1961 /++ 1962 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 1963 or if the window is moved to a new remote connection or a monitor is hot-swapped. 1964 1965 History: 1966 Added November 26, 2021 (dub v10.4) 1967 1968 See_Also: 1969 [actualDpi] 1970 +/ 1971 void delegate() onDpiChanged; 1972 1973 version(X11) { 1974 void recreateAfterDisconnect() { 1975 if(!stateDiscarded) return; 1976 1977 if(_parent !is null && _parent.stateDiscarded) 1978 _parent.recreateAfterDisconnect(); 1979 1980 bool wasHidden = hidden; 1981 1982 activeScreenPainter = null; // should already be done but just to confirm 1983 1984 actualDpi_ = 0; 1985 actualDpiLoadAttempted = false; 1986 xRandrInfoLoadAttemped = false; 1987 1988 impl.createWindow(_width, _height, _title, openglMode, _parent); 1989 1990 if(auto dh = dropHandler) { 1991 dropHandler = null; 1992 enableDragAndDrop(this, dh); 1993 } 1994 1995 if(recreateAdditionalConnectionState) 1996 recreateAdditionalConnectionState(); 1997 1998 hidden = wasHidden; 1999 stateDiscarded = false; 2000 } 2001 2002 bool stateDiscarded; 2003 void discardConnectionState() { 2004 if(XDisplayConnection.display) 2005 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 2006 if(discardAdditionalConnectionState) 2007 discardAdditionalConnectionState(); 2008 stateDiscarded = true; 2009 } 2010 2011 void delegate() discardAdditionalConnectionState; 2012 void delegate() recreateAdditionalConnectionState; 2013 2014 } 2015 2016 private DropHandler dropHandler; 2017 2018 SimpleWindow _parent; 2019 bool beingOpenKeepsAppOpen = true; 2020 /++ 2021 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. 2022 2023 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2024 2025 Params: 2026 2027 width = the width of the window's client area, in pixels 2028 height = the height of the window's client area, in pixels 2029 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2030 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2031 resizable = [Resizability] has three options: 2032 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2033 $(P `fixedSize` will not allow the user to resize the window.) 2034 $(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.) 2035 windowType = The type of window you want to make. 2036 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. 2037 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". 2038 +/ 2039 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) { 2040 claimGuiThread(); 2041 version(sdpy_thread_checks) assert(thisIsGuiThread); 2042 this._width = this._virtualWidth = width; 2043 this._height = this._virtualHeight = height; 2044 this.openglMode = opengl; 2045 version(X11) { 2046 // auto scale not implemented except with opengl and even there it is kinda weird 2047 if(resizable == Resizability.automaticallyScaleIfPossible && opengl == OpenGlOptions.no) 2048 resizable = Resizability.fixedSize; 2049 } 2050 this.resizability = resizable; 2051 this.windowType = windowType; 2052 this.customizationFlags = customizationFlags; 2053 this._title = (title is null ? "D Application" : title); 2054 this._parent = parent; 2055 impl.createWindow(width, height, this._title, opengl, parent); 2056 2057 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2058 beingOpenKeepsAppOpen = false; 2059 } 2060 2061 /// ditto 2062 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2063 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2064 } 2065 2066 /// Same as above, except using the `Size` struct instead of separate width and height. 2067 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2068 this(size.width, size.height, title, opengl, resizable); 2069 } 2070 2071 /// ditto 2072 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2073 this(size, title, opengl, resizable); 2074 } 2075 2076 2077 /++ 2078 Creates a window based on the given [Image]. It's client area 2079 width and height is equal to the image. (A window's client area 2080 is the drawable space inside; it excludes the title bar, etc.) 2081 2082 Windows based on images will not be resizable and do not use OpenGL. 2083 2084 It will draw the image in upon creation, but this will be overwritten 2085 upon any draws, including the initial window visible event. 2086 2087 You probably do not want to use this and it may be removed from 2088 the library eventually, or I might change it to be a "permanent" 2089 background image; one that is automatically drawn on it before any 2090 other drawing event. idk. 2091 +/ 2092 this(Image image, string title = null) { 2093 this(image.width, image.height, title); 2094 this.image = image; 2095 } 2096 2097 /++ 2098 Wraps a native window handle with very little additional processing - notably no destruction 2099 this is incomplete so don't use it for much right now. The purpose of this is to make native 2100 windows created through the low level API (so you can use platform-specific options and 2101 other details SimpleWindow does not expose) available to the event loop wrappers. 2102 +/ 2103 this(NativeWindowHandle nativeWindow) { 2104 windowType = WindowTypes.minimallyWrapped; 2105 version(Windows) 2106 impl.hwnd = nativeWindow; 2107 else version(X11) { 2108 impl.window = nativeWindow; 2109 if(nativeWindow) 2110 display = XDisplayConnection.get(); // get initial display to not segfault 2111 } else version(OSXCocoa) 2112 throw new NotYetImplementedException(); 2113 else featureNotImplemented(); 2114 // FIXME: set the size correctly 2115 _width = 1; 2116 _height = 1; 2117 if(nativeWindow) 2118 nativeMapping[nativeWindow] = this; 2119 2120 beingOpenKeepsAppOpen = false; 2121 2122 if(nativeWindow) 2123 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2124 _suppressDestruction = true; // so it doesn't try to close 2125 } 2126 2127 /++ 2128 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2129 The delegate will be called when the window manager asks you to take focus. 2130 2131 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2132 2133 History: 2134 Added April 1, 2022 (dub v10.8) 2135 +/ 2136 SimpleWindow delegate() setRequestedInputFocus; 2137 2138 /// Experimental, do not use yet 2139 /++ 2140 Grabs exclusive input from the user until you release it with 2141 [releaseInputGrab]. 2142 2143 2144 Note: it is extremely rude to do this without good reason. 2145 Reasons may include doing some kind of mouse drag operation 2146 or popping up a temporary menu that should get events and will 2147 be dismissed at ease by the user clicking away. 2148 2149 Params: 2150 keyboard = do you want to grab keyboard input? 2151 mouse = grab mouse input? 2152 confine = confine the mouse cursor to inside this window? 2153 2154 History: 2155 Prior to March 11, 2021, grabbing the keyboard would always also 2156 set the X input focus. Now, it only focuses if it is a non-transient 2157 window and otherwise manages the input direction internally. 2158 2159 This means spurious focus/blur events will no longer be sent and the 2160 application will not steal focus from other applications (which the 2161 window manager may have rejected anyway). 2162 +/ 2163 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2164 static if(UsingSimpledisplayX11) { 2165 XSync(XDisplayConnection.get, 0); 2166 if(keyboard) { 2167 if(isTransient && _parent) { 2168 /* 2169 FIXME: 2170 setting the keyboard focus is not actually that helpful, what I more likely want 2171 is the events from the parent window to be sent over here if we're transient. 2172 */ 2173 2174 _parent.inputProxy = this; 2175 } else { 2176 2177 SimpleWindow setTo; 2178 if(setRequestedInputFocus !is null) 2179 setTo = setRequestedInputFocus(); 2180 if(setTo is null) 2181 setTo = this; 2182 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2183 } 2184 } 2185 if(mouse) { 2186 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2187 EventMask.PointerMotionMask // FIXME: not efficient 2188 | EventMask.ButtonPressMask 2189 | EventMask.ButtonReleaseMask 2190 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2191 ) 2192 { 2193 XSync(XDisplayConnection.get, 0); 2194 import core.stdc.stdio; 2195 printf("Grab input failed %d\n", res); 2196 //throw new Exception("Grab input failed"); 2197 } else { 2198 // cool 2199 } 2200 } 2201 2202 } else version(Windows) { 2203 // FIXME: keyboard? 2204 SetCapture(impl.hwnd); 2205 if(confine) { 2206 RECT rcClip; 2207 //RECT rcOldClip; 2208 //GetClipCursor(&rcOldClip); 2209 GetWindowRect(hwnd, &rcClip); 2210 ClipCursor(&rcClip); 2211 } 2212 } else version(OSXCocoa) { 2213 throw new NotYetImplementedException(); 2214 } else static assert(0); 2215 } 2216 2217 private Point imePopupLocation = Point(0, 0); 2218 2219 /++ 2220 Sets the location for the IME (input method editor) to pop up when the user activates it. 2221 2222 Bugs: 2223 Not implemented outside X11. 2224 +/ 2225 void setIMEPopupLocation(Point location) { 2226 static if(UsingSimpledisplayX11) { 2227 imePopupLocation = location; 2228 updateIMEPopupLocation(); 2229 } else { 2230 // this is non-fatal at this point... but still wanna find it when i search for NotYetImplementedException at least 2231 // throw new NotYetImplementedException(); 2232 } 2233 } 2234 2235 /// ditto 2236 void setIMEPopupLocation(int x, int y) { 2237 return setIMEPopupLocation(Point(x, y)); 2238 } 2239 2240 // we need to remind XIM of where we wanted to place the IME whenever the window moves 2241 // so this function gets called in setIMEPopupLocation as well as whenever the window 2242 // receives a ConfigureNotify event 2243 private void updateIMEPopupLocation() { 2244 static if(UsingSimpledisplayX11) { 2245 if (xic is null) { 2246 return; 2247 } 2248 2249 XPoint nspot; 2250 nspot.x = cast(short) imePopupLocation.x; 2251 nspot.y = cast(short) imePopupLocation.y; 2252 XVaNestedList preeditAttr = XVaCreateNestedList(0, /*XNSpotLocation*/"spotLocation".ptr, &nspot, null); 2253 XSetICValues(xic, /*XNPreeditAttributes*/"preeditAttributes".ptr, preeditAttr, null); 2254 XFree(preeditAttr); 2255 } 2256 } 2257 2258 private bool imeFocused = true; 2259 2260 /++ 2261 Tells the IME whether or not an input field is currently focused in the window. 2262 2263 Bugs: 2264 Not implemented outside X11. 2265 +/ 2266 void setIMEFocused(bool value) { 2267 imeFocused = value; 2268 updateIMEFocused(); 2269 } 2270 2271 // used to focus/unfocus the IC if necessary when the window gains/loses focus 2272 private void updateIMEFocused() { 2273 static if(UsingSimpledisplayX11) { 2274 if (xic is null) { 2275 return; 2276 } 2277 2278 if (focused && imeFocused) { 2279 XSetICFocus(xic); 2280 } else { 2281 XUnsetICFocus(xic); 2282 } 2283 } 2284 } 2285 2286 /++ 2287 Returns the native window. 2288 2289 History: 2290 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2291 to access it through the `impl` member (which is semi-supported 2292 but platform specific and here it is simple enough to offer an accessor). 2293 2294 Bugs: 2295 Not implemented outside Windows or X11. 2296 +/ 2297 NativeWindowHandle nativeWindowHandle() { 2298 version(X11) 2299 return impl.window; 2300 else version(Windows) 2301 return impl.hwnd; 2302 else 2303 throw new NotYetImplementedException(); 2304 } 2305 2306 private bool isTransient() { 2307 with(WindowTypes) 2308 final switch(windowType) { 2309 case normal, undecorated, eventOnly: 2310 case nestedChild, minimallyWrapped: 2311 return (customizationFlags & WindowFlags.transient) ? true : false; 2312 case dropdownMenu, popupMenu, notification: 2313 return true; 2314 } 2315 } 2316 2317 private SimpleWindow inputProxy; 2318 2319 /++ 2320 Releases the grab acquired by [grabInput]. 2321 +/ 2322 void releaseInputGrab() { 2323 static if(UsingSimpledisplayX11) { 2324 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2325 if(_parent) 2326 _parent.inputProxy = null; 2327 } else version(Windows) { 2328 ReleaseCapture(); 2329 ClipCursor(null); 2330 } else version(OSXCocoa) { 2331 throw new NotYetImplementedException(); 2332 } else static assert(0); 2333 } 2334 2335 /++ 2336 Sets the input focus to this window. 2337 2338 You shouldn't call this very often - please let the user control the input focus. 2339 +/ 2340 void focus() { 2341 static if(UsingSimpledisplayX11) { 2342 SimpleWindow setTo; 2343 if(setRequestedInputFocus !is null) 2344 setTo = setRequestedInputFocus(); 2345 if(setTo is null) 2346 setTo = this; 2347 XSetInputFocus(XDisplayConnection.get, setTo.impl.window, RevertToParent, CurrentTime); 2348 } else version(Windows) { 2349 SetFocus(this.impl.hwnd); 2350 } else version(OSXCocoa) { 2351 throw new NotYetImplementedException(); 2352 } else static assert(0); 2353 } 2354 2355 /++ 2356 Requests attention from the user for this window. 2357 2358 2359 The typical result of this function is to change the color 2360 of the taskbar icon, though it may be tweaked on specific 2361 platforms. 2362 2363 It is meant to unobtrusively tell the user that something 2364 relevant to them happened in the background and they should 2365 check the window when they get a chance. Upon receiving the 2366 keyboard focus, the window will automatically return to its 2367 natural state. 2368 2369 If the window already has the keyboard focus, this function 2370 may do nothing, because the user is presumed to already be 2371 giving the window attention. 2372 2373 Implementation_note: 2374 2375 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2376 atom on X11 and the FlashWindow function on Windows. 2377 +/ 2378 void requestAttention() { 2379 if(_focused) 2380 return; 2381 2382 version(Windows) { 2383 FLASHWINFO info; 2384 info.cbSize = info.sizeof; 2385 info.hwnd = impl.hwnd; 2386 info.dwFlags = FLASHW_TRAY; 2387 info.uCount = 1; 2388 2389 FlashWindowEx(&info); 2390 2391 } else version(X11) { 2392 demandingAttention = true; 2393 demandAttention(this, true); 2394 } else version(OSXCocoa) { 2395 throw new NotYetImplementedException(); 2396 } else static assert(0); 2397 } 2398 2399 private bool _focused; 2400 2401 version(X11) private bool demandingAttention; 2402 2403 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2404 /// You'll have to call `close()` manually if you set this delegate. 2405 void delegate () closeQuery; 2406 2407 /// This will be called when window visibility was changed. 2408 void delegate (bool becomesVisible) visibilityChanged; 2409 2410 /// This will be called when window becomes visible for the first time. 2411 /// You can do OpenGL initialization here. Note that in X11 you can't call 2412 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2413 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2414 /// So you need to wait until this is called and call setAsCurrentOpenGlContext in there, then do the OpenGL initialization. 2415 private bool _visibleForTheFirstTimeCalled; 2416 void delegate () visibleForTheFirstTime; 2417 2418 /// Returns true if the window has been closed. 2419 final @property bool closed() { return _closed; } 2420 2421 private final @property bool notClosed() { return !_closed; } 2422 2423 /// Returns true if the window is focused. 2424 final @property bool focused() { return _focused; } 2425 2426 private bool _visible; 2427 /// Returns true if the window is visible (mapped). 2428 final @property bool visible() { return _visible; } 2429 2430 /// Closes the window. If there are no more open windows, the event loop will terminate. 2431 void close() { 2432 if (!_closed) { 2433 runInGuiThread( { 2434 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2435 if (onClosing !is null) onClosing(); 2436 impl.closeWindow(); 2437 _closed = true; 2438 } ); 2439 } 2440 } 2441 2442 /++ 2443 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2444 2445 History: 2446 Overload added on March 7, 2021. 2447 +/ 2448 void close() shared { 2449 (cast() this).close(); 2450 } 2451 2452 /++ 2453 2454 +/ 2455 void maximize() { 2456 version(Windows) 2457 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2458 else version(X11) { 2459 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2460 2461 // also note _NET_WM_STATE_FULLSCREEN 2462 } 2463 2464 } 2465 2466 private bool _fullscreen; 2467 version(Windows) 2468 private WINDOWPLACEMENT g_wpPrev; 2469 2470 /// not fully implemented but planned for a future release 2471 void fullscreen(bool yes) { 2472 version(Windows) { 2473 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2474 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2475 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2476 MONITORINFO mi; 2477 mi.cbSize = MONITORINFO.sizeof; 2478 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2479 GetMonitorInfo(MonitorFromWindow(hwnd, 2480 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2481 SetWindowLong(hwnd, GWL_STYLE, 2482 dwStyle & ~WS_OVERLAPPEDWINDOW); 2483 SetWindowPos(hwnd, HWND_TOP, 2484 mi.rcMonitor.left, mi.rcMonitor.top, 2485 mi.rcMonitor.right - mi.rcMonitor.left, 2486 mi.rcMonitor.bottom - mi.rcMonitor.top, 2487 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2488 } 2489 } else { 2490 SetWindowLong(hwnd, GWL_STYLE, 2491 dwStyle | WS_OVERLAPPEDWINDOW); 2492 SetWindowPlacement(hwnd, &g_wpPrev); 2493 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2494 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2495 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2496 } 2497 2498 } else version(X11) { 2499 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2500 } 2501 2502 _fullscreen = yes; 2503 2504 } 2505 2506 bool fullscreen() { 2507 return _fullscreen; 2508 } 2509 2510 /++ 2511 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2512 2513 +/ 2514 void minimize() { 2515 version(Windows) 2516 ShowWindow(impl.hwnd, SW_MINIMIZE); 2517 //else version(X11) 2518 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2519 } 2520 2521 /// Alias for `hidden = false` 2522 void show() { 2523 hidden = false; 2524 } 2525 2526 /// Alias for `hidden = true` 2527 void hide() { 2528 hidden = true; 2529 } 2530 2531 /// Hide cursor when it enters the window. 2532 void hideCursor() { 2533 version(OSXCocoa) throw new NotYetImplementedException(); else 2534 if (!_closed) impl.hideCursor(); 2535 } 2536 2537 /// Don't hide cursor when it enters the window. 2538 void showCursor() { 2539 version(OSXCocoa) throw new NotYetImplementedException(); else 2540 if (!_closed) impl.showCursor(); 2541 } 2542 2543 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2544 * 2545 * Please remember that the cursor is a shared resource that should usually be left to the user's 2546 * control. Try to think for other approaches before using this function. 2547 * 2548 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2549 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2550 * receive "mouse moved here" event. 2551 */ 2552 bool warpMouse (int x, int y) { 2553 version(X11) { 2554 if (!_closed) { impl.warpMouse(x, y); return true; } 2555 } else version(Windows) { 2556 if (!_closed) { 2557 POINT point; 2558 point.x = x; 2559 point.y = y; 2560 if(ClientToScreen(impl.hwnd, &point)) { 2561 SetCursorPos(point.x, point.y); 2562 return true; 2563 } 2564 } 2565 } 2566 return false; 2567 } 2568 2569 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2570 void sendDummyEvent () { 2571 version(X11) { 2572 if (!_closed) { impl.sendDummyEvent(); } 2573 } 2574 } 2575 2576 /// Set window minimal size. 2577 void setMinSize (int minwidth, int minheight) { 2578 version(OSXCocoa) throw new NotYetImplementedException(); else 2579 if (!_closed) impl.setMinSize(minwidth, minheight); 2580 } 2581 2582 /// Set window maximal size. 2583 void setMaxSize (int maxwidth, int maxheight) { 2584 version(OSXCocoa) throw new NotYetImplementedException(); else 2585 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2586 } 2587 2588 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2589 /// Currently only supported on X11. 2590 void setResizeGranularity (int granx, int grany) { 2591 version(OSXCocoa) throw new NotYetImplementedException(); else 2592 if (!_closed) impl.setResizeGranularity(granx, grany); 2593 } 2594 2595 /// Move window. 2596 void move(int x, int y) { 2597 version(OSXCocoa) throw new NotYetImplementedException(); else 2598 if (!_closed) impl.move(x, y); 2599 } 2600 2601 /// ditto 2602 void move(Point p) { 2603 version(OSXCocoa) throw new NotYetImplementedException(); else 2604 if (!_closed) impl.move(p.x, p.y); 2605 } 2606 2607 /++ 2608 Resize window. 2609 2610 Note that the width and height of the window are NOT instantly 2611 updated - it waits for the window manager to approve the resize 2612 request, which means you must return to the event loop before the 2613 width and height are actually changed. 2614 +/ 2615 void resize(int w, int h) { 2616 if(!_closed && _fullscreen) fullscreen = false; 2617 version(OSXCocoa) throw new NotYetImplementedException(); else 2618 if (!_closed) impl.resize(w, h); 2619 } 2620 2621 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2622 void moveResize (int x, int y, int w, int h) { 2623 if(!_closed && _fullscreen) fullscreen = false; 2624 version(OSXCocoa) throw new NotYetImplementedException(); else 2625 if (!_closed) impl.moveResize(x, y, w, h); 2626 } 2627 2628 private bool _hidden; 2629 2630 /// Returns true if the window is hidden. 2631 final @property bool hidden() { 2632 return _hidden; 2633 } 2634 2635 /// Shows or hides the window based on the bool argument. 2636 final @property void hidden(bool b) { 2637 _hidden = b; 2638 version(Windows) { 2639 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2640 } else version(X11) { 2641 if(b) 2642 //XUnmapWindow(impl.display, impl.window); 2643 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2644 else 2645 XMapWindow(impl.display, impl.window); 2646 } else version(OSXCocoa) { 2647 throw new NotYetImplementedException(); 2648 } else static assert(0); 2649 } 2650 2651 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2652 void opacity(double opacity) @property 2653 in { 2654 assert(opacity >= 0 && opacity <= 1); 2655 } do { 2656 version (Windows) { 2657 impl.setOpacity(cast(ubyte)(255 * opacity)); 2658 } else version (X11) { 2659 impl.setOpacity(cast(uint)(uint.max * opacity)); 2660 } else throw new NotYetImplementedException(); 2661 } 2662 2663 /++ 2664 Sets your event handlers, without entering the event loop. Useful if you 2665 have multiple windows - set the handlers on each window, then only do 2666 [eventLoop] on your main window or call `EventLoop.get.run();`. 2667 2668 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2669 [handlePulse], and [handleMouseEvent] automatically based on the provide 2670 delegate signatures. 2671 +/ 2672 void setEventHandlers(T...)(T eventHandlers) { 2673 // FIXME: add more events 2674 foreach(handler; eventHandlers) { 2675 static if(__traits(compiles, handleKeyEvent = handler)) { 2676 handleKeyEvent = handler; 2677 } else static if(__traits(compiles, handleCharEvent = handler)) { 2678 handleCharEvent = handler; 2679 } else static if(__traits(compiles, handlePulse = handler)) { 2680 handlePulse = handler; 2681 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2682 handleMouseEvent = handler; 2683 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2684 } 2685 } 2686 2687 /++ 2688 The event loop automatically returns when the window is closed 2689 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2690 pulse timer is created. The event loop will block until an event 2691 arrives or the pulse timer goes off. 2692 2693 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2694 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2695 [handleMouseEvent], based on the signature of delegates you provide. 2696 2697 Give one with no parameters to set a timer pulse handler. Give one that 2698 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2699 and one that takes `dchar` for a char event handler. You can use as many 2700 or as few handlers as you need for your application. 2701 2702 Bugs: 2703 2704 $(PITFALL 2705 You should always have one event loop live for your application. 2706 If you make two windows in sequence, the second call to eventLoop 2707 might fail: 2708 2709 --- 2710 // don't do this! 2711 auto window = new SimpleWindow(); 2712 window.eventLoop(0); 2713 2714 auto window2 = new SimpleWindow(); 2715 window2.eventLoop(0); // problematic! might crash 2716 --- 2717 2718 simpledisplay's current implementation assumes that final cleanup is 2719 done when the event loop refcount reaches zero. So after the first 2720 eventLoop returns, when there isn't already another one active, it assumes 2721 the program will exit soon and cleans up. 2722 2723 This is arguably a bug that it doesn't reinitialize, and I'll probably change 2724 it eventually, but in the mean time, there's an easy solution: 2725 2726 --- 2727 // do this 2728 EventLoop mainEventLoop = EventLoop.get; // just add this line 2729 2730 auto window = new SimpleWindow(); 2731 window.eventLoop(0); 2732 2733 auto window2 = new SimpleWindow(); 2734 window2.eventLoop(0); // perfectly fine since mainEventLoop still alive 2735 --- 2736 2737 By adding a top-level reference to the event loop, it ensures the final cleanup 2738 is not performed until it goes out of scope too, letting the individual window loops 2739 work without trouble despite the bug. 2740 ) 2741 2742 History: 2743 The overload without `pulseTimeout` was added on December 8, 2021. 2744 2745 On December 9, 2021, the default blocking mode (which is now configurable 2746 because [eventLoopWithBlockingMode] was added) switched from 2747 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2748 should almost never be noticeable to you since the typical simpledisplay 2749 paradigm has been (and I still recommend) to have one `eventLoop` call. 2750 2751 See_Also: 2752 [eventLoopWithBlockingMode] 2753 +/ 2754 final int eventLoop(T...)( 2755 long pulseTimeout, /// set to zero if you don't want a pulse. 2756 T eventHandlers) /// delegate list like std.concurrency.receive 2757 { 2758 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2759 } 2760 2761 /// ditto 2762 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2763 { 2764 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 2765 } 2766 2767 /++ 2768 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 2769 2770 History: 2771 Added December 8, 2021 (dub v10.5) 2772 2773 Previously, this implementation was right inside [eventLoop], but when I wanted 2774 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 2775 just renamed it instead of adding as an overload. Besides, the new name makes it 2776 easier to remember the order and avoids ambiguity between two int-like params anyway. 2777 2778 See_Also: 2779 [SimpleWindow.eventLoop], [EventLoop] 2780 2781 Bugs: 2782 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 2783 +/ 2784 final int eventLoopWithBlockingMode(T...)( 2785 BlockingMode blockingMode, /// when you want this function to block until 2786 long pulseTimeout, /// set to zero if you don't want a pulse. 2787 T eventHandlers) /// delegate list like std.concurrency.receive 2788 { 2789 setEventHandlers(eventHandlers); 2790 2791 version(with_eventloop) { 2792 // delegates event loop to my other module 2793 version(X11) 2794 XFlush(display); 2795 2796 import arsd.eventloop; 2797 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2798 scope(exit) clearInterval(handle); 2799 2800 loop(); 2801 return 0; 2802 } else version(OSXCocoa) { 2803 // FIXME 2804 if (handlePulse !is null && pulseTimeout != 0) { 2805 timer = scheduledTimer(pulseTimeout*1e-3, 2806 view, sel_registerName("simpledisplay_pulse"), 2807 null, true); 2808 } 2809 2810 setNeedsDisplay(view, true); 2811 run(NSApp); 2812 return 0; 2813 } else { 2814 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2815 2816 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 2817 return 0; 2818 2819 return el.run( 2820 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 2821 null : 2822 &this.notClosed 2823 ); 2824 } 2825 } 2826 2827 /++ 2828 This lets you draw on the window (or its backing buffer) using basic 2829 2D primitives. 2830 2831 Be sure to call this in a limited scope because your changes will not 2832 actually appear on the window until ScreenPainter's destructor runs. 2833 2834 Returns: an instance of [ScreenPainter], which has the drawing methods 2835 on it to draw on this window. 2836 2837 Params: 2838 manualInvalidations = if you set this to true, you will need to 2839 set the invalid rectangle on the painter yourself. If false, it 2840 assumes the whole window has been redrawn each time you draw. 2841 2842 Only invalidated rectangles are blitted back to the window when 2843 the destructor runs. Doing this yourself can reduce flickering 2844 of child windows. 2845 2846 History: 2847 The `manualInvalidations` parameter overload was added on 2848 December 30, 2021 (dub v10.5) 2849 +/ 2850 ScreenPainter draw() { 2851 return draw(false); 2852 } 2853 /// ditto 2854 ScreenPainter draw(bool manualInvalidations) { 2855 return impl.getPainter(manualInvalidations); 2856 } 2857 2858 // This is here to implement the interface we use for various native handlers. 2859 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2860 2861 // maps native window handles to SimpleWindow instances, if there are any 2862 // you shouldn't need this, but it is public in case you do in a native event handler or something 2863 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2864 2865 // the size the user requested in the constructor, in automatic scale modes it always pretends to be this size 2866 private int _virtualWidth; 2867 private int _virtualHeight; 2868 2869 /// Width of the window's drawable client area, in pixels. 2870 @scriptable 2871 final @property int width() const pure nothrow @safe @nogc { 2872 if(resizability == Resizability.automaticallyScaleIfPossible) 2873 return _virtualWidth; 2874 else 2875 return _width; 2876 } 2877 2878 /// Height of the window's drawable client area, in pixels. 2879 @scriptable 2880 final @property int height() const pure nothrow @safe @nogc { 2881 if(resizability == Resizability.automaticallyScaleIfPossible) 2882 return _virtualHeight; 2883 else 2884 return _height; 2885 } 2886 2887 /++ 2888 Returns the actual size of the window, bypassing the logical 2889 illusions of [Resizability.automaticallyScaleIfPossible]. 2890 2891 History: 2892 Added November 11, 2022 (dub v10.10) 2893 +/ 2894 final @property Size actualWindowSize() const pure nothrow @safe @nogc { 2895 return Size(_width, _height); 2896 } 2897 2898 2899 private int _width; 2900 private int _height; 2901 2902 // HACK: making the best of some copy constructor woes with refcounting 2903 private ScreenPainterImplementation* activeScreenPainter_; 2904 2905 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2906 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2907 2908 private OpenGlOptions openglMode; 2909 private Resizability resizability; 2910 private WindowTypes windowType; 2911 private int customizationFlags; 2912 2913 /// `true` if OpenGL was initialized for this window. 2914 @property bool isOpenGL () const pure nothrow @safe @nogc { 2915 version(without_opengl) 2916 return false; 2917 else 2918 return (openglMode == OpenGlOptions.yes); 2919 } 2920 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2921 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2922 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2923 2924 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2925 /// to call this, as it's not recommended to share window between threads. 2926 void mtLock () { 2927 version(X11) { 2928 XLockDisplay(this.display); 2929 } 2930 } 2931 2932 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2933 /// to call this, as it's not recommended to share window between threads. 2934 void mtUnlock () { 2935 version(X11) { 2936 XUnlockDisplay(this.display); 2937 } 2938 } 2939 2940 /// Emit a beep to get user's attention. 2941 void beep () { 2942 version(X11) { 2943 XBell(this.display, 100); 2944 } else version(Windows) { 2945 MessageBeep(0xFFFFFFFF); 2946 } 2947 } 2948 2949 2950 2951 version(without_opengl) {} else { 2952 2953 /// 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`. 2954 void delegate() redrawOpenGlScene; 2955 2956 /// This will allow you to change OpenGL vsync state. 2957 final @property void vsync (bool wait) { 2958 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2959 version(X11) { 2960 setAsCurrentOpenGlContext(); 2961 glxSetVSync(display, impl.window, wait); 2962 } else version(Windows) { 2963 setAsCurrentOpenGlContext(); 2964 wglSetVSync(wait); 2965 } 2966 } 2967 2968 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 2969 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 2970 /// enough without waiting 'em to finish their frame business. 2971 bool useGLFinish = true; 2972 2973 // FIXME: it should schedule it for the end of the current iteration of the event loop... 2974 /// 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. 2975 void redrawOpenGlSceneNow() { 2976 version(X11) if (!this._visible) return; // no need to do this if window is invisible 2977 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2978 if(redrawOpenGlScene is null) 2979 return; 2980 2981 this.mtLock(); 2982 scope(exit) this.mtUnlock(); 2983 2984 this.setAsCurrentOpenGlContext(); 2985 2986 redrawOpenGlScene(); 2987 2988 this.swapOpenGlBuffers(); 2989 // 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. 2990 if (useGLFinish) glFinish(); 2991 } 2992 2993 private bool redrawOpenGlSceneSoonSet = false; 2994 private static class RedrawOpenGlSceneEvent { 2995 SimpleWindow w; 2996 this(SimpleWindow w) { this.w = w; } 2997 } 2998 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 2999 /++ 3000 Queues an opengl redraw as soon as the other pending events are cleared. 3001 +/ 3002 void redrawOpenGlSceneSoon() { 3003 if(!redrawOpenGlSceneSoonSet) { 3004 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 3005 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 3006 redrawOpenGlSceneSoonSet = true; 3007 } 3008 this.postEvent(redrawOpenGlSceneEvent, true); 3009 } 3010 3011 3012 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3013 void setAsCurrentOpenGlContext() { 3014 assert(openglMode == OpenGlOptions.yes); 3015 version(X11) { 3016 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 3017 throw new Exception("glXMakeCurrent"); 3018 } else version(Windows) { 3019 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3020 if (!wglMakeCurrent(ghDC, ghRC)) 3021 throw new Exception("wglMakeCurrent " ~ toInternal!int(GetLastError())); // let windows users suffer too 3022 } 3023 } 3024 3025 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 3026 /// This doesn't throw, returning success flag instead. 3027 bool setAsCurrentOpenGlContextNT() nothrow { 3028 assert(openglMode == OpenGlOptions.yes); 3029 version(X11) { 3030 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 3031 } else version(Windows) { 3032 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3033 return wglMakeCurrent(ghDC, ghRC) ? true : false; 3034 } 3035 } 3036 3037 /// 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. 3038 /// This doesn't throw, returning success flag instead. 3039 bool releaseCurrentOpenGlContext() nothrow { 3040 assert(openglMode == OpenGlOptions.yes); 3041 version(X11) { 3042 return (glXMakeCurrent(display, 0, null) != 0); 3043 } else version(Windows) { 3044 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 3045 return wglMakeCurrent(ghDC, null) ? true : false; 3046 } 3047 } 3048 3049 /++ 3050 simpledisplay always uses double buffering, usually automatically. This 3051 manually swaps the OpenGL buffers. 3052 3053 3054 You should not need to call this yourself because simpledisplay will do it 3055 for you after calling your `redrawOpenGlScene`. 3056 3057 Remember that this may throw an exception, which you can catch in a multithreaded 3058 application to keep your thread from dying from an unhandled exception. 3059 +/ 3060 void swapOpenGlBuffers() { 3061 assert(openglMode == OpenGlOptions.yes); 3062 version(X11) { 3063 if (!this._visible) return; // no need to do this if window is invisible 3064 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 3065 glXSwapBuffers(display, impl.window); 3066 } else version(Windows) { 3067 SwapBuffers(ghDC); 3068 } 3069 } 3070 } 3071 3072 /++ 3073 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 3074 3075 3076 --- 3077 auto window = new SimpleWindow(100, 100, "First title"); 3078 window.title = "A new title"; 3079 --- 3080 3081 You may call this function at any time. 3082 +/ 3083 @property void title(string title) { 3084 _title = title; 3085 version(OSXCocoa) throw new NotYetImplementedException(); else 3086 impl.setTitle(title); 3087 } 3088 3089 private string _title; 3090 3091 /// Gets the title 3092 @property string title() { 3093 if(_title is null) 3094 _title = getRealTitle(); 3095 return _title; 3096 } 3097 3098 /++ 3099 Get the title as set by the window manager. 3100 May not match what you attempted to set. 3101 +/ 3102 string getRealTitle() { 3103 static if(is(typeof(impl.getTitle()))) 3104 return impl.getTitle(); 3105 else 3106 return null; 3107 } 3108 3109 // don't use this generally it is not yet really released 3110 version(X11) 3111 @property Image secret_icon() { 3112 return secret_icon_inner; 3113 } 3114 private Image secret_icon_inner; 3115 3116 3117 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 3118 @property void icon(MemoryImage icon) { 3119 if(icon is null) 3120 return; 3121 auto tci = icon.getAsTrueColorImage(); 3122 version(Windows) { 3123 winIcon = new WindowsIcon(icon); 3124 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 3125 } else version(X11) { 3126 secret_icon_inner = Image.fromMemoryImage(icon); 3127 // FIXME: ensure this is correct 3128 auto display = XDisplayConnection.get; 3129 arch_ulong[] buffer; 3130 buffer ~= icon.width; 3131 buffer ~= icon.height; 3132 foreach(c; tci.imageData.colors) { 3133 arch_ulong b; 3134 b |= c.a << 24; 3135 b |= c.r << 16; 3136 b |= c.g << 8; 3137 b |= c.b; 3138 buffer ~= b; 3139 } 3140 3141 XChangeProperty( 3142 display, 3143 impl.window, 3144 GetAtom!("_NET_WM_ICON", true)(display), 3145 GetAtom!"CARDINAL"(display), 3146 32 /* bits */, 3147 0 /*PropModeReplace*/, 3148 buffer.ptr, 3149 cast(int) buffer.length); 3150 } else version(OSXCocoa) { 3151 throw new NotYetImplementedException(); 3152 } else static assert(0); 3153 } 3154 3155 version(Windows) 3156 private WindowsIcon winIcon; 3157 3158 bool _suppressDestruction; 3159 3160 ~this() { 3161 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 3162 if(_suppressDestruction) 3163 return; 3164 impl.dispose(); 3165 } 3166 3167 private bool _closed; 3168 3169 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 3170 /* 3171 ScreenPainter drawTransiently() { 3172 return impl.getPainter(); 3173 } 3174 */ 3175 3176 /// Draws an image on the window. This is meant to provide quick look 3177 /// of a static image generated elsewhere. 3178 @property void image(Image i) { 3179 /+ 3180 version(Windows) { 3181 BITMAP bm; 3182 HDC hdc = GetDC(hwnd); 3183 HDC hdcMem = CreateCompatibleDC(hdc); 3184 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3185 3186 GetObject(i.handle, bm.sizeof, &bm); 3187 3188 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3189 3190 SelectObject(hdcMem, hbmOld); 3191 DeleteDC(hdcMem); 3192 ReleaseDC(hwnd, hdc); 3193 3194 /* 3195 RECT r; 3196 r.right = i.width; 3197 r.bottom = i.height; 3198 InvalidateRect(hwnd, &r, false); 3199 */ 3200 } else 3201 version(X11) { 3202 if(!destroyed) { 3203 if(i.usingXshm) 3204 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3205 else 3206 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3207 } 3208 } else 3209 version(OSXCocoa) { 3210 draw().drawImage(Point(0, 0), i); 3211 setNeedsDisplay(view, true); 3212 } else static assert(0); 3213 +/ 3214 auto painter = this.draw; 3215 painter.drawImage(Point(0, 0), i); 3216 } 3217 3218 /++ 3219 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3220 3221 --- 3222 window.cursor = GenericCursor.Help; 3223 // now the window mouse cursor is set to a generic help 3224 --- 3225 3226 +/ 3227 @property void cursor(MouseCursor cursor) { 3228 version(OSXCocoa) 3229 featureNotImplemented(); 3230 else 3231 if(this.impl.curHidden <= 0) { 3232 static if(UsingSimpledisplayX11) { 3233 auto ch = cursor.cursorHandle; 3234 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3235 } else version(Windows) { 3236 auto ch = cursor.cursorHandle; 3237 impl.currentCursor = ch; 3238 SetCursor(ch); // redraw without waiting for mouse movement to update 3239 } else featureNotImplemented(); 3240 } 3241 3242 } 3243 3244 /// What follows are the event handlers. These are set automatically 3245 /// by the eventLoop function, but are still public so you can change 3246 /// them later. wasPressed == true means key down. false == key up. 3247 3248 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3249 void delegate(KeyEvent ke) handleKeyEvent; 3250 3251 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3252 void delegate(dchar c) handleCharEvent; 3253 3254 /// Handles a timer pulse. Settable through setEventHandlers. 3255 void delegate() handlePulse; 3256 3257 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3258 void delegate(bool) onFocusChange; 3259 3260 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3261 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3262 void delegate() onClosing; 3263 3264 /** Called when we received destroy notification. At this stage we cannot do much with our window 3265 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3266 * last minute cleanup. */ 3267 void delegate() onDestroyed; 3268 3269 static if (UsingSimpledisplayX11) 3270 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3271 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3272 * You will probably never need to setup this handler, it is for very low-level stuff. 3273 * 3274 * WARNING! Xlib is multithread-locked when this handles is called! */ 3275 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3276 3277 //version(Windows) 3278 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3279 3280 private { 3281 int lastMouseX = int.min; 3282 int lastMouseY = int.min; 3283 void mdx(ref MouseEvent ev) { 3284 if(lastMouseX == int.min || lastMouseY == int.min) { 3285 ev.dx = 0; 3286 ev.dy = 0; 3287 } else { 3288 ev.dx = ev.x - lastMouseX; 3289 ev.dy = ev.y - lastMouseY; 3290 } 3291 3292 lastMouseX = ev.x; 3293 lastMouseY = ev.y; 3294 } 3295 } 3296 3297 /// Mouse event handler. Settable through setEventHandlers. 3298 void delegate(MouseEvent) handleMouseEvent; 3299 3300 /// use to redraw child widgets if you use system apis to add stuff 3301 void delegate() paintingFinished; 3302 3303 void delegate() paintingFinishedDg() { 3304 return paintingFinished; 3305 } 3306 3307 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3308 /// for this to ever happen. 3309 void delegate(int width, int height) windowResized; 3310 3311 /++ 3312 Platform specific - handle any native message this window gets. 3313 3314 Note: this is called *in addition to* other event handlers, unless you either: 3315 3316 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3317 3318 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. 3319 3320 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3321 3322 On X, it takes the form of `int delegate(XEvent)`. 3323 3324 History: 3325 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3326 3327 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. 3328 +/ 3329 NativeEventHandler handleNativeEvent_; 3330 3331 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3332 return handleNativeEvent_; 3333 } 3334 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3335 handleNativeEvent_ = neh; 3336 } 3337 3338 version(Windows) 3339 // compatibility shim with the old deprecated way 3340 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3341 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) { 3342 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3343 auto ret = dg(h, m, w, l); 3344 if(ret == 0) 3345 r = 1; 3346 return ret; 3347 }; 3348 } 3349 3350 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3351 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3352 /// this instead and it will work the same way. 3353 __gshared NativeEventHandler handleNativeGlobalEvent; 3354 3355 // private: 3356 /// The native implementation is available, but you shouldn't use it unless you are 3357 /// familiar with the underlying operating system, don't mind depending on it, and 3358 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3359 /// do what you need to do with handleNativeEvent instead. 3360 /// 3361 /// This is likely to eventually change to be just a struct holding platform-specific 3362 /// handles instead of a template mixin at some point because I'm not happy with the 3363 /// code duplication here (ironically). 3364 mixin NativeSimpleWindowImplementation!() impl; 3365 3366 /** 3367 This is in-process one-way (from anything to window) event sending mechanics. 3368 It is thread-safe, so it can be used in multi-threaded applications to send, 3369 for example, "wake up and repaint" events when thread completed some operation. 3370 This will allow to avoid using timer pulse to check events with synchronization, 3371 'cause event handler will be called in UI thread. You can stop guessing which 3372 pulse frequency will be enough for your app. 3373 Note that events handlers may be called in arbitrary order, i.e. last registered 3374 handler can be called first, and vice versa. 3375 */ 3376 public: 3377 /** Is our custom event queue empty? Can be used in simple cases to prevent 3378 * "spamming" window with events it can't cope with. 3379 * It is safe to call this from non-UI threads. 3380 */ 3381 @property bool eventQueueEmpty() () { 3382 synchronized(this) { 3383 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3384 } 3385 return true; 3386 } 3387 3388 /** Does our custom event queue contains at least one with the given type? 3389 * Can be used in simple cases to prevent "spamming" window with events 3390 * it can't cope with. 3391 * It is safe to call this from non-UI threads. 3392 */ 3393 @property bool eventQueued(ET:Object) () { 3394 synchronized(this) { 3395 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3396 if (!o.doProcess) { 3397 if (cast(ET)(o.evt)) return true; 3398 } 3399 } 3400 } 3401 return false; 3402 } 3403 3404 /++ 3405 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3406 3407 History: 3408 Added May 12, 2021 3409 +/ 3410 void delegate(Exception e) nothrow eventUncaughtException; 3411 3412 /** Add listener for custom event. Can be used like this: 3413 * 3414 * --------------------- 3415 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3416 * ... 3417 * win.removeEventListener(eid); 3418 * --------------------- 3419 * 3420 * Returns: 0 on failure (should never happen, so ignore it) 3421 * 3422 * $(WARNING Don't use this method in object destructors!) 3423 * 3424 * $(WARNING It is better to register all event handlers and don't remove 'em, 3425 * 'cause if event handler id counter will overflow, you won't be able 3426 * to register any more events.) 3427 */ 3428 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3429 if (dg is null) return 0; // ignore empty handlers 3430 synchronized(this) { 3431 //FIXME: abort on overflow? 3432 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3433 EventHandlerEntry e; 3434 e.dg = delegate (Object o) { 3435 if (auto co = cast(ET)o) { 3436 try { 3437 dg(co); 3438 } catch (Exception e) { 3439 // sorry! 3440 if(eventUncaughtException) 3441 eventUncaughtException(e); 3442 } 3443 return true; 3444 } 3445 return false; 3446 }; 3447 e.id = lastUsedHandlerId; 3448 auto optr = eventHandlers.ptr; 3449 eventHandlers ~= e; 3450 if (eventHandlers.ptr !is optr) { 3451 import core.memory : GC; 3452 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3453 } 3454 return lastUsedHandlerId; 3455 } 3456 } 3457 3458 /// Remove event listener. It is safe to pass invalid event id here. 3459 /// $(WARNING Don't use this method in object destructors!) 3460 void removeEventListener() (uint id) { 3461 if (id == 0 || id > lastUsedHandlerId) return; 3462 synchronized(this) { 3463 foreach (immutable idx; 0..eventHandlers.length) { 3464 if (eventHandlers[idx].id == id) { 3465 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3466 eventHandlers[$-1].dg = null; 3467 eventHandlers.length -= 1; 3468 eventHandlers.assumeSafeAppend; 3469 return; 3470 } 3471 } 3472 } 3473 } 3474 3475 /// Post event to queue. It is safe to call this from non-UI threads. 3476 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3477 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3478 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3479 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3480 if (this.closed) return false; // closed windows can't handle events 3481 3482 // remove all events of type `ET` 3483 void removeAllET () { 3484 uint eidx = 0, ec = eventQueueUsed; 3485 auto eptr = eventQueue.ptr; 3486 while (eidx < ec) { 3487 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3488 if (cast(ET)eptr.evt !is null) { 3489 // i found her! 3490 if (inCustomEventProcessor) { 3491 // if we're in custom event processing loop, processor will clear it for us 3492 eptr.evt = null; 3493 ++eidx; 3494 ++eptr; 3495 } else { 3496 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3497 ec = --eventQueueUsed; 3498 // clear last event (it is already copied) 3499 eventQueue.ptr[ec].evt = null; 3500 } 3501 } else { 3502 ++eidx; 3503 ++eptr; 3504 } 3505 } 3506 } 3507 3508 if (evt is null) { 3509 if (replace) { synchronized(this) removeAllET(); } 3510 // ignore empty events, they can't be handled anyway 3511 return false; 3512 } 3513 3514 // add events even if no event FD/event object created yet 3515 synchronized(this) { 3516 if (replace) removeAllET(); 3517 if (eventQueueUsed == uint.max) return false; // just in case 3518 if (eventQueueUsed < eventQueue.length) { 3519 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3520 } else { 3521 if (eventQueue.capacity == eventQueue.length) { 3522 // need to reallocate; do a trick to ensure that old array is cleared 3523 auto oarr = eventQueue; 3524 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3525 // just in case, do yet another check 3526 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3527 import core.memory : GC; 3528 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3529 } else { 3530 auto optr = eventQueue.ptr; 3531 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3532 assert(eventQueue.ptr is optr); 3533 } 3534 ++eventQueueUsed; 3535 assert(eventQueueUsed == eventQueue.length); 3536 } 3537 if (!eventWakeUp()) { 3538 // can't wake up event processor, so there is no reason to keep the event 3539 assert(eventQueueUsed > 0); 3540 eventQueue[--eventQueueUsed].evt = null; 3541 return false; 3542 } 3543 return true; 3544 } 3545 } 3546 3547 /// Post event to queue. It is safe to call this from non-UI threads. 3548 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3549 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3550 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3551 return postTimeout!ET(evt, 0, replace); 3552 } 3553 3554 private: 3555 private import core.time : MonoTime; 3556 3557 version(Posix) { 3558 __gshared int customEventFDRead = -1; 3559 __gshared int customEventFDWrite = -1; 3560 __gshared int customSignalFD = -1; 3561 } else version(Windows) { 3562 __gshared HANDLE customEventH = null; 3563 } 3564 3565 // wake up event processor 3566 static bool eventWakeUp () { 3567 version(X11) { 3568 import core.sys.posix.unistd : write; 3569 ulong n = 1; 3570 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3571 return true; 3572 } else version(Windows) { 3573 if (customEventH !is null) SetEvent(customEventH); 3574 return true; 3575 } else { 3576 // not implemented for other OSes 3577 return false; 3578 } 3579 } 3580 3581 static struct QueuedEvent { 3582 Object evt; 3583 bool timed = false; 3584 MonoTime hittime = MonoTime.zero; 3585 bool doProcess = false; // process event at the current iteration (internal flag) 3586 3587 this (Object aevt, uint toutmsecs) { 3588 evt = aevt; 3589 if (toutmsecs > 0) { 3590 import core.time : msecs; 3591 timed = true; 3592 hittime = MonoTime.currTime+toutmsecs.msecs; 3593 } 3594 } 3595 } 3596 3597 alias CustomEventHandler = bool delegate (Object o) nothrow; 3598 static struct EventHandlerEntry { 3599 CustomEventHandler dg; 3600 uint id; 3601 } 3602 3603 uint lastUsedHandlerId; 3604 EventHandlerEntry[] eventHandlers; 3605 QueuedEvent[] eventQueue = null; 3606 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3607 bool inCustomEventProcessor = false; // required to properly remove events 3608 3609 // process queued events and call custom event handlers 3610 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3611 void processCustomEvents () { 3612 bool hasSomethingToDo = false; 3613 uint ecount; 3614 bool ocep; 3615 synchronized(this) { 3616 ocep = inCustomEventProcessor; 3617 inCustomEventProcessor = true; 3618 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3619 auto ctt = MonoTime.currTime; 3620 bool hasEmpty = false; 3621 // mark events to process (this is required for `eventQueued()`) 3622 foreach (ref qe; eventQueue[0..ecount]) { 3623 if (qe.evt is null) { hasEmpty = true; continue; } 3624 if (qe.timed) { 3625 qe.doProcess = (qe.hittime <= ctt); 3626 } else { 3627 qe.doProcess = true; 3628 } 3629 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3630 } 3631 if (!hasSomethingToDo) { 3632 // remove empty events 3633 if (hasEmpty) { 3634 uint eidx = 0, ec = eventQueueUsed; 3635 auto eptr = eventQueue.ptr; 3636 while (eidx < ec) { 3637 if (eptr.evt is null) { 3638 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3639 ec = --eventQueueUsed; 3640 eventQueue.ptr[ec].evt = null; // make GC life easier 3641 } else { 3642 ++eidx; 3643 ++eptr; 3644 } 3645 } 3646 } 3647 inCustomEventProcessor = ocep; 3648 return; 3649 } 3650 } 3651 // process marked events 3652 uint efree = 0; // non-processed events will be put at this index 3653 EventHandlerEntry[] eh; 3654 Object evt; 3655 foreach (immutable eidx; 0..ecount) { 3656 synchronized(this) { 3657 if (!eventQueue[eidx].doProcess) { 3658 // skip this event 3659 assert(efree <= eidx); 3660 if (efree != eidx) { 3661 // copy this event to queue start 3662 eventQueue[efree] = eventQueue[eidx]; 3663 eventQueue[eidx].evt = null; // just in case 3664 } 3665 ++efree; 3666 continue; 3667 } 3668 evt = eventQueue[eidx].evt; 3669 eventQueue[eidx].evt = null; // in case event handler will hit GC 3670 if (evt is null) continue; // just in case 3671 // try all handlers; this can be slow, but meh... 3672 eh = eventHandlers; 3673 } 3674 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3675 evt = null; 3676 eh = null; 3677 } 3678 synchronized(this) { 3679 // move all unprocessed events to queue top; efree holds first "free index" 3680 foreach (immutable eidx; ecount..eventQueueUsed) { 3681 assert(efree <= eidx); 3682 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3683 ++efree; 3684 } 3685 eventQueueUsed = efree; 3686 // wake up event processor on next event loop iteration if we have more queued events 3687 // also, remove empty events 3688 bool awaken = false; 3689 uint eidx = 0, ec = eventQueueUsed; 3690 auto eptr = eventQueue.ptr; 3691 while (eidx < ec) { 3692 if (eptr.evt is null) { 3693 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3694 ec = --eventQueueUsed; 3695 eventQueue.ptr[ec].evt = null; // make GC life easier 3696 } else { 3697 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3698 ++eidx; 3699 ++eptr; 3700 } 3701 } 3702 inCustomEventProcessor = ocep; 3703 } 3704 } 3705 3706 // for all windows in nativeMapping 3707 package static void processAllCustomEvents () { 3708 3709 cleanupQueue.process(); 3710 3711 justCommunication.processCustomEvents(); 3712 3713 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3714 if (sw is null || sw.closed) continue; 3715 sw.processCustomEvents(); 3716 } 3717 3718 runPendingRunInGuiThreadDelegates(); 3719 } 3720 3721 // 0: infinite (i.e. no scheduled events in queue) 3722 uint eventQueueTimeoutMSecs () { 3723 synchronized(this) { 3724 if (eventQueueUsed == 0) return 0; 3725 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3726 uint res = int.max; 3727 auto ctt = MonoTime.currTime; 3728 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3729 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3730 if (qe.doProcess) continue; // just in case 3731 if (!qe.timed) return 1; // minimal 3732 if (qe.hittime <= ctt) return 1; // minimal 3733 auto tms = (qe.hittime-ctt).total!"msecs"; 3734 if (tms < 1) tms = 1; // safety net 3735 if (tms >= int.max) tms = int.max-1; // and another safety net 3736 if (res > tms) res = cast(uint)tms; 3737 } 3738 return (res >= int.max ? 0 : res); 3739 } 3740 } 3741 3742 // for all windows in nativeMapping 3743 static uint eventAllQueueTimeoutMSecs () { 3744 uint res = uint.max; 3745 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3746 if (sw is null || sw.closed) continue; 3747 uint to = sw.eventQueueTimeoutMSecs(); 3748 if (to && to < res) { 3749 res = to; 3750 if (to == 1) break; // can't have less than this 3751 } 3752 } 3753 return (res >= int.max ? 0 : res); 3754 } 3755 3756 version(X11) { 3757 ResizeEvent pendingResizeEvent; 3758 } 3759 3760 /++ 3761 When in opengl mode and automatically resizing, it will set the opengl viewport to stretch. 3762 3763 If you work with multiple opengl contexts and/or threads, this might be more trouble than it is 3764 worth so you can disable it by setting this to `true`. 3765 3766 History: 3767 Added November 13, 2022. 3768 +/ 3769 public bool suppressAutoOpenglViewport = false; 3770 private void updateOpenglViewportIfNeeded(int width, int height) { 3771 if(suppressAutoOpenglViewport) return; 3772 3773 version(without_opengl) {} else 3774 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 3775 import std.stdio; writeln(width, " ", height); 3776 setAsCurrentOpenGlContextNT(); 3777 glViewport(0, 0, width, height); 3778 } 3779 } 3780 } 3781 3782 /++ 3783 Magic pseudo-window for just posting events to a global queue. 3784 3785 Not entirely supported, I might delete it at any time. 3786 3787 Added Nov 5, 2021. 3788 +/ 3789 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init); 3790 3791 /* Drag and drop support { */ 3792 version(X11) { 3793 3794 } else version(Windows) { 3795 import core.sys.windows.uuid; 3796 import core.sys.windows.ole2; 3797 import core.sys.windows.oleidl; 3798 import core.sys.windows.objidl; 3799 import core.sys.windows.wtypes; 3800 3801 pragma(lib, "ole32"); 3802 void initDnd() { 3803 auto err = OleInitialize(null); 3804 if(err != S_OK && err != S_FALSE) 3805 throw new Exception("init");//err); 3806 } 3807 } 3808 /* } End drag and drop support */ 3809 3810 3811 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 3812 /// See [GenericCursor]. 3813 class MouseCursor { 3814 int osId; 3815 bool isStockCursor; 3816 private this(int osId) { 3817 this.osId = osId; 3818 this.isStockCursor = true; 3819 } 3820 3821 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 3822 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 3823 3824 version(Windows) { 3825 HCURSOR cursor_; 3826 HCURSOR cursorHandle() { 3827 if(cursor_ is null) 3828 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 3829 return cursor_; 3830 } 3831 3832 } else static if(UsingSimpledisplayX11) { 3833 Cursor cursor_ = None; 3834 int xDisplaySequence; 3835 3836 Cursor cursorHandle() { 3837 if(this.osId == None) 3838 return None; 3839 3840 // we need to reload if we on a new X connection 3841 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 3842 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 3843 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 3844 } 3845 return cursor_; 3846 } 3847 } 3848 } 3849 3850 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 3851 // https://tronche.com/gui/x/xlib/appendix/b/ 3852 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 3853 /// 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. 3854 enum GenericCursorType { 3855 Default, /// The default arrow pointer. 3856 Wait, /// A cursor indicating something is loading and the user must wait. 3857 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3858 Help, /// A cursor indicating the user can get help about the pointer location. 3859 Cross, /// A crosshair. 3860 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3861 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 3862 UpArrow, /// An arrow pointing straight up. 3863 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3864 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3865 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 3866 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 3867 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 3868 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 3869 3870 } 3871 3872 /* 3873 X_plus == css cell == Windows ? 3874 */ 3875 3876 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3877 static struct GenericCursor { 3878 static: 3879 /// 3880 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3881 static MouseCursor mc; 3882 3883 auto type = __traits(getMember, GenericCursorType, str); 3884 3885 if(mc is null) { 3886 3887 version(Windows) { 3888 int osId; 3889 final switch(type) { 3890 case GenericCursorType.Default: osId = IDC_ARROW; break; 3891 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3892 case GenericCursorType.Hand: osId = IDC_HAND; break; 3893 case GenericCursorType.Help: osId = IDC_HELP; break; 3894 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3895 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3896 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3897 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3898 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3899 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3900 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3901 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3902 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3903 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3904 } 3905 } else static if(UsingSimpledisplayX11) { 3906 int osId; 3907 final switch(type) { 3908 case GenericCursorType.Default: osId = None; break; 3909 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3910 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3911 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3912 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3913 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3914 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3915 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3916 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3917 3918 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3919 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3920 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3921 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3922 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3923 } 3924 3925 } else featureNotImplemented(); 3926 3927 mc = new MouseCursor(osId); 3928 } 3929 return mc; 3930 } 3931 } 3932 3933 3934 /++ 3935 If you want to get more control over the event loop, you can use this. 3936 3937 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 3938 to `EventLoop.get.run`. 3939 +/ 3940 struct EventLoop { 3941 @disable this(); 3942 3943 /// Gets a reference to an existing event loop 3944 static EventLoop get() { 3945 return EventLoop(0, null); 3946 } 3947 3948 static void quitApplication() { 3949 EventLoop.get().exit(); 3950 } 3951 3952 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 3953 3954 /// Construct an application-global event loop for yourself 3955 /// See_Also: [SimpleWindow.setEventHandlers] 3956 this(long pulseTimeout, void delegate() handlePulse) { 3957 synchronized(monitor) { 3958 if(impl is null) { 3959 claimGuiThread(); 3960 version(sdpy_thread_checks) assert(thisIsGuiThread); 3961 impl = new EventLoopImpl(pulseTimeout, handlePulse); 3962 } else { 3963 if(pulseTimeout) { 3964 impl.pulseTimeout = pulseTimeout; 3965 impl.handlePulse = handlePulse; 3966 } 3967 } 3968 impl.refcount++; 3969 } 3970 } 3971 3972 ~this() { 3973 if(impl is null) 3974 return; 3975 impl.refcount--; 3976 if(impl.refcount == 0) { 3977 impl.dispose(); 3978 if(thisIsGuiThread) 3979 guiThreadFinalize(); 3980 } 3981 3982 } 3983 3984 this(this) { 3985 if(impl is null) 3986 return; 3987 impl.refcount++; 3988 } 3989 3990 /// Runs the event loop until the whileCondition, if present, returns false 3991 int run(bool delegate() whileCondition = null) { 3992 assert(impl !is null); 3993 impl.notExited = true; 3994 return impl.run(whileCondition); 3995 } 3996 3997 /// Exits the event loop 3998 void exit() { 3999 assert(impl !is null); 4000 impl.notExited = false; 4001 } 4002 4003 version(linux) 4004 ref void delegate(int) signalHandler() { 4005 assert(impl !is null); 4006 return impl.signalHandler; 4007 } 4008 4009 __gshared static EventLoopImpl* impl; 4010 } 4011 4012 version(linux) 4013 void delegate(int, int) globalHupHandler; 4014 4015 version(Posix) 4016 void makeNonBlocking(int fd) { 4017 import fcntl = core.sys.posix.fcntl; 4018 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 4019 if(flags == -1) 4020 throw new Exception("fcntl get"); 4021 flags |= fcntl.O_NONBLOCK; 4022 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 4023 if(s == -1) 4024 throw new Exception("fcntl set"); 4025 } 4026 4027 struct EventLoopImpl { 4028 int refcount; 4029 4030 bool notExited = true; 4031 4032 version(linux) { 4033 static import ep = core.sys.linux.epoll; 4034 static import unix = core.sys.posix.unistd; 4035 static import err = core.stdc.errno; 4036 import core.sys.linux.timerfd; 4037 4038 void delegate(int) signalHandler; 4039 } 4040 4041 version(X11) { 4042 int pulseFd = -1; 4043 version(linux) ep.epoll_event[16] events = void; 4044 } else version(Windows) { 4045 Timer pulser; 4046 HANDLE[] handles; 4047 } 4048 4049 4050 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 4051 /// to call this, as it's not recommended to share window between threads. 4052 void mtLock () { 4053 version(X11) { 4054 XLockDisplay(this.display); 4055 } 4056 } 4057 4058 version(X11) 4059 auto display() { return XDisplayConnection.get; } 4060 4061 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 4062 /// to call this, as it's not recommended to share window between threads. 4063 void mtUnlock () { 4064 version(X11) { 4065 XUnlockDisplay(this.display); 4066 } 4067 } 4068 4069 version(with_eventloop) 4070 void initialize(long pulseTimeout) {} 4071 else 4072 void initialize(long pulseTimeout) { 4073 version(Windows) { 4074 if(pulseTimeout && handlePulse !is null) 4075 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 4076 4077 if (customEventH is null) { 4078 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 4079 if (customEventH !is null) { 4080 handles ~= customEventH; 4081 } else { 4082 // this is something that should not be; better be safe than sorry 4083 throw new Exception("can't create eventfd for custom event processing"); 4084 } 4085 } 4086 4087 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 4088 } 4089 4090 version(linux) { 4091 prepareEventLoop(); 4092 { 4093 auto display = XDisplayConnection.get; 4094 // adding Xlib file 4095 ep.epoll_event ev = void; 4096 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4097 ev.events = ep.EPOLLIN; 4098 ev.data.fd = display.fd; 4099 //import std.conv; 4100 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 4101 throw new Exception("add x fd");// ~ to!string(epollFd)); 4102 displayFd = display.fd; 4103 } 4104 4105 if(pulseTimeout && handlePulse !is null) { 4106 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 4107 if(pulseFd == -1) 4108 throw new Exception("pulse timer create failed"); 4109 4110 itimerspec value; 4111 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 4112 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4113 4114 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 4115 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 4116 4117 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 4118 throw new Exception("couldn't make pulse timer"); 4119 4120 ep.epoll_event ev = void; 4121 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4122 ev.events = ep.EPOLLIN; 4123 ev.data.fd = pulseFd; 4124 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 4125 } 4126 4127 // eventfd for custom events 4128 if (customEventFDWrite == -1) { 4129 customEventFDWrite = eventfd(0, 0); 4130 customEventFDRead = customEventFDWrite; 4131 if (customEventFDRead >= 0) { 4132 ep.epoll_event ev = void; 4133 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4134 ev.events = ep.EPOLLIN; 4135 ev.data.fd = customEventFDRead; 4136 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 4137 } else { 4138 // this is something that should not be; better be safe than sorry 4139 throw new Exception("can't create eventfd for custom event processing"); 4140 } 4141 } 4142 4143 if (customSignalFD == -1) { 4144 import core.sys.linux.sys.signalfd; 4145 4146 sigset_t sigset; 4147 auto err = sigemptyset(&sigset); 4148 assert(!err); 4149 err = sigaddset(&sigset, SIGINT); 4150 assert(!err); 4151 err = sigaddset(&sigset, SIGHUP); 4152 assert(!err); 4153 err = sigprocmask(SIG_BLOCK, &sigset, null); 4154 assert(!err); 4155 4156 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 4157 assert(customSignalFD != -1); 4158 4159 ep.epoll_event ev = void; 4160 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4161 ev.events = ep.EPOLLIN; 4162 ev.data.fd = customSignalFD; 4163 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 4164 } 4165 } else version(Posix) { 4166 prepareEventLoop(); 4167 if (customEventFDRead == -1) { 4168 int[2] bfr; 4169 import core.sys.posix.unistd; 4170 auto ret = pipe(bfr); 4171 if(ret == -1) throw new Exception("pipe"); 4172 customEventFDRead = bfr[0]; 4173 customEventFDWrite = bfr[1]; 4174 } 4175 4176 } 4177 4178 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 4179 4180 version(linux) { 4181 this.mtLock(); 4182 scope(exit) this.mtUnlock(); 4183 XPending(display); // no, really 4184 } 4185 4186 disposed = false; 4187 } 4188 4189 bool disposed = true; 4190 version(X11) 4191 int displayFd = -1; 4192 4193 version(with_eventloop) 4194 void dispose() {} 4195 else 4196 void dispose() { 4197 disposed = true; 4198 version(X11) { 4199 if(pulseFd != -1) { 4200 import unix = core.sys.posix.unistd; 4201 unix.close(pulseFd); 4202 pulseFd = -1; 4203 } 4204 4205 version(linux) 4206 if(displayFd != -1) { 4207 // 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 4208 ep.epoll_event ev = void; 4209 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4210 ev.events = ep.EPOLLIN; 4211 ev.data.fd = displayFd; 4212 //import std.conv; 4213 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4214 displayFd = -1; 4215 } 4216 4217 } else version(Windows) { 4218 if(pulser !is null) { 4219 pulser.destroy(); 4220 pulser = null; 4221 } 4222 if (customEventH !is null) { 4223 CloseHandle(customEventH); 4224 customEventH = null; 4225 } 4226 } 4227 } 4228 4229 this(long pulseTimeout, void delegate() handlePulse) { 4230 this.pulseTimeout = pulseTimeout; 4231 this.handlePulse = handlePulse; 4232 initialize(pulseTimeout); 4233 } 4234 4235 private long pulseTimeout; 4236 void delegate() handlePulse; 4237 4238 ~this() { 4239 dispose(); 4240 } 4241 4242 version(Posix) 4243 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4244 version(Posix) 4245 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4246 version(linux) 4247 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4248 version(Windows) 4249 ref auto customEventH() { return SimpleWindow.customEventH; } 4250 4251 version(with_eventloop) { 4252 int loopHelper(bool delegate() whileCondition) { 4253 // FIXME: whileCondition 4254 import arsd.eventloop; 4255 loop(); 4256 return 0; 4257 } 4258 } else 4259 int loopHelper(bool delegate() whileCondition) { 4260 version(X11) { 4261 bool done = false; 4262 4263 XFlush(display); 4264 insideXEventLoop = true; 4265 scope(exit) insideXEventLoop = false; 4266 4267 version(linux) { 4268 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4269 bool forceXPending = false; 4270 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4271 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4272 { 4273 this.mtLock(); 4274 scope(exit) this.mtUnlock(); 4275 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 4276 } 4277 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4278 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4279 if(nfds == -1) { 4280 if(err.errno == err.EINTR) { 4281 //if(forceXPending) goto xpending; 4282 continue; // interrupted by signal, just try again 4283 } 4284 throw new Exception("epoll wait failure"); 4285 } 4286 4287 SimpleWindow.processAllCustomEvents(); // anyway 4288 //version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4289 foreach(idx; 0 .. nfds) { 4290 if(done) break; 4291 auto fd = events[idx].data.fd; 4292 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4293 auto flags = events[idx].events; 4294 if(flags & ep.EPOLLIN) { 4295 if (fd == customSignalFD) { 4296 version(linux) { 4297 import core.sys.linux.sys.signalfd; 4298 import core.sys.posix.unistd : read; 4299 signalfd_siginfo info; 4300 read(customSignalFD, &info, info.sizeof); 4301 4302 auto sig = info.ssi_signo; 4303 4304 if(EventLoop.get.signalHandler !is null) { 4305 EventLoop.get.signalHandler()(sig); 4306 } else { 4307 EventLoop.get.exit(); 4308 } 4309 } 4310 } else if(fd == display.fd) { 4311 version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); } 4312 this.mtLock(); 4313 scope(exit) this.mtUnlock(); 4314 while(!done && XPending(display)) { 4315 done = doXNextEvent(this.display); 4316 } 4317 forceXPending = false; 4318 } else if(fd == pulseFd) { 4319 long expirationCount; 4320 // 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... 4321 4322 handlePulse(); 4323 4324 // read just to clear the buffer so poll doesn't trigger again 4325 // BTW I read AFTER the pulse because if the pulse handler takes 4326 // a lot of time to execute, we don't want the app to get stuck 4327 // in a loop of timer hits without a chance to do anything else 4328 // 4329 // IOW handlePulse happens at most once per pulse interval. 4330 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4331 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 4332 } else if (fd == customEventFDRead) { 4333 // we have some custom events; process 'em 4334 import core.sys.posix.unistd : read; 4335 ulong n; 4336 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4337 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4338 //SimpleWindow.processAllCustomEvents(); 4339 } else { 4340 // some other timer 4341 version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); } 4342 4343 if(Timer* t = fd in Timer.mapping) 4344 (*t).trigger(); 4345 4346 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4347 (*pfr).ready(flags); 4348 4349 // or i might add support for other FDs too 4350 // but for now it is just timer 4351 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4352 } 4353 } 4354 if(flags & ep.EPOLLHUP) { 4355 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4356 (*pfr).hup(flags); 4357 if(globalHupHandler) 4358 globalHupHandler(fd, flags); 4359 } 4360 /+ 4361 } else { 4362 // not interested in OUT, we are just reading here. 4363 // 4364 // error or hup might also be reported 4365 // but it shouldn't here since we are only 4366 // using a few types of FD and Xlib will report 4367 // if it dies. 4368 // so instead of thoughtfully handling it, I'll 4369 // just throw. for now at least 4370 4371 throw new Exception("epoll did something else"); 4372 } 4373 +/ 4374 } 4375 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4376 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4377 xpending: 4378 if (!done && forceXPending) { 4379 this.mtLock(); 4380 scope(exit) this.mtUnlock(); 4381 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4382 while(!done && XPending(display)) { 4383 done = doXNextEvent(this.display); 4384 } 4385 } 4386 } 4387 } else { 4388 // Generic fallback: yes to simple pulse support, 4389 // but NO timer support! 4390 4391 // FIXME: we could probably support the POSIX timer_create 4392 // signal-based option, but I'm in no rush to write it since 4393 // I prefer the fd-based functions. 4394 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4395 4396 import core.sys.posix.poll; 4397 4398 pollfd[] pfds; 4399 pollfd[32] pfdsBuffer; 4400 auto len = PosixFdReader.mapping.length + 2; 4401 // FIXME: i should just reuse the buffer 4402 if(len < pfdsBuffer.length) 4403 pfds = pfdsBuffer[0 .. len]; 4404 else 4405 pfds = new pollfd[](len); 4406 4407 pfds[0].fd = display.fd; 4408 pfds[0].events = POLLIN; 4409 pfds[0].revents = 0; 4410 4411 int slot = 1; 4412 4413 if(customEventFDRead != -1) { 4414 pfds[slot].fd = customEventFDRead; 4415 pfds[slot].events = POLLIN; 4416 pfds[slot].revents = 0; 4417 4418 slot++; 4419 } 4420 4421 foreach(fd, obj; PosixFdReader.mapping) { 4422 if(!obj.enabled) continue; 4423 pfds[slot].fd = fd; 4424 pfds[slot].events = POLLIN; 4425 pfds[slot].revents = 0; 4426 4427 slot++; 4428 } 4429 4430 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4431 if(ret == -1) throw new Exception("poll"); 4432 4433 if(ret == 0) { 4434 // FIXME it may not necessarily time out if events keep coming 4435 if(handlePulse !is null) 4436 handlePulse(); 4437 } else { 4438 foreach(s; 0 .. slot) { 4439 if(pfds[s].revents == 0) continue; 4440 4441 if(pfds[s].fd == display.fd) { 4442 while(!done && XPending(display)) { 4443 this.mtLock(); 4444 scope(exit) this.mtUnlock(); 4445 done = doXNextEvent(this.display); 4446 } 4447 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4448 4449 import core.sys.posix.unistd : read; 4450 ulong n; 4451 read(customEventFDRead, &n, n.sizeof); 4452 SimpleWindow.processAllCustomEvents(); 4453 } else { 4454 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4455 if(pfds[s].revents & POLLNVAL) { 4456 obj.dispose(); 4457 } else { 4458 obj.ready(pfds[s].revents); 4459 } 4460 } 4461 4462 ret--; 4463 if(ret == 0) break; 4464 } 4465 } 4466 } 4467 } 4468 } 4469 4470 version(Windows) { 4471 int ret = -1; 4472 MSG message; 4473 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4474 eventLoopRound++; 4475 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4476 auto waitResult = MsgWaitForMultipleObjectsEx( 4477 cast(int) handles.length, handles.ptr, 4478 (wto == 0 ? INFINITE : wto), /* timeout */ 4479 0x04FF, /* QS_ALLINPUT */ 4480 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4481 4482 SimpleWindow.processAllCustomEvents(); // anyway 4483 enum WAIT_OBJECT_0 = 0; 4484 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4485 auto h = handles[waitResult - WAIT_OBJECT_0]; 4486 if(auto e = h in WindowsHandleReader.mapping) { 4487 (*e).ready(); 4488 } 4489 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4490 // message ready 4491 int count; 4492 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 4493 ret = GetMessage(&message, null, 0, 0); 4494 if(ret == -1) 4495 throw new WindowsApiException("GetMessage", GetLastError()); 4496 TranslateMessage(&message); 4497 DispatchMessage(&message); 4498 4499 count++; 4500 if(count > 10) 4501 break; // take the opportunity to catch up on other events 4502 4503 if(ret == 0) { // WM_QUIT 4504 EventLoop.quitApplication(); 4505 break; 4506 } 4507 } 4508 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4509 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4510 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4511 // timeout, should never happen since we aren't using it 4512 } else if(waitResult == 0xFFFFFFFF) { 4513 // failed 4514 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 4515 } else { 4516 // idk.... 4517 } 4518 } 4519 4520 // return message.wParam; 4521 return 0; 4522 } else { 4523 return 0; 4524 } 4525 } 4526 4527 int run(bool delegate() whileCondition = null) { 4528 if(disposed) 4529 initialize(this.pulseTimeout); 4530 4531 version(X11) { 4532 try { 4533 return loopHelper(whileCondition); 4534 } catch(XDisconnectException e) { 4535 if(e.userRequested) { 4536 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4537 item.discardConnectionState(); 4538 XCloseDisplay(XDisplayConnection.display); 4539 } 4540 4541 XDisplayConnection.display = null; 4542 4543 this.dispose(); 4544 4545 throw e; 4546 } 4547 } else { 4548 return loopHelper(whileCondition); 4549 } 4550 } 4551 } 4552 4553 4554 /++ 4555 Provides an icon on the system notification area (also known as the system tray). 4556 4557 4558 If a notification area is not available with the NotificationIcon object is created, 4559 it will silently succeed and simply attempt to create one when an area becomes available. 4560 4561 4562 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. 4563 If this is wrong, pass -version=WindowsXP to dmd when compiling and it will 4564 use the older version. 4565 +/ 4566 version(OSXCocoa) {} else // NotYetImplementedException 4567 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4568 4569 version(X11) { 4570 void recreateAfterDisconnect() { 4571 stateDiscarded = false; 4572 clippixmap = None; 4573 throw new Exception("NOT IMPLEMENTED"); 4574 } 4575 4576 bool stateDiscarded; 4577 void discardConnectionState() { 4578 stateDiscarded = true; 4579 } 4580 } 4581 4582 4583 version(X11) { 4584 Image img; 4585 4586 NativeEventHandler getNativeEventHandler() { 4587 return delegate int(XEvent e) { 4588 switch(e.type) { 4589 case EventType.Expose: 4590 //case EventType.VisibilityNotify: 4591 redraw(); 4592 break; 4593 case EventType.ClientMessage: 4594 version(sddddd) { 4595 import std.stdio; 4596 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4597 writeln("\t", e.xclient.format); 4598 writeln("\t", e.xclient.data.l); 4599 } 4600 break; 4601 case EventType.ButtonPress: 4602 auto event = e.xbutton; 4603 if (onClick !is null || onClickEx !is null) { 4604 MouseButton mb = cast(MouseButton)0; 4605 switch (event.button) { 4606 case 1: mb = MouseButton.left; break; // left 4607 case 2: mb = MouseButton.middle; break; // middle 4608 case 3: mb = MouseButton.right; break; // right 4609 case 4: mb = MouseButton.wheelUp; break; // scroll up 4610 case 5: mb = MouseButton.wheelDown; break; // scroll down 4611 case 6: break; // scroll left... 4612 case 7: break; // scroll right... 4613 case 8: mb = MouseButton.backButton; break; 4614 case 9: mb = MouseButton.forwardButton; break; 4615 default: 4616 } 4617 if (mb) { 4618 try { onClick()(mb); } catch (Exception) {} 4619 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 4620 } 4621 } 4622 break; 4623 case EventType.EnterNotify: 4624 if (onEnter !is null) { 4625 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 4626 } 4627 break; 4628 case EventType.LeaveNotify: 4629 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 4630 break; 4631 case EventType.DestroyNotify: 4632 active = false; 4633 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 4634 break; 4635 case EventType.ConfigureNotify: 4636 auto event = e.xconfigure; 4637 this.width = event.width; 4638 this.height = event.height; 4639 //import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y); 4640 redraw(); 4641 break; 4642 default: return 1; 4643 } 4644 return 1; 4645 }; 4646 } 4647 4648 /* private */ void hideBalloon() { 4649 balloon.close(); 4650 version(with_timer) 4651 timer.destroy(); 4652 balloon = null; 4653 version(with_timer) 4654 timer = null; 4655 } 4656 4657 void redraw() { 4658 if (!active) return; 4659 4660 auto display = XDisplayConnection.get; 4661 auto gc = DefaultGC(display, DefaultScreen(display)); 4662 XClearWindow(display, nativeHandle); 4663 4664 XSetClipMask(display, gc, clippixmap); 4665 4666 XSetForeground(display, gc, 4667 cast(uint) 0 << 16 | 4668 cast(uint) 0 << 8 | 4669 cast(uint) 0); 4670 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 4671 4672 if (img is null) { 4673 XSetForeground(display, gc, 4674 cast(uint) 0 << 16 | 4675 cast(uint) 127 << 8 | 4676 cast(uint) 0); 4677 XFillArc(display, nativeHandle, 4678 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 4679 } else { 4680 int dx = 0; 4681 int dy = 0; 4682 if(width > img.width) 4683 dx = (width - img.width) / 2; 4684 if(height > img.height) 4685 dy = (height - img.height) / 2; 4686 XSetClipOrigin(display, gc, dx, dy); 4687 4688 if (img.usingXshm) 4689 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false); 4690 else 4691 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height); 4692 } 4693 XSetClipMask(display, gc, None); 4694 flushGui(); 4695 } 4696 4697 static Window getTrayOwner() { 4698 auto display = XDisplayConnection.get; 4699 auto i = cast(int) DefaultScreen(display); 4700 if(i < 10 && i >= 0) { 4701 static Atom atom; 4702 if(atom == None) 4703 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 4704 return XGetSelectionOwner(display, atom); 4705 } 4706 return None; 4707 } 4708 4709 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 4710 auto to = getTrayOwner(); 4711 auto display = XDisplayConnection.get; 4712 XEvent ev; 4713 ev.xclient.type = EventType.ClientMessage; 4714 ev.xclient.window = to; 4715 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 4716 ev.xclient.format = 32; 4717 ev.xclient.data.l[0] = CurrentTime; 4718 ev.xclient.data.l[1] = message; 4719 ev.xclient.data.l[2] = d1; 4720 ev.xclient.data.l[3] = d2; 4721 ev.xclient.data.l[4] = d3; 4722 4723 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 4724 } 4725 4726 private static NotificationAreaIcon[] activeIcons; 4727 4728 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 4729 private void newManager() { 4730 close(); 4731 createXWin(); 4732 4733 if(this.clippixmap) 4734 XFreePixmap(XDisplayConnection.get, clippixmap); 4735 if(this.originalMemoryImage) 4736 this.icon = this.originalMemoryImage; 4737 else if(this.img) 4738 this.icon = this.img; 4739 } 4740 4741 private void createXWin () { 4742 // create window 4743 auto display = XDisplayConnection.get; 4744 4745 // to check for MANAGER on root window to catch new/changed tray owners 4746 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 4747 // so if a thing does appear, we can handle it 4748 foreach(ai; activeIcons) 4749 if(ai is this) 4750 goto alreadythere; 4751 activeIcons ~= this; 4752 alreadythere: 4753 4754 // and check for an existing tray 4755 auto trayOwner = getTrayOwner(); 4756 if(trayOwner == None) 4757 return; 4758 //throw new Exception("No notification area found"); 4759 4760 Visual* v = cast(Visual*) CopyFromParent; 4761 /+ 4762 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 4763 if(visualProp !is null) { 4764 c_ulong[] info = cast(c_ulong[]) visualProp; 4765 if(info.length == 1) { 4766 auto vid = info[0]; 4767 int returned; 4768 XVisualInfo t; 4769 t.visualid = vid; 4770 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 4771 if(got !is null) { 4772 if(returned == 1) { 4773 v = got.visual; 4774 import std.stdio; 4775 writeln("using special visual ", *got); 4776 } 4777 XFree(got); 4778 } 4779 } 4780 } 4781 +/ 4782 4783 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null); 4784 assert(nativeWindow); 4785 4786 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4787 4788 nativeHandle = nativeWindow; 4789 4790 ///+ 4791 arch_ulong[2] info; 4792 info[0] = 0; 4793 info[1] = 1; 4794 4795 string title = this.name is null ? "simpledisplay.d program" : this.name; 4796 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4797 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4798 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4799 4800 XChangeProperty( 4801 display, 4802 nativeWindow, 4803 GetAtom!("_XEMBED_INFO", true)(display), 4804 GetAtom!("_XEMBED_INFO", true)(display), 4805 32 /* bits */, 4806 0 /*PropModeReplace*/, 4807 info.ptr, 4808 2); 4809 4810 import core.sys.posix.unistd; 4811 arch_ulong pid = getpid(); 4812 4813 XChangeProperty( 4814 display, 4815 nativeWindow, 4816 GetAtom!("_NET_WM_PID", true)(display), 4817 XA_CARDINAL, 4818 32 /* bits */, 4819 0 /*PropModeReplace*/, 4820 &pid, 4821 1); 4822 4823 updateNetWmIcon(); 4824 4825 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4826 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4827 XClassHint klass; 4828 XWMHints wh; 4829 XSizeHints size; 4830 klass.res_name = sdpyWindowClassStr; 4831 klass.res_class = sdpyWindowClassStr; 4832 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4833 } 4834 4835 // believe it or not, THIS is what xfce needed for the 9999 issue 4836 XSizeHints sh; 4837 c_long spr; 4838 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4839 sh.flags |= PMaxSize | PMinSize; 4840 // FIXME maybe nicer resizing 4841 sh.min_width = 16; 4842 sh.min_height = 16; 4843 sh.max_width = 16; 4844 sh.max_height = 16; 4845 XSetWMNormalHints(display, nativeWindow, &sh); 4846 4847 4848 //+/ 4849 4850 4851 XSelectInput(display, nativeWindow, 4852 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 4853 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 4854 4855 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 4856 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 4857 active = true; 4858 } 4859 4860 void updateNetWmIcon() { 4861 if(img is null) return; 4862 auto display = XDisplayConnection.get; 4863 // FIXME: ensure this is correct 4864 arch_ulong[] buffer; 4865 auto imgMi = img.toTrueColorImage; 4866 buffer ~= imgMi.width; 4867 buffer ~= imgMi.height; 4868 foreach(c; imgMi.imageData.colors) { 4869 arch_ulong b; 4870 b |= c.a << 24; 4871 b |= c.r << 16; 4872 b |= c.g << 8; 4873 b |= c.b; 4874 buffer ~= b; 4875 } 4876 4877 XChangeProperty( 4878 display, 4879 nativeHandle, 4880 GetAtom!"_NET_WM_ICON"(display), 4881 GetAtom!"CARDINAL"(display), 4882 32 /* bits */, 4883 0 /*PropModeReplace*/, 4884 buffer.ptr, 4885 cast(int) buffer.length); 4886 } 4887 4888 4889 4890 private SimpleWindow balloon; 4891 version(with_timer) 4892 private Timer timer; 4893 4894 private Window nativeHandle; 4895 private Pixmap clippixmap = None; 4896 private int width = 16; 4897 private int height = 16; 4898 private bool active = false; 4899 4900 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 4901 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 4902 void delegate () onLeave; /// X11 only. 4903 4904 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 4905 4906 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 4907 void getWindowRect (out int x, out int y, out int width, out int height) { 4908 if (!active) { width = 1; height = 1; return; } // 1: just in case 4909 Window dummyw; 4910 auto dpy = XDisplayConnection.get; 4911 //XWindowAttributes xwa; 4912 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 4913 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 4914 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 4915 width = this.width; 4916 height = this.height; 4917 } 4918 } 4919 4920 /+ 4921 What I actually want from this: 4922 4923 * set / change: icon, tooltip 4924 * handle: mouse click, right click 4925 * show: notification bubble. 4926 +/ 4927 4928 version(Windows) { 4929 WindowsIcon win32Icon; 4930 HWND hwnd; 4931 4932 NOTIFYICONDATAW data; 4933 4934 NativeEventHandler getNativeEventHandler() { 4935 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 4936 if(msg == WM_USER) { 4937 auto event = LOWORD(lParam); 4938 auto iconId = HIWORD(lParam); 4939 //auto x = GET_X_LPARAM(wParam); 4940 //auto y = GET_Y_LPARAM(wParam); 4941 switch(event) { 4942 case WM_LBUTTONDOWN: 4943 onClick()(MouseButton.left); 4944 break; 4945 case WM_RBUTTONDOWN: 4946 onClick()(MouseButton.right); 4947 break; 4948 case WM_MBUTTONDOWN: 4949 onClick()(MouseButton.middle); 4950 break; 4951 case WM_MOUSEMOVE: 4952 // sent, we could use it. 4953 break; 4954 case WM_MOUSEWHEEL: 4955 // NOT SENT 4956 break; 4957 //case NIN_KEYSELECT: 4958 //case NIN_SELECT: 4959 //break; 4960 default: {} 4961 } 4962 } 4963 return 0; 4964 }; 4965 } 4966 4967 enum NIF_SHOWTIP = 0x00000080; 4968 4969 private static struct NOTIFYICONDATAW { 4970 DWORD cbSize; 4971 HWND hWnd; 4972 UINT uID; 4973 UINT uFlags; 4974 UINT uCallbackMessage; 4975 HICON hIcon; 4976 WCHAR[128] szTip; 4977 DWORD dwState; 4978 DWORD dwStateMask; 4979 WCHAR[256] szInfo; 4980 union { 4981 UINT uTimeout; 4982 UINT uVersion; 4983 } 4984 WCHAR[64] szInfoTitle; 4985 DWORD dwInfoFlags; 4986 GUID guidItem; 4987 HICON hBalloonIcon; 4988 } 4989 4990 } 4991 4992 /++ 4993 Note that on Windows, only left, right, and middle buttons are sent. 4994 Mouse wheel buttons are NOT set, so don't rely on those events if your 4995 program is meant to be used on Windows too. 4996 +/ 4997 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 4998 // The canonical constructor for Windows needs the MemoryImage, so it is here, 4999 // but on X, we need an Image, so its canonical ctor is there. They should 5000 // forward to each other though. 5001 version(X11) { 5002 this.name = name; 5003 this.onClick = onClick; 5004 createXWin(); 5005 this.icon = icon; 5006 } else version(Windows) { 5007 this.onClick = onClick; 5008 this.win32Icon = new WindowsIcon(icon); 5009 5010 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 5011 5012 static bool registered = false; 5013 if(!registered) { 5014 WNDCLASSEX wc; 5015 wc.cbSize = wc.sizeof; 5016 wc.hInstance = hInstance; 5017 wc.lpfnWndProc = &WndProc; 5018 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 5019 if(!RegisterClassExW(&wc)) 5020 throw new WindowsApiException("RegisterClass", GetLastError()); 5021 registered = true; 5022 } 5023 5024 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 5025 if(hwnd is null) 5026 throw new WindowsApiException("CreateWindow", GetLastError()); 5027 5028 data.cbSize = data.sizeof; 5029 data.hWnd = hwnd; 5030 data.uID = cast(uint) cast(void*) this; 5031 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 5032 // NIF_INFO means show balloon 5033 data.uCallbackMessage = WM_USER; 5034 data.hIcon = this.win32Icon.hIcon; 5035 data.szTip = ""; // FIXME 5036 data.dwState = 0; // NIS_HIDDEN; // windows vista 5037 data.dwStateMask = NIS_HIDDEN; // windows vista 5038 5039 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 5040 5041 5042 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 5043 5044 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 5045 } else version(OSXCocoa) { 5046 throw new NotYetImplementedException(); 5047 } else static assert(0); 5048 } 5049 5050 /// ditto 5051 this(string name, Image icon, void delegate(MouseButton button) onClick) { 5052 version(X11) { 5053 this.onClick = onClick; 5054 this.name = name; 5055 createXWin(); 5056 this.icon = icon; 5057 } else version(Windows) { 5058 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 5059 } else version(OSXCocoa) { 5060 throw new NotYetImplementedException(); 5061 } else static assert(0); 5062 } 5063 5064 version(X11) { 5065 /++ 5066 X-specific extension (for now at least) 5067 +/ 5068 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5069 this.onClickEx = onClickEx; 5070 createXWin(); 5071 if (icon !is null) this.icon = icon; 5072 } 5073 5074 /// ditto 5075 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 5076 this.onClickEx = onClickEx; 5077 createXWin(); 5078 this.icon = icon; 5079 } 5080 } 5081 5082 private void delegate (MouseButton button) onClick_; 5083 5084 /// 5085 @property final void delegate(MouseButton) onClick() { 5086 if(onClick_ is null) 5087 onClick_ = delegate void(MouseButton) {}; 5088 return onClick_; 5089 } 5090 5091 /// ditto 5092 @property final void onClick(void delegate(MouseButton) handler) { 5093 // I made this a property setter so we can wrap smaller arg 5094 // delegates and just forward all to onClickEx or something. 5095 onClick_ = handler; 5096 } 5097 5098 5099 string name_; 5100 @property void name(string n) { 5101 name_ = n; 5102 } 5103 5104 @property string name() { 5105 return name_; 5106 } 5107 5108 private MemoryImage originalMemoryImage; 5109 5110 /// 5111 @property void icon(MemoryImage i) { 5112 version(X11) { 5113 this.originalMemoryImage = i; 5114 if (!active) return; 5115 if (i !is null) { 5116 this.img = Image.fromMemoryImage(i); 5117 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 5118 //import std.stdio; writeln("using pixmap ", clippixmap); 5119 updateNetWmIcon(); 5120 redraw(); 5121 } else { 5122 if (this.img !is null) { 5123 this.img = null; 5124 redraw(); 5125 } 5126 } 5127 } else version(Windows) { 5128 this.win32Icon = new WindowsIcon(i); 5129 5130 data.uFlags = NIF_ICON; 5131 data.hIcon = this.win32Icon.hIcon; 5132 5133 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5134 } else version(OSXCocoa) { 5135 throw new NotYetImplementedException(); 5136 } else static assert(0); 5137 } 5138 5139 /// ditto 5140 @property void icon (Image i) { 5141 version(X11) { 5142 if (!active) return; 5143 if (i !is img) { 5144 originalMemoryImage = null; 5145 img = i; 5146 redraw(); 5147 } 5148 } else version(Windows) { 5149 this.icon(i is null ? null : i.toTrueColorImage()); 5150 } else version(OSXCocoa) { 5151 throw new NotYetImplementedException(); 5152 } else static assert(0); 5153 } 5154 5155 /++ 5156 Shows a balloon notification. You can only show one balloon at a time, if you call 5157 it twice while one is already up, the first balloon will be replaced. 5158 5159 5160 The user is free to block notifications and they will automatically disappear after 5161 a timeout period. 5162 5163 Params: 5164 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 5165 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 5166 icon = the icon to display with the notification. If null, it uses your existing icon. 5167 onclick = delegate called if the user clicks the balloon. (not yet implemented) 5168 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 5169 +/ 5170 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 5171 bool useCustom = true; 5172 version(libnotify) { 5173 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 5174 try { 5175 if(!active) return; 5176 5177 if(libnotify is null) { 5178 libnotify = new C_DynamicLibrary("libnotify.so"); 5179 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 5180 } 5181 5182 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 5183 5184 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 5185 5186 if(onclick) { 5187 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 5188 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); 5189 libnotify_action_delegates_count++; 5190 } 5191 5192 // FIXME icon 5193 5194 // set hint image-data 5195 // set default action for onclick 5196 5197 void* error; 5198 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5199 5200 useCustom = false; 5201 } catch(Exception e) { 5202 5203 } 5204 } 5205 5206 version(X11) { 5207 if(useCustom) { 5208 if(!active) return; 5209 if(balloon) { 5210 hideBalloon(); 5211 } 5212 // I know there are two specs for this, but one is never 5213 // implemented by any window manager I have ever seen, and 5214 // the other is a bloated mess and too complicated for simpledisplay... 5215 // so doing my own little window instead. 5216 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5217 5218 int x, y, width, height; 5219 getWindowRect(x, y, width, height); 5220 5221 int bx = x - balloon.width; 5222 int by = y - balloon.height; 5223 if(bx < 0) 5224 bx = x + width + balloon.width; 5225 if(by < 0) 5226 by = y + height; 5227 5228 // just in case, make sure it is actually on scren 5229 if(bx < 0) 5230 bx = 0; 5231 if(by < 0) 5232 by = 0; 5233 5234 balloon.move(bx, by); 5235 auto painter = balloon.draw(); 5236 painter.fillColor = Color(220, 220, 220); 5237 painter.outlineColor = Color.black; 5238 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5239 auto iconWidth = icon is null ? 0 : icon.width; 5240 if(icon) 5241 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5242 iconWidth += 6; // margin around the icon 5243 5244 // draw a close button 5245 painter.outlineColor = Color(44, 44, 44); 5246 painter.fillColor = Color(255, 255, 255); 5247 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5248 painter.pen = Pen(Color.black, 3); 5249 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5250 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5251 painter.pen = Pen(Color.black, 1); 5252 painter.fillColor = Color(220, 220, 220); 5253 5254 // Draw the title and message 5255 painter.drawText(Point(4 + iconWidth, 4), title); 5256 painter.drawLine( 5257 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5258 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5259 ); 5260 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5261 5262 balloon.setEventHandlers( 5263 (MouseEvent ev) { 5264 if(ev.type == MouseEventType.buttonPressed) { 5265 if(ev.x > balloon.width - 16 && ev.y < 16) 5266 hideBalloon(); 5267 else if(onclick) 5268 onclick(); 5269 } 5270 } 5271 ); 5272 balloon.show(); 5273 5274 version(with_timer) 5275 timer = new Timer(timeout, &hideBalloon); 5276 else {} // FIXME 5277 } 5278 } else version(Windows) { 5279 enum NIF_INFO = 0x00000010; 5280 5281 data.uFlags = NIF_INFO; 5282 5283 // FIXME: go back to the last valid unicode code point 5284 if(title.length > 40) 5285 title = title[0 .. 40]; 5286 if(message.length > 220) 5287 message = message[0 .. 220]; 5288 5289 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5290 enum NIIF_LARGE_ICON = 0x00000020; 5291 enum NIIF_NOSOUND = 0x00000010; 5292 enum NIIF_USER = 0x00000004; 5293 enum NIIF_ERROR = 0x00000003; 5294 enum NIIF_WARNING = 0x00000002; 5295 enum NIIF_INFO = 0x00000001; 5296 enum NIIF_NONE = 0; 5297 5298 WCharzBuffer t = WCharzBuffer(title); 5299 WCharzBuffer m = WCharzBuffer(message); 5300 5301 t.copyInto(data.szInfoTitle); 5302 m.copyInto(data.szInfo); 5303 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5304 5305 if(icon !is null) { 5306 auto i = new WindowsIcon(icon); 5307 data.hBalloonIcon = i.hIcon; 5308 data.dwInfoFlags |= NIIF_USER; 5309 } 5310 5311 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5312 } else version(OSXCocoa) { 5313 throw new NotYetImplementedException(); 5314 } else static assert(0); 5315 } 5316 5317 /// 5318 //version(Windows) 5319 void show() { 5320 version(X11) { 5321 if(!hidden) 5322 return; 5323 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5324 hidden = false; 5325 } else version(Windows) { 5326 data.uFlags = NIF_STATE; 5327 data.dwState = 0; // NIS_HIDDEN; // windows vista 5328 data.dwStateMask = NIS_HIDDEN; // windows vista 5329 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5330 } else version(OSXCocoa) { 5331 throw new NotYetImplementedException(); 5332 } else static assert(0); 5333 } 5334 5335 version(X11) 5336 bool hidden = false; 5337 5338 /// 5339 //version(Windows) 5340 void hide() { 5341 version(X11) { 5342 if(hidden) 5343 return; 5344 hidden = true; 5345 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5346 } else version(Windows) { 5347 data.uFlags = NIF_STATE; 5348 data.dwState = NIS_HIDDEN; // windows vista 5349 data.dwStateMask = NIS_HIDDEN; // windows vista 5350 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5351 } else version(OSXCocoa) { 5352 throw new NotYetImplementedException(); 5353 } else static assert(0); 5354 } 5355 5356 /// 5357 void close () { 5358 version(X11) { 5359 if (active) { 5360 active = false; // event handler will set this too, but meh 5361 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5362 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5363 flushGui(); 5364 } 5365 } else version(Windows) { 5366 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5367 } else version(OSXCocoa) { 5368 throw new NotYetImplementedException(); 5369 } else static assert(0); 5370 } 5371 5372 ~this() { 5373 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5374 version(X11) 5375 if(clippixmap != None) 5376 XFreePixmap(XDisplayConnection.get, clippixmap); 5377 close(); 5378 } 5379 } 5380 5381 version(X11) 5382 /// Call `XFreePixmap` on the return value. 5383 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5384 char[] data = new char[](i.width * i.height / 8 + 2); 5385 data[] = 0; 5386 5387 int bitOffset = 0; 5388 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5389 ubyte v = c.a > 128 ? 1 : 0; 5390 data[bitOffset / 8] |= v << (bitOffset%8); 5391 bitOffset++; 5392 } 5393 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5394 return handle; 5395 } 5396 5397 5398 // basic functions to make timers 5399 /** 5400 A timer that will trigger your function on a given interval. 5401 5402 5403 You create a timer with an interval and a callback. It will continue 5404 to fire on the interval until it is destroyed. 5405 5406 There are currently no one-off timers (instead, just create one and 5407 destroy it when it is triggered) nor are there pause/resume functions - 5408 the timer must again be destroyed and recreated if you want to pause it. 5409 5410 auto timer = new Timer(50, { it happened!; }); 5411 timer.destroy(); 5412 5413 Timers can only be expected to fire when the event loop is running and only 5414 once per iteration through the event loop. 5415 5416 History: 5417 Prior to December 9, 2020, a timer pulse set too high with a handler too 5418 slow could lock up the event loop. It now guarantees other things will 5419 get a chance to run between timer calls, even if that means not keeping up 5420 with the requested interval. 5421 */ 5422 version(with_timer) { 5423 class Timer { 5424 // FIXME: needs pause and unpause 5425 // FIXME: I might add overloads for ones that take a count of 5426 // how many elapsed since last time (on Windows, it will divide 5427 // the ticks thing given, on Linux it is just available) and 5428 // maybe one that takes an instance of the Timer itself too 5429 /// Create a timer with a callback when it triggers. 5430 this(int intervalInMilliseconds, void delegate() onPulse) { 5431 assert(onPulse !is null); 5432 5433 this.intervalInMilliseconds = intervalInMilliseconds; 5434 this.onPulse = onPulse; 5435 5436 version(Windows) { 5437 /* 5438 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5439 if(handle == 0) 5440 throw new WindowsApiException("SetTimer", GetLastError()); 5441 */ 5442 5443 // thanks to Archival 998 for the WaitableTimer blocks 5444 handle = CreateWaitableTimer(null, false, null); 5445 long initialTime = -intervalInMilliseconds; 5446 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5447 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 5448 5449 mapping[handle] = this; 5450 5451 } else version(linux) { 5452 static import ep = core.sys.linux.epoll; 5453 5454 import core.sys.linux.timerfd; 5455 5456 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5457 if(fd == -1) 5458 throw new Exception("timer create failed"); 5459 5460 mapping[fd] = this; 5461 5462 itimerspec value; 5463 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5464 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5465 5466 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5467 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5468 5469 if(timerfd_settime(fd, 0, &value, null) == -1) 5470 throw new Exception("couldn't make pulse timer"); 5471 5472 version(with_eventloop) { 5473 import arsd.eventloop; 5474 addFileEventListeners(fd, &trigger, null, null); 5475 } else { 5476 prepareEventLoop(); 5477 5478 ep.epoll_event ev = void; 5479 ev.events = ep.EPOLLIN; 5480 ev.data.fd = fd; 5481 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5482 } 5483 } else featureNotImplemented(); 5484 } 5485 5486 private int intervalInMilliseconds; 5487 5488 // just cuz I sometimes call it this. 5489 alias dispose = destroy; 5490 5491 /// Stop and destroy the timer object. 5492 void destroy() { 5493 version(Windows) { 5494 staticDestroy(handle); 5495 handle = null; 5496 } else version(linux) { 5497 staticDestroy(fd); 5498 fd = -1; 5499 } else featureNotImplemented(); 5500 } 5501 5502 version(Windows) 5503 static void staticDestroy(HANDLE handle) { 5504 if(handle) { 5505 // KillTimer(null, handle); 5506 CancelWaitableTimer(cast(void*)handle); 5507 mapping.remove(handle); 5508 CloseHandle(handle); 5509 } 5510 } 5511 else version(linux) 5512 static void staticDestroy(int fd) { 5513 if(fd != -1) { 5514 import unix = core.sys.posix.unistd; 5515 static import ep = core.sys.linux.epoll; 5516 5517 version(with_eventloop) { 5518 import arsd.eventloop; 5519 removeFileEventListeners(fd); 5520 } else { 5521 ep.epoll_event ev = void; 5522 ev.events = ep.EPOLLIN; 5523 ev.data.fd = fd; 5524 5525 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5526 } 5527 unix.close(fd); 5528 mapping.remove(fd); 5529 } 5530 } 5531 5532 ~this() { 5533 version(Windows) { if(handle) 5534 cleanupQueue.queue!staticDestroy(handle); 5535 } else version(linux) { if(fd != -1) 5536 cleanupQueue.queue!staticDestroy(fd); 5537 } 5538 } 5539 5540 5541 void changeTime(int intervalInMilliseconds) 5542 { 5543 this.intervalInMilliseconds = intervalInMilliseconds; 5544 version(Windows) 5545 { 5546 if(handle) 5547 { 5548 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5549 long initialTime = -intervalInMilliseconds; 5550 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5551 throw new WindowsApiException("couldn't change pulse timer", GetLastError()); 5552 } 5553 } 5554 } 5555 5556 5557 private: 5558 5559 void delegate() onPulse; 5560 5561 int lastEventLoopRoundTriggered; 5562 5563 void trigger() { 5564 version(linux) { 5565 import unix = core.sys.posix.unistd; 5566 long val; 5567 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 5568 } else version(Windows) { 5569 if(this.lastEventLoopRoundTriggered == eventLoopRound) 5570 return; // never try to actually run faster than the event loop 5571 lastEventLoopRoundTriggered = eventLoopRound; 5572 } else featureNotImplemented(); 5573 5574 onPulse(); 5575 } 5576 5577 version(Windows) 5578 void rearm() { 5579 5580 } 5581 5582 version(Windows) 5583 extern(Windows) 5584 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 5585 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 5586 if(Timer* t = timer in mapping) { 5587 try 5588 (*t).trigger(); 5589 catch(Exception e) { sdpy_abort(e); assert(0); } 5590 } 5591 } 5592 5593 version(Windows) { 5594 //UINT_PTR handle; 5595 //static Timer[UINT_PTR] mapping; 5596 HANDLE handle; 5597 __gshared Timer[HANDLE] mapping; 5598 } else version(linux) { 5599 int fd = -1; 5600 __gshared Timer[int] mapping; 5601 } else static assert(0, "timer not supported"); 5602 } 5603 } 5604 5605 version(Windows) 5606 private int eventLoopRound; 5607 5608 version(Windows) 5609 /// 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 5610 class WindowsHandleReader { 5611 /// 5612 this(void delegate() onReady, HANDLE handle) { 5613 this.onReady = onReady; 5614 this.handle = handle; 5615 5616 mapping[handle] = this; 5617 5618 enable(); 5619 } 5620 5621 /// 5622 void enable() { 5623 auto el = EventLoop.get().impl; 5624 el.handles ~= handle; 5625 } 5626 5627 /// 5628 void disable() { 5629 auto el = EventLoop.get().impl; 5630 for(int i = 0; i < el.handles.length; i++) { 5631 if(el.handles[i] is handle) { 5632 el.handles[i] = el.handles[$-1]; 5633 el.handles = el.handles[0 .. $-1]; 5634 return; 5635 } 5636 } 5637 } 5638 5639 void dispose() { 5640 disable(); 5641 if(handle) 5642 mapping.remove(handle); 5643 handle = null; 5644 } 5645 5646 void ready() { 5647 if(onReady) 5648 onReady(); 5649 } 5650 5651 HANDLE handle; 5652 void delegate() onReady; 5653 5654 __gshared WindowsHandleReader[HANDLE] mapping; 5655 } 5656 5657 version(Posix) 5658 /// Lets you add files to the event loop for reading. Use at your own risk. 5659 class PosixFdReader { 5660 /// 5661 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5662 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 5663 } 5664 5665 /// 5666 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5667 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 5668 } 5669 5670 /// 5671 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5672 this.onReady = onReady; 5673 this.fd = fd; 5674 this.captureWrites = captureWrites; 5675 this.captureReads = captureReads; 5676 5677 mapping[fd] = this; 5678 5679 version(with_eventloop) { 5680 import arsd.eventloop; 5681 addFileEventListeners(fd, &readyel); 5682 } else { 5683 enable(); 5684 } 5685 } 5686 5687 bool captureReads; 5688 bool captureWrites; 5689 5690 version(with_eventloop) {} else 5691 /// 5692 void enable() { 5693 prepareEventLoop(); 5694 5695 enabled = true; 5696 5697 version(linux) { 5698 static import ep = core.sys.linux.epoll; 5699 ep.epoll_event ev = void; 5700 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5701 //import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites); 5702 ev.data.fd = fd; 5703 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5704 } else { 5705 5706 } 5707 } 5708 5709 version(with_eventloop) {} else 5710 /// 5711 void disable() { 5712 prepareEventLoop(); 5713 5714 enabled = false; 5715 5716 version(linux) { 5717 static import ep = core.sys.linux.epoll; 5718 ep.epoll_event ev = void; 5719 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5720 //import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites); 5721 ev.data.fd = fd; 5722 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5723 } 5724 } 5725 5726 version(with_eventloop) {} else 5727 /// 5728 void dispose() { 5729 if(enabled) 5730 disable(); 5731 if(fd != -1) 5732 mapping.remove(fd); 5733 fd = -1; 5734 } 5735 5736 void delegate(int, bool, bool) onReady; 5737 5738 version(with_eventloop) 5739 void readyel() { 5740 onReady(fd, true, true); 5741 } 5742 5743 void ready(uint flags) { 5744 version(linux) { 5745 static import ep = core.sys.linux.epoll; 5746 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 5747 } else { 5748 import core.sys.posix.poll; 5749 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 5750 } 5751 } 5752 5753 void hup(uint flags) { 5754 if(onHup) 5755 onHup(); 5756 } 5757 5758 void delegate() onHup; 5759 5760 int fd = -1; 5761 private bool enabled; 5762 __gshared PosixFdReader[int] mapping; 5763 } 5764 5765 // basic functions to access the clipboard 5766 /+ 5767 5768 5769 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 5770 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 5771 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5772 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 5773 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 5774 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5775 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 5776 5777 +/ 5778 5779 /++ 5780 this does a delegate because it is actually an async call on X... 5781 the receiver may never be called if the clipboard is empty or unavailable 5782 gets plain text from the clipboard. 5783 +/ 5784 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 5785 version(Windows) { 5786 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5787 if(OpenClipboard(hwndOwner) == 0) 5788 throw new WindowsApiException("OpenClipboard", GetLastError()); 5789 scope(exit) 5790 CloseClipboard(); 5791 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 5792 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 5793 5794 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 5795 scope(exit) 5796 GlobalUnlock(dataHandle); 5797 5798 // FIXME: CR/LF conversions 5799 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 5800 int len = 0; 5801 auto d = data; 5802 while(*d) { 5803 d++; 5804 len++; 5805 } 5806 string s; 5807 s.reserve(len); 5808 foreach(dchar ch; data[0 .. len]) { 5809 s ~= ch; 5810 } 5811 receiver(s); 5812 } 5813 } 5814 } else version(X11) { 5815 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5816 } else version(OSXCocoa) { 5817 throw new NotYetImplementedException(); 5818 } else static assert(0); 5819 } 5820 5821 // FIXME: a clipboard listener might be cool btw 5822 5823 /++ 5824 this does a delegate because it is actually an async call on X... 5825 the receiver may never be called if the clipboard is empty or unavailable 5826 gets image from the clipboard. 5827 5828 templated because it introduces an optional dependency on arsd.bmp 5829 +/ 5830 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 5831 version(Windows) { 5832 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5833 if(OpenClipboard(hwndOwner) == 0) 5834 throw new WindowsApiException("OpenClipboard", GetLastError()); 5835 scope(exit) 5836 CloseClipboard(); 5837 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 5838 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 5839 scope(exit) 5840 GlobalUnlock(dataHandle); 5841 5842 auto len = GlobalSize(dataHandle); 5843 5844 import arsd.bmp; 5845 auto img = readBmp(data[0 .. len], false); 5846 receiver(img); 5847 } 5848 } 5849 } else version(X11) { 5850 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5851 } else version(OSXCocoa) { 5852 throw new NotYetImplementedException(); 5853 } else static assert(0); 5854 } 5855 5856 /// Copies some text to the clipboard. 5857 void setClipboardText(SimpleWindow clipboardOwner, string text) { 5858 assert(clipboardOwner !is null); 5859 version(Windows) { 5860 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5861 throw new WindowsApiException("OpenClipboard", GetLastError()); 5862 scope(exit) 5863 CloseClipboard(); 5864 EmptyClipboard(); 5865 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5866 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 5867 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 5868 if(auto data = cast(wchar*) GlobalLock(handle)) { 5869 auto slice = data[0 .. sz]; 5870 scope(failure) 5871 GlobalUnlock(handle); 5872 5873 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5874 5875 GlobalUnlock(handle); 5876 SetClipboardData(CF_UNICODETEXT, handle); 5877 } 5878 } else version(X11) { 5879 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 5880 } else version(OSXCocoa) { 5881 throw new NotYetImplementedException(); 5882 } else static assert(0); 5883 } 5884 5885 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 5886 assert(clipboardOwner !is null); 5887 version(Windows) { 5888 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5889 throw new WindowsApiException("OpenClipboard", GetLastError()); 5890 scope(exit) 5891 CloseClipboard(); 5892 EmptyClipboard(); 5893 5894 5895 import arsd.bmp; 5896 ubyte[] mdata; 5897 mdata.reserve(img.width * img.height); 5898 void sink(ubyte b) { 5899 mdata ~= b; 5900 } 5901 writeBmpIndirect(img, &sink, false); 5902 5903 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 5904 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 5905 if(auto data = cast(ubyte*) GlobalLock(handle)) { 5906 auto slice = data[0 .. mdata.length]; 5907 scope(failure) 5908 GlobalUnlock(handle); 5909 5910 slice[] = mdata[]; 5911 5912 GlobalUnlock(handle); 5913 SetClipboardData(CF_DIB, handle); 5914 } 5915 } else version(X11) { 5916 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 5917 mixin X11SetSelectionHandler_Basics; 5918 private const(ubyte)[] mdata; 5919 private const(ubyte)[] mdata_original; 5920 this(MemoryImage img) { 5921 import arsd.bmp; 5922 5923 mdata.reserve(img.width * img.height); 5924 void sink(ubyte b) { 5925 mdata ~= b; 5926 } 5927 writeBmpIndirect(img, &sink, true); 5928 5929 mdata_original = mdata; 5930 } 5931 5932 Atom[] availableFormats() { 5933 auto display = XDisplayConnection.get; 5934 return [ 5935 GetAtom!"image/bmp"(display), 5936 GetAtom!"TARGETS"(display) 5937 ]; 5938 } 5939 5940 ubyte[] getData(Atom format, return scope ubyte[] data) { 5941 if(mdata.length < data.length) { 5942 data[0 .. mdata.length] = mdata[]; 5943 auto ret = data[0 .. mdata.length]; 5944 mdata = mdata[$..$]; 5945 return ret; 5946 } else { 5947 data[] = mdata[0 .. data.length]; 5948 mdata = mdata[data.length .. $]; 5949 return data[]; 5950 } 5951 } 5952 5953 void done() { 5954 mdata = mdata_original; 5955 } 5956 } 5957 5958 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 5959 } else version(OSXCocoa) { 5960 throw new NotYetImplementedException(); 5961 } else static assert(0); 5962 } 5963 5964 5965 version(X11) { 5966 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 5967 5968 private Atom*[] interredAtoms; // for discardAndRecreate 5969 5970 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 5971 /// Platform-specific for X11. 5972 /// History: On February 21, 2021, I changed the default value of `create` to be true. 5973 @property Atom GetAtom(string name, bool create = true)(Display* display) { 5974 static Atom a; 5975 if(!a) { 5976 a = XInternAtom(display, name, !create); 5977 interredAtoms ~= &a; 5978 } 5979 if(a == None) 5980 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 5981 return a; 5982 } 5983 5984 /// Platform-specific for X11 - gets atom names as a string. 5985 string getAtomName(Atom atom, Display* display) { 5986 auto got = XGetAtomName(display, atom); 5987 scope(exit) XFree(got); 5988 import core.stdc.string; 5989 string s = got[0 .. strlen(got)].idup; 5990 return s; 5991 } 5992 5993 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 5994 void setPrimarySelection(SimpleWindow window, string text) { 5995 setX11Selection!"PRIMARY"(window, text); 5996 } 5997 5998 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 5999 void setSecondarySelection(SimpleWindow window, string text) { 6000 setX11Selection!"SECONDARY"(window, text); 6001 } 6002 6003 interface X11SetSelectionHandler { 6004 // should include TARGETS right now 6005 Atom[] availableFormats(); 6006 // Return the slice of data you filled, empty slice if done. 6007 // this is to support the incremental thing 6008 ubyte[] getData(Atom format, return scope ubyte[] data); 6009 6010 void done(); 6011 6012 void handleRequest(XEvent); 6013 6014 bool matchesIncr(Window, Atom); 6015 void sendMoreIncr(XPropertyEvent*); 6016 } 6017 6018 mixin template X11SetSelectionHandler_Basics() { 6019 Window incrWindow; 6020 Atom incrAtom; 6021 Atom selectionAtom; 6022 Atom formatAtom; 6023 ubyte[] toSend; 6024 bool matchesIncr(Window w, Atom a) { 6025 return incrAtom && incrAtom == a && w == incrWindow; 6026 } 6027 void sendMoreIncr(XPropertyEvent* event) { 6028 auto display = XDisplayConnection.get; 6029 6030 XChangeProperty (display, 6031 incrWindow, 6032 incrAtom, 6033 formatAtom, 6034 8 /* bits */, PropModeReplace, 6035 toSend.ptr, cast(int) toSend.length); 6036 6037 if(toSend.length != 0) { 6038 toSend = this.getData(formatAtom, toSend[]); 6039 } else { 6040 this.done(); 6041 incrWindow = None; 6042 incrAtom = None; 6043 selectionAtom = None; 6044 formatAtom = None; 6045 toSend = null; 6046 } 6047 } 6048 void handleRequest(XEvent ev) { 6049 6050 auto display = XDisplayConnection.get; 6051 6052 XSelectionRequestEvent* event = &ev.xselectionrequest; 6053 XSelectionEvent selectionEvent; 6054 selectionEvent.type = EventType.SelectionNotify; 6055 selectionEvent.display = event.display; 6056 selectionEvent.requestor = event.requestor; 6057 selectionEvent.selection = event.selection; 6058 selectionEvent.time = event.time; 6059 selectionEvent.target = event.target; 6060 6061 bool supportedType() { 6062 foreach(t; this.availableFormats()) 6063 if(t == event.target) 6064 return true; 6065 return false; 6066 } 6067 6068 if(event.property == None) { 6069 selectionEvent.property = event.target; 6070 6071 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6072 XFlush(display); 6073 } if(event.target == GetAtom!"TARGETS"(display)) { 6074 /* respond with the supported types */ 6075 auto tlist = this.availableFormats(); 6076 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6077 selectionEvent.property = event.property; 6078 6079 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6080 XFlush(display); 6081 } else if(supportedType()) { 6082 auto buffer = new ubyte[](1024 * 64); 6083 auto toSend = this.getData(event.target, buffer[]); 6084 6085 if(toSend.length < 32 * 1024) { 6086 // small enough to send directly... 6087 selectionEvent.property = event.property; 6088 XChangeProperty (display, 6089 selectionEvent.requestor, 6090 selectionEvent.property, 6091 event.target, 6092 8 /* bits */, 0 /* PropModeReplace */, 6093 toSend.ptr, cast(int) toSend.length); 6094 6095 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6096 XFlush(display); 6097 } else { 6098 // large, let's send incrementally 6099 arch_ulong l = toSend.length; 6100 6101 // if I wanted other events from this window don't want to clear that out.... 6102 XWindowAttributes xwa; 6103 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6104 6105 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6106 6107 incrWindow = event.requestor; 6108 incrAtom = event.property; 6109 formatAtom = event.target; 6110 selectionAtom = event.selection; 6111 this.toSend = toSend; 6112 6113 selectionEvent.property = event.property; 6114 XChangeProperty (display, 6115 selectionEvent.requestor, 6116 selectionEvent.property, 6117 GetAtom!"INCR"(display), 6118 32 /* bits */, PropModeReplace, 6119 &l, 1); 6120 6121 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6122 XFlush(display); 6123 } 6124 //if(after) 6125 //after(); 6126 } else { 6127 debug(sdpy_clip) { 6128 import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display)); 6129 } 6130 selectionEvent.property = None; // I don't know how to handle this type... 6131 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6132 XFlush(display); 6133 } 6134 } 6135 } 6136 6137 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6138 mixin X11SetSelectionHandler_Basics; 6139 private const(ubyte)[] text; 6140 private const(ubyte)[] text_original; 6141 this(string text) { 6142 this.text = cast(const ubyte[]) text; 6143 this.text_original = this.text; 6144 } 6145 Atom[] availableFormats() { 6146 auto display = XDisplayConnection.get; 6147 return [ 6148 GetAtom!"UTF8_STRING"(display), 6149 GetAtom!"text/plain"(display), 6150 XA_STRING, 6151 GetAtom!"TARGETS"(display) 6152 ]; 6153 } 6154 6155 ubyte[] getData(Atom format, return scope ubyte[] data) { 6156 if(text.length < data.length) { 6157 data[0 .. text.length] = text[]; 6158 return data[0 .. text.length]; 6159 } else { 6160 data[] = text[0 .. data.length]; 6161 text = text[data.length .. $]; 6162 return data[]; 6163 } 6164 } 6165 6166 void done() { 6167 text = text_original; 6168 } 6169 } 6170 6171 /// 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?!) 6172 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6173 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6174 } 6175 6176 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6177 assert(window !is null); 6178 6179 auto display = XDisplayConnection.get(); 6180 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6181 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6182 else Atom a = GetAtom!atomName(display); 6183 6184 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6185 6186 window.impl.setSelectionHandlers[a] = data; 6187 } 6188 6189 /// 6190 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6191 getX11Selection!"PRIMARY"(window, handler); 6192 } 6193 6194 // added July 28, 2020 6195 // undocumented as experimental tho 6196 interface X11GetSelectionHandler { 6197 void handleData(Atom target, in ubyte[] data); 6198 Atom findBestFormat(Atom[] answer); 6199 6200 void prepareIncremental(Window, Atom); 6201 bool matchesIncr(Window, Atom); 6202 void handleIncrData(Atom, in ubyte[] data); 6203 } 6204 6205 mixin template X11GetSelectionHandler_Basics() { 6206 Window incrWindow; 6207 Atom incrAtom; 6208 6209 void prepareIncremental(Window w, Atom a) { 6210 incrWindow = w; 6211 incrAtom = a; 6212 } 6213 bool matchesIncr(Window w, Atom a) { 6214 return incrWindow == w && incrAtom == a; 6215 } 6216 6217 Atom incrFormatAtom; 6218 ubyte[] incrData; 6219 void handleIncrData(Atom format, in ubyte[] data) { 6220 incrFormatAtom = format; 6221 6222 if(data.length) 6223 incrData ~= data; 6224 else 6225 handleData(incrFormatAtom, incrData); 6226 6227 } 6228 } 6229 6230 /// 6231 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6232 assert(window !is null); 6233 6234 auto display = XDisplayConnection.get(); 6235 auto atom = GetAtom!atomName(display); 6236 6237 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6238 this(void delegate(in char[]) handler) { 6239 this.handler = handler; 6240 } 6241 6242 mixin X11GetSelectionHandler_Basics; 6243 6244 void delegate(in char[]) handler; 6245 6246 void handleData(Atom target, in ubyte[] data) { 6247 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6248 handler(cast(const char[]) data); 6249 } 6250 6251 Atom findBestFormat(Atom[] answer) { 6252 Atom best = None; 6253 foreach(option; answer) { 6254 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6255 best = option; 6256 break; 6257 } else if(option == XA_STRING) { 6258 best = option; 6259 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6260 best = option; 6261 } 6262 } 6263 return best; 6264 } 6265 } 6266 6267 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6268 6269 auto target = GetAtom!"TARGETS"(display); 6270 6271 // SDD_DATA is "simpledisplay.d data" 6272 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6273 } 6274 6275 /// Gets the image on the clipboard, if there is one. Added July 2020. 6276 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6277 assert(window !is null); 6278 6279 auto display = XDisplayConnection.get(); 6280 auto atom = GetAtom!atomName(display); 6281 6282 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6283 this(void delegate(MemoryImage) handler) { 6284 this.handler = handler; 6285 } 6286 6287 mixin X11GetSelectionHandler_Basics; 6288 6289 void delegate(MemoryImage) handler; 6290 6291 void handleData(Atom target, in ubyte[] data) { 6292 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6293 import arsd.bmp; 6294 handler(readBmp(data)); 6295 } 6296 } 6297 6298 Atom findBestFormat(Atom[] answer) { 6299 Atom best = None; 6300 foreach(option; answer) { 6301 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6302 best = option; 6303 } 6304 } 6305 return best; 6306 } 6307 6308 } 6309 6310 6311 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6312 6313 auto target = GetAtom!"TARGETS"(display); 6314 6315 // SDD_DATA is "simpledisplay.d data" 6316 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6317 } 6318 6319 6320 /// 6321 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6322 Atom actualType; 6323 int actualFormat; 6324 arch_ulong actualItems; 6325 arch_ulong bytesRemaining; 6326 void* data; 6327 6328 auto display = XDisplayConnection.get(); 6329 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6330 if(actualFormat == 0) 6331 return null; 6332 else { 6333 int byteLength; 6334 if(actualFormat == 32) { 6335 // 32 means it is a C long... which is variable length 6336 actualFormat = cast(int) arch_long.sizeof * 8; 6337 } 6338 6339 // then it is just a bit count 6340 byteLength = cast(int) (actualItems * actualFormat / 8); 6341 6342 auto d = new ubyte[](byteLength); 6343 d[] = cast(ubyte[]) data[0 .. byteLength]; 6344 XFree(data); 6345 return d; 6346 } 6347 } 6348 return null; 6349 } 6350 6351 /* defined in the systray spec */ 6352 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6353 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6354 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6355 6356 6357 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6358 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6359 public class GlobalHotkey { 6360 KeyEvent key; 6361 void delegate () handler; 6362 6363 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6364 6365 /// Create from initialzed KeyEvent object 6366 this (KeyEvent akey, void delegate () ahandler=null) { 6367 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6368 key = akey; 6369 handler = ahandler; 6370 } 6371 6372 /// Create from emacs-like key name ("C-M-Y", etc.) 6373 this (const(char)[] akey, void delegate () ahandler=null) { 6374 key = KeyEvent.parse(akey); 6375 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6376 handler = ahandler; 6377 } 6378 6379 } 6380 6381 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6382 //conwriteln("failed to grab key"); 6383 GlobalHotkeyManager.ghfailed = true; 6384 return 0; 6385 } 6386 6387 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6388 Image.impl.xshmfailed = true; 6389 return 0; 6390 } 6391 6392 private __gshared int errorHappened; 6393 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6394 import core.stdc.stdio; 6395 char[265] buffer; 6396 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6397 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); 6398 errorHappened = true; 6399 return 0; 6400 } 6401 6402 /++ 6403 Global hotkey manager. It contains static methods to manage global hotkeys. 6404 6405 --- 6406 try { 6407 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6408 } catch (Exception e) { 6409 conwriteln("ERROR registering hotkey!"); 6410 } 6411 EventLoop.get.run(); 6412 --- 6413 6414 The key strings are based on Emacs. In practical terms, 6415 `M` means `alt` and `H` means the Windows logo key. `C` 6416 is `ctrl`. 6417 6418 $(WARNING 6419 This is X-specific right now. If you are on 6420 Windows, try [registerHotKey] instead. 6421 6422 We will probably merge these into a single 6423 interface later. 6424 ) 6425 +/ 6426 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6427 version(X11) { 6428 void recreateAfterDisconnect() { 6429 throw new Exception("NOT IMPLEMENTED"); 6430 } 6431 void discardConnectionState() { 6432 throw new Exception("NOT IMPLEMENTED"); 6433 } 6434 } 6435 6436 private static immutable uint[8] masklist = [ 0, 6437 KeyOrButtonMask.LockMask, 6438 KeyOrButtonMask.Mod2Mask, 6439 KeyOrButtonMask.Mod3Mask, 6440 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6441 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6442 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6443 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6444 ]; 6445 private __gshared GlobalHotkeyManager ghmanager; 6446 private __gshared bool ghfailed = false; 6447 6448 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6449 if (modmask == 0) return false; 6450 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6451 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6452 return true; 6453 } 6454 6455 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6456 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6457 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6458 return modmask; 6459 } 6460 6461 private static uint keyEvent2KeyCode() (scope auto ref const KeyEvent ke) { 6462 uint keycode = cast(uint)ke.key; 6463 auto dpy = XDisplayConnection.get; 6464 return XKeysymToKeycode(dpy, keycode); 6465 } 6466 6467 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6468 6469 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6470 6471 NativeEventHandler getNativeEventHandler () { 6472 return delegate int (XEvent e) { 6473 if (e.type != EventType.KeyPress) return 1; 6474 auto kev = cast(const(XKeyEvent)*)&e; 6475 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6476 if (auto ghkp = hash in globalHotkeyList) { 6477 try { 6478 ghkp.doHandle(); 6479 } catch (Exception e) { 6480 import core.stdc.stdio : stderr, fprintf; 6481 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6482 } 6483 } 6484 return 1; 6485 }; 6486 } 6487 6488 private this () { 6489 auto dpy = XDisplayConnection.get; 6490 auto root = RootWindow(dpy, DefaultScreen(dpy)); 6491 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 6492 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 6493 } 6494 6495 /// Register new global hotkey with initialized `GlobalHotkey` object. 6496 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 6497 static void register (GlobalHotkey gh) { 6498 if (gh is null) return; 6499 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 6500 6501 auto dpy = XDisplayConnection.get; 6502 immutable keycode = keyEvent2KeyCode(gh.key); 6503 6504 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 6505 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 6506 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 6507 XSync(dpy, 0/*False*/); 6508 6509 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6510 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6511 ghfailed = false; 6512 foreach (immutable uint ormask; masklist[]) { 6513 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 6514 } 6515 XSync(dpy, 0/*False*/); 6516 XSetErrorHandler(savedErrorHandler); 6517 6518 if (ghfailed) { 6519 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6520 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 6521 XSync(dpy, 0/*False*/); 6522 XSetErrorHandler(savedErrorHandler); 6523 throw new Exception("cannot register global hotkey"); 6524 } 6525 6526 globalHotkeyList[hash] = gh; 6527 } 6528 6529 /// Ditto 6530 static void register (const(char)[] akey, void delegate () ahandler) { 6531 register(new GlobalHotkey(akey, ahandler)); 6532 } 6533 6534 private static void removeByHash (ulong hash) { 6535 if (auto ghp = hash in globalHotkeyList) { 6536 auto dpy = XDisplayConnection.get; 6537 immutable keycode = keyEvent2KeyCode(ghp.key); 6538 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6539 XSync(dpy, 0/*False*/); 6540 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6541 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 6542 XSync(dpy, 0/*False*/); 6543 XSetErrorHandler(savedErrorHandler); 6544 globalHotkeyList.remove(hash); 6545 } 6546 } 6547 6548 /// Register new global hotkey with previously used `GlobalHotkey` object. 6549 /// It is safe to unregister unknown or invalid hotkey. 6550 static void unregister (GlobalHotkey gh) { 6551 //TODO: add second AA for faster search? prolly doesn't worth it. 6552 if (gh is null) return; 6553 foreach (const ref kv; globalHotkeyList.byKeyValue) { 6554 if (kv.value is gh) { 6555 removeByHash(kv.key); 6556 return; 6557 } 6558 } 6559 } 6560 6561 /// Ditto. 6562 static void unregister (const(char)[] key) { 6563 auto kev = KeyEvent.parse(key); 6564 immutable keycode = keyEvent2KeyCode(kev); 6565 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 6566 } 6567 } 6568 } 6569 6570 version(Windows) { 6571 /++ 6572 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 6573 6574 This is platform-specific UTF-16 function for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application). 6575 +/ 6576 void sendSyntheticInput(wstring s) { 6577 INPUT[] inputs; 6578 inputs.reserve(s.length * 2); 6579 6580 foreach(wchar c; s) { 6581 INPUT input; 6582 input.type = INPUT_KEYBOARD; 6583 input.ki.wScan = c; 6584 input.ki.dwFlags = KEYEVENTF_UNICODE; 6585 inputs ~= input; 6586 6587 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6588 inputs ~= input; 6589 } 6590 6591 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6592 throw new WindowsApiException("SendInput", GetLastError()); 6593 } 6594 6595 } 6596 6597 6598 // global hotkey helper function 6599 6600 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 6601 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6602 __gshared int hotkeyId = 0; 6603 int id = ++hotkeyId; 6604 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6605 throw new Exception("RegisterHotKey"); 6606 6607 __gshared void delegate()[WPARAM][HWND] handlers; 6608 6609 handlers[window.impl.hwnd][id] = handler; 6610 6611 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 6612 6613 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 6614 switch(msg) { 6615 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6616 case WM_HOTKEY: 6617 if(auto list = hwnd in handlers) { 6618 if(auto h = wParam in *list) { 6619 (*h)(); 6620 return 0; 6621 } 6622 } 6623 goto default; 6624 default: 6625 } 6626 if(oldHandler) 6627 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 6628 return 1; // pass it on 6629 }; 6630 6631 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6632 oldHandler = window.handleNativeEvent; 6633 window.handleNativeEvent = nativeEventHandler; 6634 } 6635 6636 return id; 6637 } 6638 6639 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 6640 void unregisterHotKey(SimpleWindow window, int id) { 6641 if(!UnregisterHotKey(window.impl.hwnd, id)) 6642 throw new WindowsApiException("UnregisterHotKey", GetLastError()); 6643 } 6644 } 6645 6646 version (X11) { 6647 pragma(lib, "dl"); 6648 import core.sys.posix.dlfcn; 6649 } 6650 6651 /++ 6652 Allows for sending synthetic input to the X server via the Xtst 6653 extension or on Windows using SendInput. 6654 6655 Please remember user input is meant to be user - don't use this 6656 if you have some other alternative! 6657 6658 History: 6659 Added May 17, 2020 with the X implementation. 6660 6661 Added unified implementation for Windows on April 3, 2022. (Prior to that, you had to use the top-level [sendSyntheticInput] or the Windows SendInput call directly.) 6662 Bugs: 6663 All methods on OSX Cocoa will throw not yet implemented exceptions. 6664 +/ 6665 struct SyntheticInput { 6666 @disable this(); 6667 6668 private int* refcount; 6669 6670 version(X11) { 6671 private void* lib; 6672 6673 private extern(C) { 6674 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6675 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6676 } 6677 } 6678 6679 /// The dummy param must be 0. 6680 this(int dummy) { 6681 version(X11) { 6682 lib = dlopen("libXtst.so", RTLD_NOW); 6683 if(lib is null) 6684 throw new Exception("cannot load xtest lib extension"); 6685 scope(failure) 6686 dlclose(lib); 6687 6688 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6689 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6690 6691 if(XTestFakeKeyEvent is null) 6692 throw new Exception("No XTestFakeKeyEvent"); 6693 if(XTestFakeButtonEvent is null) 6694 throw new Exception("No XTestFakeButtonEvent"); 6695 } 6696 6697 refcount = new int; 6698 *refcount = 1; 6699 } 6700 6701 this(this) { 6702 if(refcount) 6703 *refcount += 1; 6704 } 6705 6706 ~this() { 6707 if(refcount) { 6708 *refcount -= 1; 6709 if(*refcount == 0) 6710 // I commented this because if I close the lib before 6711 // XCloseDisplay, it is liable to segfault... so just 6712 // gonna keep it loaded if it is loaded, no big deal 6713 // anyway. 6714 {} // dlclose(lib); 6715 } 6716 } 6717 6718 /++ 6719 Simulates typing a string into the keyboard. 6720 6721 Bugs: 6722 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 6723 6724 Not implemented except on Windows and X11. 6725 +/ 6726 void sendSyntheticInput(string s) { 6727 version(Windows) { 6728 INPUT[] inputs; 6729 inputs.reserve(s.length * 2); 6730 6731 auto ei = GetMessageExtraInfo(); 6732 6733 foreach(wchar c; s) { 6734 INPUT input; 6735 input.type = INPUT_KEYBOARD; 6736 input.ki.wScan = c; 6737 input.ki.dwFlags = KEYEVENTF_UNICODE; 6738 input.ki.dwExtraInfo = ei; 6739 inputs ~= input; 6740 6741 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6742 inputs ~= input; 6743 } 6744 6745 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6746 throw new WindowsApiException("SendInput", GetLastError()); 6747 } 6748 } else version(X11) { 6749 int delay = 0; 6750 foreach(ch; s) { 6751 pressKey(cast(Key) ch, true, delay); 6752 pressKey(cast(Key) ch, false, delay); 6753 delay += 5; 6754 } 6755 } else throw new NotYetImplementedException(); 6756 } 6757 6758 /++ 6759 Sends a fake press or release key event. 6760 6761 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6762 6763 Bugs: 6764 The `delay` parameter is not implemented yet on Windows. 6765 6766 Not implemented except on Windows and X11. 6767 +/ 6768 void pressKey(Key key, bool pressed, int delay = 0) { 6769 version(Windows) { 6770 INPUT input; 6771 input.type = INPUT_KEYBOARD; 6772 input.ki.wVk = cast(ushort) key; 6773 6774 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 6775 input.ki.dwExtraInfo = GetMessageExtraInfo(); 6776 6777 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6778 throw new WindowsApiException("SendInput", GetLastError()); 6779 } 6780 } else version(X11) { 6781 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6782 } else throw new NotYetImplementedException(); 6783 } 6784 6785 /++ 6786 Sends a fake mouse button press or release event. 6787 6788 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6789 6790 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 6791 6792 Bugs: 6793 The `delay` parameter is not implemented yet on Windows. 6794 6795 The backButton and forwardButton will throw NotYetImplementedException on Windows. 6796 6797 All arguments will throw NotYetImplementedException on OSX Cocoa. 6798 +/ 6799 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6800 version(Windows) { 6801 INPUT input; 6802 input.type = INPUT_MOUSE; 6803 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6804 6805 // input.mi.mouseData for a wheel event 6806 6807 switch(button) { 6808 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 6809 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 6810 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 6811 case MouseButton.wheelUp: 6812 case MouseButton.wheelDown: 6813 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 6814 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 6815 break; 6816 case MouseButton.backButton: throw new NotYetImplementedException(); 6817 case MouseButton.forwardButton: throw new NotYetImplementedException(); 6818 default: 6819 } 6820 6821 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6822 throw new WindowsApiException("SendInput", GetLastError()); 6823 } 6824 } else version(X11) { 6825 int btn; 6826 6827 switch(button) { 6828 case MouseButton.left: btn = 1; break; 6829 case MouseButton.middle: btn = 2; break; 6830 case MouseButton.right: btn = 3; break; 6831 case MouseButton.wheelUp: btn = 4; break; 6832 case MouseButton.wheelDown: btn = 5; break; 6833 case MouseButton.backButton: btn = 8; break; 6834 case MouseButton.forwardButton: btn = 9; break; 6835 default: 6836 } 6837 6838 assert(btn); 6839 6840 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 6841 } else throw new NotYetImplementedException(); 6842 } 6843 6844 /// 6845 static void moveMouseArrowBy(int dx, int dy) { 6846 version(Windows) { 6847 INPUT input; 6848 input.type = INPUT_MOUSE; 6849 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6850 input.mi.dx = dx; 6851 input.mi.dy = dy; 6852 input.mi.dwFlags = MOUSEEVENTF_MOVE; 6853 6854 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6855 throw new WindowsApiException("SendInput", GetLastError()); 6856 } 6857 } else version(X11) { 6858 auto disp = XDisplayConnection.get(); 6859 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 6860 XFlush(disp); 6861 } else throw new NotYetImplementedException(); 6862 } 6863 6864 /// 6865 static void moveMouseArrowTo(int x, int y) { 6866 version(Windows) { 6867 INPUT input; 6868 input.type = INPUT_MOUSE; 6869 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6870 input.mi.dx = x; 6871 input.mi.dy = y; 6872 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 6873 6874 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6875 throw new WindowsApiException("SendInput", GetLastError()); 6876 } 6877 } else version(X11) { 6878 auto disp = XDisplayConnection.get(); 6879 auto root = RootWindow(disp, DefaultScreen(disp)); 6880 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 6881 XFlush(disp); 6882 } else throw new NotYetImplementedException(); 6883 } 6884 } 6885 6886 6887 6888 /++ 6889 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 6890 6891 See_Also: 6892 $(LIST 6893 *[ScreenPainter] 6894 *[ScreenPainter.rasterOp] 6895 ) 6896 +/ 6897 enum RasterOp { 6898 normal, /// Replaces the pixel. 6899 xor, /// Uses bitwise xor to draw. 6900 } 6901 6902 // being phobos-free keeps the size WAY down 6903 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 6904 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 6905 package(arsd) const(wchar)* toWStringz(string s) { 6906 wstring r; 6907 foreach(dchar c; s) 6908 r ~= c; 6909 r ~= '\0'; 6910 return r.ptr; 6911 } 6912 private string[] split(in void[] a, char c) { 6913 string[] ret; 6914 size_t previous = 0; 6915 foreach(i, char ch; cast(ubyte[]) a) { 6916 if(ch == c) { 6917 ret ~= cast(string) a[previous .. i]; 6918 previous = i + 1; 6919 } 6920 } 6921 if(previous != a.length) 6922 ret ~= cast(string) a[previous .. $]; 6923 return ret; 6924 } 6925 6926 version(without_opengl) { 6927 enum OpenGlOptions { 6928 no, 6929 } 6930 } else { 6931 /++ 6932 Determines if you want an OpenGL context created on the new window. 6933 6934 6935 See more: [#topics-3d|in the 3d topic]. 6936 6937 --- 6938 import arsd.simpledisplay; 6939 void main() { 6940 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 6941 6942 // Set up the matrix 6943 window.setAsCurrentOpenGlContext(); // make this window active 6944 6945 // This is called on each frame, we will draw our scene 6946 window.redrawOpenGlScene = delegate() { 6947 6948 }; 6949 6950 window.eventLoop(0); 6951 } 6952 --- 6953 +/ 6954 enum OpenGlOptions { 6955 no, /// No OpenGL context is created 6956 yes, /// Yes, create an OpenGL context 6957 } 6958 6959 version(X11) { 6960 static if (!SdpyIsUsingIVGLBinds) { 6961 6962 6963 struct __GLXFBConfigRec {} 6964 alias GLXFBConfig = __GLXFBConfigRec*; 6965 6966 //pragma(lib, "GL"); 6967 //pragma(lib, "GLU"); 6968 interface GLX { 6969 extern(C) nothrow @nogc { 6970 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 6971 const int *attrib_list); 6972 6973 void glXCopyContext(Display *dpy, GLXContext src, 6974 GLXContext dst, arch_ulong mask); 6975 6976 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 6977 GLXContext share_list, Bool direct); 6978 6979 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 6980 Pixmap pixmap); 6981 6982 void glXDestroyContext(Display *dpy, GLXContext ctx); 6983 6984 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 6985 6986 int glXGetConfig(Display *dpy, XVisualInfo *vis, 6987 int attrib, int *value); 6988 6989 GLXContext glXGetCurrentContext(); 6990 6991 GLXDrawable glXGetCurrentDrawable(); 6992 6993 Bool glXIsDirect(Display *dpy, GLXContext ctx); 6994 6995 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 6996 GLXContext ctx); 6997 6998 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 6999 7000 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7001 7002 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7003 7004 void glXUseXFont(Font font, int first, int count, int list_base); 7005 7006 void glXWaitGL(); 7007 7008 void glXWaitX(); 7009 7010 7011 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7012 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7013 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7014 7015 char* glXQueryExtensionsString (Display*, int); 7016 void* glXGetProcAddress (const(char)*); 7017 7018 } 7019 } 7020 7021 version(OSX) 7022 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7023 else 7024 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7025 shared static this() { 7026 glx.loadDynamicLibrary(); 7027 } 7028 7029 alias glbindGetProcAddress = glXGetProcAddress; 7030 } 7031 } else version(Windows) { 7032 /* it is done below by interface GL */ 7033 } else 7034 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."); 7035 } 7036 7037 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7038 alias Resizablity = Resizability; 7039 7040 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7041 enum Resizability { 7042 fixedSize, /// the window cannot be resized. If it is resized anyway, simpledisplay will position and truncate your drawn content without necessarily informing your program, maintaining the API illusion of a non-resizable window. 7043 allowResizing, /// the window can be resized. The buffer (if there is one) will automatically adjust size, but not stretch the contents. the windowResized delegate will be called so you can respond to the new size yourself. This allows most control for both user and you as the library consumer, but you also have to do the most work to handle it well. 7044 /++ 7045 $(PITFALL 7046 Planned for the future but not implemented. 7047 ) 7048 7049 Allow the user to resize the window, but try to maintain the original aspect ratio of the client area. The simpledisplay library may letterbox your content if necessary but will not stretch it. The windowResized delegate and width and height members will be updated with the size. 7050 7051 History: 7052 Added November 11, 2022, but not yet implemented and may not be for some time. 7053 +/ 7054 /*@__future*/ allowResizingMaintainingAspectRatio, 7055 /++ 7056 If possible, your drawing buffer will remain the same size and simply be automatically scaled to the new window size, letterboxing if needed to keep the aspect ratio. If this is impossible, it will fallback to [fixedSize]. The simpledisplay library will always provide the illusion that your window is the same size you requested, even if it scales things for you, meaning [width] and [height] will never change. 7057 7058 History: 7059 Prior to November 11, 2022, width and height would change, which made this mode harder to use than intended. While I had documented this as a possiblity, I still considered it a bug, a leaky abstraction, and changed the code to tighten it up. After that date, the width and height members, as well as mouse coordinates, are always scaled to maintain the illusion of a fixed canvas size. 7060 7061 Your programs should not be affected, as they will continue to function as if the user simply never resized the window at all. 7062 +/ 7063 automaticallyScaleIfPossible, 7064 } 7065 7066 7067 /++ 7068 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7069 +/ 7070 enum TextAlignment : uint { 7071 Left = 0, /// 7072 Center = 1, /// 7073 Right = 2, /// 7074 7075 VerticalTop = 0, /// 7076 VerticalCenter = 4, /// 7077 VerticalBottom = 8, /// 7078 } 7079 7080 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7081 alias Rectangle = arsd.color.Rectangle; 7082 7083 7084 /++ 7085 Keyboard press and release events. 7086 +/ 7087 struct KeyEvent { 7088 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7089 Key key; 7090 ubyte hardwareCode; /// A platform and hardware specific code for the key 7091 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7092 7093 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7094 7095 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7096 7097 SimpleWindow window; /// associated Window 7098 7099 /++ 7100 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7101 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7102 to predict if char events are actually coming.. 7103 7104 Only available on X systems since this information is not given ahead of time elsewhere. 7105 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7106 7107 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7108 and potential quirks I'd recommend avoiding it. 7109 7110 History: 7111 Added April 26, 2021 (dub v9.5) 7112 +/ 7113 version(X11) 7114 dchar[] charsPossible; 7115 7116 // convert key event to simplified string representation a-la emacs 7117 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7118 uint dpos = 0; 7119 void put (const(char)[] s...) nothrow @trusted { 7120 static if (growdest) { 7121 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7122 } else { 7123 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7124 } 7125 } 7126 7127 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7128 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7129 } 7130 7131 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7132 7133 // put modifiers 7134 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7135 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7136 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7137 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7138 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7139 7140 if (this.key) { 7141 foreach (string kn; __traits(allMembers, Key)) { 7142 if (this.key == __traits(getMember, Key, kn)) { 7143 // HACK! 7144 static if (kn == "N0") put("0"); 7145 else static if (kn == "N1") put("1"); 7146 else static if (kn == "N2") put("2"); 7147 else static if (kn == "N3") put("3"); 7148 else static if (kn == "N4") put("4"); 7149 else static if (kn == "N5") put("5"); 7150 else static if (kn == "N6") put("6"); 7151 else static if (kn == "N7") put("7"); 7152 else static if (kn == "N8") put("8"); 7153 else static if (kn == "N9") put("9"); 7154 else put(kn); 7155 return dest[0..dpos]; 7156 } 7157 } 7158 put("Unknown"); 7159 } else { 7160 if (dpos && dest[dpos-1] == '+') --dpos; 7161 } 7162 return dest[0..dpos]; 7163 } 7164 7165 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7166 7167 /** Parse string into key name with modifiers. It accepts things like: 7168 * 7169 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7170 * 7171 * Ctrl+Win+1 -- windows style 7172 * 7173 * Ctrl-Win-1 -- '-' is a valid delimiter too 7174 * 7175 * Ctrl Win 1 -- and space 7176 * 7177 * and even "Win + 1 + Ctrl". 7178 */ 7179 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7180 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7181 7182 // remove trailing spaces 7183 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7184 7185 // tokens delimited by blank, '+', or '-' 7186 // null on eol 7187 const(char)[] getToken () nothrow @trusted @nogc { 7188 // remove leading spaces and delimiters 7189 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7190 if (name.length == 0) return null; // oops, no more tokens 7191 // get token 7192 size_t epos = 0; 7193 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7194 assert(epos > 0 && epos <= name.length); 7195 auto res = name[0..epos]; 7196 name = name[epos..$]; 7197 return res; 7198 } 7199 7200 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7201 if (s0.length != s1.length) return false; 7202 foreach (immutable ci, char c0; s0) { 7203 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7204 char c1 = s1[ci]; 7205 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7206 if (c0 != c1) return false; 7207 } 7208 return true; 7209 } 7210 7211 if (ignoreModsOut !is null) *ignoreModsOut = false; 7212 if (updown !is null) *updown = -1; 7213 KeyEvent res; 7214 res.key = cast(Key)0; // just in case 7215 const(char)[] tk, tkn; // last token 7216 bool allowEmascStyle = true; 7217 bool ignoreModifiers = false; 7218 tokenloop: for (;;) { 7219 tk = tkn; 7220 tkn = getToken(); 7221 //k8: yay, i took "Bloody Mess" trait from Fallout! 7222 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7223 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7224 if (allowEmascStyle && tkn.length != 0) { 7225 if (tk.length == 1) { 7226 char mdc = tk[0]; 7227 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7228 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7229 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7230 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7231 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7232 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7233 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7234 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7235 } 7236 } 7237 allowEmascStyle = false; 7238 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7239 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7240 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7241 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7242 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7243 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7244 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7245 if (tk.length == 0) continue; 7246 // try key name 7247 if (res.key == 0) { 7248 // little hack 7249 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7250 final switch (tk[0]) { 7251 case '0': tk = "N0"; break; 7252 case '1': tk = "N1"; break; 7253 case '2': tk = "N2"; break; 7254 case '3': tk = "N3"; break; 7255 case '4': tk = "N4"; break; 7256 case '5': tk = "N5"; break; 7257 case '6': tk = "N6"; break; 7258 case '7': tk = "N7"; break; 7259 case '8': tk = "N8"; break; 7260 case '9': tk = "N9"; break; 7261 } 7262 } 7263 foreach (string kn; __traits(allMembers, Key)) { 7264 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7265 } 7266 } 7267 // unknown or duplicate key name, get out of here 7268 break; 7269 } 7270 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7271 return res; // something 7272 } 7273 7274 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7275 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7276 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7277 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7278 } 7279 bool ignoreMods; 7280 int updown; 7281 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7282 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7283 if (this.key != ke.key) { 7284 // things like "ctrl+alt" are complicated 7285 uint tkm = this.modifierState&modmask; 7286 uint kkm = ke.modifierState&modmask; 7287 Key tk = this.key; 7288 // ke 7289 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7290 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7291 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7292 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7293 // this 7294 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7295 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7296 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7297 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7298 return (tk == ke.key && tkm == kkm); 7299 } 7300 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7301 } 7302 } 7303 7304 /// Sets the application name. 7305 @property string ApplicationName(string name) { 7306 return _applicationName = name; 7307 } 7308 7309 string _applicationName; 7310 7311 /// ditto 7312 @property string ApplicationName() { 7313 if(_applicationName is null) { 7314 import core.runtime; 7315 return Runtime.args[0]; 7316 } 7317 return _applicationName; 7318 } 7319 7320 7321 /// Type of a [MouseEvent]. 7322 enum MouseEventType : int { 7323 motion = 0, /// The mouse moved inside the window 7324 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7325 buttonReleased = 2, /// A mouse button was released 7326 } 7327 7328 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7329 /++ 7330 Listen for this on your event listeners if you are interested in mouse action. 7331 7332 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. 7333 7334 Examples: 7335 7336 This will draw boxes on the window with the mouse as you hold the left button. 7337 --- 7338 import arsd.simpledisplay; 7339 7340 void main() { 7341 auto window = new SimpleWindow(); 7342 7343 window.eventLoop(0, 7344 (MouseEvent ev) { 7345 if(ev.modifierState & ModifierState.leftButtonDown) { 7346 auto painter = window.draw(); 7347 painter.fillColor = Color.red; 7348 painter.outlineColor = Color.black; 7349 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7350 } 7351 } 7352 ); 7353 } 7354 --- 7355 +/ 7356 struct MouseEvent { 7357 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7358 7359 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. 7360 int y; /// Current Y position of the cursor when the event fired. 7361 7362 int dx; /// Change in X position since last report 7363 int dy; /// Change in Y position since last report 7364 7365 MouseButton button; /// See [MouseButton] 7366 int modifierState; /// See [ModifierState] 7367 7368 version(X11) 7369 private Time timestamp; 7370 7371 /// Returns a linear representation of mouse button, 7372 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7373 /// 7374 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7375 @property ubyte buttonLinear() const { 7376 import core.bitop; 7377 if(button == 0) 7378 return 0; 7379 return (bsf(button) + 1) & 0b1111; 7380 } 7381 7382 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7383 7384 SimpleWindow window; /// The window in which the event happened. 7385 7386 Point globalCoordinates() { 7387 Point p; 7388 if(window is null) 7389 throw new Exception("wtf"); 7390 static if(UsingSimpledisplayX11) { 7391 Window child; 7392 XTranslateCoordinates( 7393 XDisplayConnection.get, 7394 window.impl.window, 7395 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7396 x, y, &p.x, &p.y, &child); 7397 return p; 7398 } else version(Windows) { 7399 POINT[1] points; 7400 points[0].x = x; 7401 points[0].y = y; 7402 MapWindowPoints( 7403 window.impl.hwnd, 7404 null, 7405 points.ptr, 7406 points.length 7407 ); 7408 p.x = points[0].x; 7409 p.y = points[0].y; 7410 7411 return p; 7412 } else version(OSXCocoa) { 7413 throw new NotYetImplementedException(); 7414 } else static assert(0); 7415 } 7416 7417 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7418 7419 /** 7420 can contain emacs-like modifier prefix 7421 case-insensitive names: 7422 lmbX/leftX 7423 rmbX/rightX 7424 mmbX/middleX 7425 wheelX 7426 motion (no prefix allowed) 7427 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7428 */ 7429 static bool equStr() (scope auto ref const MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7430 if (str.length == 0) return false; // just in case 7431 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7432 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7433 auto anchor = str; 7434 uint mods = 0; // uint.max == any 7435 // interesting bits in kmod 7436 uint kmodmask = 7437 ModifierState.shift| 7438 ModifierState.ctrl| 7439 ModifierState.alt| 7440 ModifierState.windows| 7441 ModifierState.leftButtonDown| 7442 ModifierState.middleButtonDown| 7443 ModifierState.rightButtonDown| 7444 0; 7445 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7446 bool wasButtons = false; 7447 while (str.length) { 7448 if (str.ptr[0] <= ' ') { 7449 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7450 continue; 7451 } 7452 // one-letter modifier? 7453 if (str.length >= 2 && str.ptr[1] == '-') { 7454 switch (str.ptr[0]) { 7455 case '*': // "any" modifier (cannot be undone) 7456 mods = mods.max; 7457 break; 7458 case 'C': case 'c': // emacs "ctrl" 7459 if (mods != mods.max) mods |= ModifierState.ctrl; 7460 break; 7461 case 'M': case 'm': // emacs "meta" 7462 if (mods != mods.max) mods |= ModifierState.alt; 7463 break; 7464 case 'S': case 's': // emacs "shift" 7465 if (mods != mods.max) mods |= ModifierState.shift; 7466 break; 7467 case 'H': case 'h': // emacs "hyper" (aka winkey) 7468 if (mods != mods.max) mods |= ModifierState.windows; 7469 break; 7470 default: 7471 return false; // unknown modifier 7472 } 7473 str = str[2..$]; 7474 continue; 7475 } 7476 // word 7477 char[16] buf = void; // locased 7478 auto wep = 0; 7479 while (str.length) { 7480 immutable char ch = str.ptr[0]; 7481 if (ch <= ' ' || ch == '-') break; 7482 str = str[1..$]; 7483 if (wep > buf.length) return false; // too long 7484 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7485 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7486 else return false; // invalid char 7487 } 7488 if (wep == 0) return false; // just in case 7489 uint bnum; 7490 enum UpDown { None = -1, Up, Down, Any } 7491 auto updown = UpDown.None; // 0: up; 1: down 7492 switch (buf[0..wep]) { 7493 // left button 7494 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 7495 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 7496 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 7497 case "lmb": case "left": bnum = 0; break; 7498 // middle button 7499 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 7500 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 7501 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 7502 case "mmb": case "middle": bnum = 1; break; 7503 // right button 7504 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 7505 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 7506 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 7507 case "rmb": case "right": bnum = 2; break; 7508 // wheel 7509 case "wheelup": updown = UpDown.Up; goto case "wheel"; 7510 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 7511 case "wheelany": updown = UpDown.Any; goto case "wheel"; 7512 case "wheel": bnum = 3; break; 7513 // motion 7514 case "motion": bnum = 7; break; 7515 // unknown 7516 default: return false; 7517 } 7518 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7519 // parse possible "-up" or "-down" 7520 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 7521 wep = 0; 7522 foreach (immutable idx, immutable char ch; str[1..$]) { 7523 if (ch <= ' ' || ch == '-') break; 7524 assert(idx == wep); // for now; trick 7525 if (wep > buf.length) { wep = 0; break; } // too long 7526 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7527 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7528 else { wep = 0; break; } // invalid char 7529 } 7530 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 7531 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 7532 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 7533 // remove parsed part 7534 if (updown != UpDown.None) str = str[wep+1..$]; 7535 } 7536 if (updown == UpDown.None) { 7537 updown = UpDown.Down; 7538 } 7539 wasButtons = wasButtons || (bnum <= 2); 7540 //assert(updown != UpDown.None); 7541 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7542 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 7543 if (lastButt != lastButt.max) { 7544 if ((lastButt&0xff) >= 3) return false; // wheel or motion 7545 if (mods != mods.max) { 7546 uint butbit = 0; 7547 final switch (lastButt&0x03) { 7548 case 0: butbit = ModifierState.leftButtonDown; break; 7549 case 1: butbit = ModifierState.middleButtonDown; break; 7550 case 2: butbit = ModifierState.rightButtonDown; break; 7551 } 7552 if (lastButt&Flag.Down) mods |= butbit; 7553 else if (lastButt&Flag.Up) mods &= ~butbit; 7554 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 7555 } 7556 } 7557 // remember last button 7558 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 7559 } 7560 // no button -- nothing to do 7561 if (lastButt == lastButt.max) return false; 7562 // done parsing, check if something's left 7563 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 7564 // remove action button from mask 7565 if ((lastButt&0xff) < 3) { 7566 final switch (lastButt&0x03) { 7567 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 7568 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 7569 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 7570 } 7571 } 7572 // special case: "Motion" means "ignore buttons" 7573 if ((lastButt&0xff) == 7 && !wasButtons) { 7574 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 7575 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 7576 } 7577 uint kmod = event.modifierState&kmodmask; 7578 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 7579 // check modifier state 7580 if (mods != mods.max) { 7581 if (kmod != mods) return false; 7582 } 7583 // now check type 7584 if ((lastButt&0xff) == 7) { 7585 // motion 7586 if (event.type != MouseEventType.motion) return false; 7587 } else if ((lastButt&0xff) == 3) { 7588 // wheel 7589 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 7590 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 7591 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 7592 return false; 7593 } else { 7594 // buttons 7595 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 7596 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 7597 { 7598 return false; 7599 } 7600 // button number 7601 switch (lastButt&0x03) { 7602 case 0: if (event.button != MouseButton.left) return false; break; 7603 case 1: if (event.button != MouseButton.middle) return false; break; 7604 case 2: if (event.button != MouseButton.right) return false; break; 7605 default: return false; 7606 } 7607 } 7608 return true; 7609 } 7610 } 7611 7612 version(arsd_mevent_strcmp_test) unittest { 7613 MouseEvent event; 7614 event.type = MouseEventType.buttonPressed; 7615 event.button = MouseButton.left; 7616 event.modifierState = ModifierState.ctrl; 7617 assert(event == "C-LMB"); 7618 assert(event != "C-LMBUP"); 7619 assert(event != "C-LMB-UP"); 7620 assert(event != "C-S-LMB"); 7621 assert(event == "*-LMB"); 7622 assert(event != "*-LMB-UP"); 7623 7624 event.type = MouseEventType.buttonReleased; 7625 assert(event != "C-LMB"); 7626 assert(event == "C-LMBUP"); 7627 assert(event == "C-LMB-UP"); 7628 assert(event != "C-S-LMB"); 7629 assert(event != "*-LMB"); 7630 assert(event == "*-LMB-UP"); 7631 7632 event.button = MouseButton.right; 7633 event.modifierState |= ModifierState.shift; 7634 event.type = MouseEventType.buttonPressed; 7635 assert(event != "C-LMB"); 7636 assert(event != "C-LMBUP"); 7637 assert(event != "C-LMB-UP"); 7638 assert(event != "C-S-LMB"); 7639 assert(event != "*-LMB"); 7640 assert(event != "*-LMB-UP"); 7641 7642 assert(event != "C-RMB"); 7643 assert(event != "C-RMBUP"); 7644 assert(event != "C-RMB-UP"); 7645 assert(event == "C-S-RMB"); 7646 assert(event == "*-RMB"); 7647 assert(event != "*-RMB-UP"); 7648 } 7649 7650 /// This gives a few more options to drawing lines and such 7651 struct Pen { 7652 Color color; /// the foreground color 7653 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. 7654 Style style; /// See [Style] 7655 /+ 7656 // From X.h 7657 7658 #define LineSolid 0 7659 #define LineOnOffDash 1 7660 #define LineDoubleDash 2 7661 LineDou- The full path of the line is drawn, but the 7662 bleDash even dashes are filled differently from the 7663 odd dashes (see fill-style) with CapButt 7664 style used where even and odd dashes meet. 7665 7666 7667 7668 /* capStyle */ 7669 7670 #define CapNotLast 0 7671 #define CapButt 1 7672 #define CapRound 2 7673 #define CapProjecting 3 7674 7675 /* joinStyle */ 7676 7677 #define JoinMiter 0 7678 #define JoinRound 1 7679 #define JoinBevel 2 7680 7681 /* fillStyle */ 7682 7683 #define FillSolid 0 7684 #define FillTiled 1 7685 #define FillStippled 2 7686 #define FillOpaqueStippled 3 7687 7688 7689 +/ 7690 /// Style of lines drawn 7691 enum Style { 7692 Solid, /// a solid line 7693 Dashed, /// a dashed line 7694 Dotted, /// a dotted line 7695 } 7696 } 7697 7698 7699 /++ 7700 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 7701 7702 7703 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 7704 7705 $(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.) 7706 7707 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. 7708 7709 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 7710 7711 $(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. 7712 7713 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! 7714 7715 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!) 7716 7717 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 7718 7719 --- 7720 auto image = new Image(256, 256); 7721 scope(exit) destroy(image); 7722 --- 7723 7724 As long as you don't hold on to it outside the scope. 7725 7726 I might change it to be an owned pointer at some point in the future. 7727 7728 ) 7729 7730 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7731 you can also often get a fair amount of speedup by getting the raw data format and 7732 writing some custom code. 7733 7734 FIXME INSERT EXAMPLES HERE 7735 7736 7737 +/ 7738 final class Image { 7739 /// 7740 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7741 this.width = width; 7742 this.height = height; 7743 this.enableAlpha = enableAlpha; 7744 7745 impl.createImage(width, height, forcexshm, enableAlpha); 7746 } 7747 7748 /// 7749 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7750 this(size.width, size.height, forcexshm, enableAlpha); 7751 } 7752 7753 private bool suppressDestruction; 7754 7755 version(X11) 7756 this(XImage* handle) { 7757 this.handle = handle; 7758 this.rawData = cast(ubyte*) handle.data; 7759 this.width = handle.width; 7760 this.height = handle.height; 7761 this.enableAlpha = handle.depth == 32; 7762 suppressDestruction = true; 7763 } 7764 7765 ~this() { 7766 if(suppressDestruction) return; 7767 impl.dispose(); 7768 } 7769 7770 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7771 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7772 pure const @system nothrow { 7773 /* 7774 To use these to draw a blue rectangle with size WxH at position X,Y... 7775 7776 // make certain that it will fit before we proceed 7777 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! 7778 7779 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7780 // (though calculating them isn't really that expensive). 7781 auto nextLineAdjustment = img.adjustmentForNextLine(); 7782 auto offR = img.redByteOffset(); 7783 auto offB = img.blueByteOffset(); 7784 auto offG = img.greenByteOffset(); 7785 auto bpp = img.bytesPerPixel(); 7786 7787 auto data = img.getDataPointer(); 7788 7789 // figure out the starting byte offset 7790 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7791 7792 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7793 7794 // and now our drawing loop for the rectangle 7795 foreach(y; 0 .. H) { 7796 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7797 foreach(x; 0 .. W) { 7798 // write our color 7799 data[offR] = 0; 7800 data[offG] = 0; 7801 data[offB] = 255; 7802 7803 data += bpp; // moving to the next pixel is just an addition... 7804 } 7805 startOfLine += nextLineAdjustment; 7806 } 7807 7808 7809 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7810 7811 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7812 can be made into a bitmask or something so we can write them as *uint... 7813 */ 7814 7815 /// 7816 int offsetForTopLeftPixel() { 7817 version(X11) { 7818 return 0; 7819 } else version(Windows) { 7820 if(enableAlpha) { 7821 return (width * 4) * (height - 1); 7822 } else { 7823 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7824 } 7825 } else version(OSXCocoa) { 7826 return 0 ; //throw new NotYetImplementedException(); 7827 } else static assert(0, "fill in this info for other OSes"); 7828 } 7829 7830 /// 7831 int offsetForPixel(int x, int y) { 7832 version(X11) { 7833 auto offset = (y * width + x) * 4; 7834 return offset; 7835 } else version(Windows) { 7836 if(enableAlpha) { 7837 auto itemsPerLine = width * 4; 7838 // remember, bmps are upside down 7839 auto offset = itemsPerLine * (height - y - 1) + x * 4; 7840 return offset; 7841 } else { 7842 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 7843 // remember, bmps are upside down 7844 auto offset = itemsPerLine * (height - y - 1) + x * 3; 7845 return offset; 7846 } 7847 } else version(OSXCocoa) { 7848 return 0 ; //throw new NotYetImplementedException(); 7849 } else static assert(0, "fill in this info for other OSes"); 7850 } 7851 7852 /// 7853 int adjustmentForNextLine() { 7854 version(X11) { 7855 return width * 4; 7856 } else version(Windows) { 7857 // windows bmps are upside down, so the adjustment is actually negative 7858 if(enableAlpha) 7859 return - (cast(int) width * 4); 7860 else 7861 return -((cast(int) width * 3 + 3) / 4) * 4; 7862 } else version(OSXCocoa) { 7863 return 0 ; //throw new NotYetImplementedException(); 7864 } else static assert(0, "fill in this info for other OSes"); 7865 } 7866 7867 /// once you have the position of a pixel, use these to get to the proper color 7868 int redByteOffset() { 7869 version(X11) { 7870 return 2; 7871 } else version(Windows) { 7872 return 2; 7873 } else version(OSXCocoa) { 7874 return 0 ; //throw new NotYetImplementedException(); 7875 } else static assert(0, "fill in this info for other OSes"); 7876 } 7877 7878 /// 7879 int greenByteOffset() { 7880 version(X11) { 7881 return 1; 7882 } else version(Windows) { 7883 return 1; 7884 } else version(OSXCocoa) { 7885 return 0 ; //throw new NotYetImplementedException(); 7886 } else static assert(0, "fill in this info for other OSes"); 7887 } 7888 7889 /// 7890 int blueByteOffset() { 7891 version(X11) { 7892 return 0; 7893 } else version(Windows) { 7894 return 0; 7895 } else version(OSXCocoa) { 7896 return 0 ; //throw new NotYetImplementedException(); 7897 } else static assert(0, "fill in this info for other OSes"); 7898 } 7899 7900 /// Only valid if [enableAlpha] is true 7901 int alphaByteOffset() { 7902 version(X11) { 7903 return 3; 7904 } else version(Windows) { 7905 return 3; 7906 } else version(OSXCocoa) { 7907 return 3; //throw new NotYetImplementedException(); 7908 } else static assert(0, "fill in this info for other OSes"); 7909 } 7910 } 7911 7912 /// 7913 final void putPixel(int x, int y, Color c) { 7914 if(x < 0 || x >= width) 7915 return; 7916 if(y < 0 || y >= height) 7917 return; 7918 7919 impl.setPixel(x, y, c); 7920 } 7921 7922 /// 7923 final Color getPixel(int x, int y) { 7924 if(x < 0 || x >= width) 7925 return Color.transparent; 7926 if(y < 0 || y >= height) 7927 return Color.transparent; 7928 7929 version(OSXCocoa) throw new NotYetImplementedException(); else 7930 return impl.getPixel(x, y); 7931 } 7932 7933 /// 7934 final void opIndexAssign(Color c, int x, int y) { 7935 putPixel(x, y, c); 7936 } 7937 7938 /// 7939 TrueColorImage toTrueColorImage() { 7940 auto tci = new TrueColorImage(width, height); 7941 convertToRgbaBytes(tci.imageData.bytes); 7942 return tci; 7943 } 7944 7945 /// 7946 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) { 7947 auto tci = i.getAsTrueColorImage(); 7948 auto img = new Image(tci.width, tci.height, false, enableAlpha); 7949 img.setRgbaBytes(tci.imageData.bytes); 7950 return img; 7951 } 7952 7953 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 7954 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 7955 /// if you pass null, it will allocate a new one. 7956 ubyte[] getRgbaBytes(ubyte[] where = null) { 7957 if(where is null) 7958 where = new ubyte[this.width*this.height*4]; 7959 convertToRgbaBytes(where); 7960 return where; 7961 } 7962 7963 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 7964 void setRgbaBytes(in ubyte[] from ) { 7965 assert(from.length == this.width * this.height * 4); 7966 setFromRgbaBytes(from); 7967 } 7968 7969 // FIXME: make properly cross platform by getting rgba right 7970 7971 /// warning: this is not portable across platforms because the data format can change 7972 ubyte* getDataPointer() { 7973 return impl.rawData; 7974 } 7975 7976 /// for use with getDataPointer 7977 final int bytesPerLine() const pure @safe nothrow { 7978 version(Windows) 7979 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 7980 else version(X11) 7981 return 4 * width; 7982 else version(OSXCocoa) 7983 return 4 * width; 7984 else static assert(0); 7985 } 7986 7987 /// for use with getDataPointer 7988 final int bytesPerPixel() const pure @safe nothrow { 7989 version(Windows) 7990 return enableAlpha ? 4 : 3; 7991 else version(X11) 7992 return 4; 7993 else version(OSXCocoa) 7994 return 4; 7995 else static assert(0); 7996 } 7997 7998 /// 7999 immutable int width; 8000 8001 /// 8002 immutable int height; 8003 8004 /// 8005 immutable bool enableAlpha; 8006 //private: 8007 mixin NativeImageImplementation!() impl; 8008 } 8009 8010 /++ 8011 A convenience function to pop up a window displaying the image. 8012 If you pass a win, it will draw the image in it. Otherwise, it will 8013 create a window with the size of the image and run its event loop, closing 8014 when a key is pressed. 8015 8016 History: 8017 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8018 always block until the application quit which could cause bizarre behavior 8019 inside a more complex application. Now, the default is to block until 8020 this window closes if it is the only event loop running, and otherwise, 8021 not to block at all and just pop up the display window asynchronously. 8022 +/ 8023 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8024 if(win is null) { 8025 win = new SimpleWindow(image); 8026 { 8027 auto p = win.draw; 8028 p.drawImage(Point(0, 0), image); 8029 } 8030 win.eventLoopWithBlockingMode( 8031 bm, 0, 8032 (KeyEvent ev) { 8033 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8034 } ); 8035 } else { 8036 win.image = image; 8037 } 8038 } 8039 8040 enum FontWeight : int { 8041 dontcare = 0, 8042 thin = 100, 8043 extralight = 200, 8044 light = 300, 8045 regular = 400, 8046 medium = 500, 8047 semibold = 600, 8048 bold = 700, 8049 extrabold = 800, 8050 heavy = 900 8051 } 8052 8053 /++ 8054 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8055 8056 History: 8057 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8058 +/ 8059 interface MeasurableFont { 8060 /++ 8061 Returns true if it is a monospace font, meaning each of the 8062 glyphs (at least the ascii characters) have matching width 8063 and no kerning, so you can determine the display width of some 8064 strings by simply multiplying the string width by [averageWidth]. 8065 8066 (Please note that multiply doesn't $(I actually) work in general, 8067 consider characters like tab and newline, but it does sometimes.) 8068 +/ 8069 bool isMonospace(); 8070 8071 /++ 8072 The average width of glyphs in the font, traditionally equal to the 8073 width of the lowercase x. Can be used to estimate bounding boxes, 8074 especially if the font [isMonospace]. 8075 8076 Given in pixels. 8077 +/ 8078 int averageWidth(); 8079 /++ 8080 The height of the bounding box of a line. 8081 +/ 8082 int height(); 8083 /++ 8084 The maximum ascent of a glyph above the baseline. 8085 8086 Given in pixels. 8087 +/ 8088 int ascent(); 8089 /++ 8090 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8091 8092 Given in pixels. 8093 +/ 8094 int descent(); 8095 /++ 8096 The display width of the given string, and if you provide a window, it will use it to 8097 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8098 8099 Given in pixels. 8100 +/ 8101 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8102 8103 } 8104 8105 // FIXME: i need a font cache and it needs to handle disconnects. 8106 8107 /++ 8108 Represents a font loaded off the operating system or the X server. 8109 8110 8111 While the api here is unified cross platform, the fonts are not necessarily 8112 available, even across machines of the same platform, so be sure to always check 8113 for null (using [isNull]) and have a fallback plan. 8114 8115 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8116 8117 Worst case, a null font will automatically fall back to the default font loaded 8118 for your system. 8119 +/ 8120 class OperatingSystemFont : MeasurableFont { 8121 // FIXME: when the X Connection is lost, these need to be invalidated! 8122 // that means I need to store the original stuff again to reconstruct it too. 8123 8124 version(X11) { 8125 XFontStruct* font; 8126 XFontSet fontset; 8127 8128 version(with_xft) { 8129 XftFont* xftFont; 8130 bool isXft; 8131 } 8132 } else version(Windows) { 8133 HFONT font; 8134 int width_; 8135 int height_; 8136 } else version(OSXCocoa) { 8137 // FIXME 8138 } else static assert(0); 8139 8140 /++ 8141 Constructs the class and immediately calls [load]. 8142 +/ 8143 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8144 load(name, size, weight, italic); 8145 } 8146 8147 /++ 8148 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8149 8150 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8151 8152 History: 8153 Added January 24, 2021. 8154 +/ 8155 this() { 8156 // this space intentionally left blank 8157 } 8158 8159 /++ 8160 Constructs a copy of the given font object. 8161 8162 History: 8163 Added January 7, 2023. 8164 +/ 8165 this(OperatingSystemFont font) { 8166 if(font is null || font.loadedInfo is LoadedInfo.init) 8167 loadDefault(); 8168 else 8169 load(font.loadedInfo.tupleof); 8170 } 8171 8172 /++ 8173 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8174 8175 History: 8176 Added November 13, 2020. 8177 +/ 8178 version(with_xft) 8179 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8180 unload(); 8181 8182 if(!XftLibrary.attempted) { 8183 XftLibrary.loadDynamicLibrary(); 8184 } 8185 8186 if(!XftLibrary.loadSuccessful) 8187 return false; 8188 8189 auto display = XDisplayConnection.get; 8190 8191 char[256] nameBuffer = void; 8192 int nbp = 0; 8193 8194 void add(in char[] a) { 8195 nameBuffer[nbp .. nbp + a.length] = a[]; 8196 nbp += a.length; 8197 } 8198 add(name); 8199 8200 if(size) { 8201 add(":size="); 8202 add(toInternal!string(size)); 8203 } 8204 if(weight != FontWeight.dontcare) { 8205 add(":weight="); 8206 add(weightToString(weight)); 8207 } 8208 if(italic) 8209 add(":slant=100"); 8210 8211 nameBuffer[nbp] = 0; 8212 8213 this.xftFont = XftFontOpenName( 8214 display, 8215 DefaultScreen(display), 8216 nameBuffer.ptr 8217 ); 8218 8219 this.isXft = true; 8220 8221 if(xftFont !is null) { 8222 isMonospace_ = stringWidth("x") == stringWidth("M"); 8223 ascent_ = xftFont.ascent; 8224 descent_ = xftFont.descent; 8225 } 8226 8227 return !isNull(); 8228 } 8229 8230 /++ 8231 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8232 8233 8234 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. 8235 8236 If `pattern` is null, it returns all available font families. 8237 8238 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. 8239 8240 The format of the pattern is platform-specific. 8241 8242 History: 8243 Added May 1, 2021 (dub v9.5) 8244 +/ 8245 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8246 version(Windows) { 8247 auto hdc = GetDC(null); 8248 scope(exit) ReleaseDC(null, hdc); 8249 LOGFONT logfont; 8250 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8251 auto localHandler = *(cast(typeof(handler)*) p); 8252 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8253 } 8254 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8255 } else version(X11) { 8256 //import core.stdc.stdio; 8257 bool done = false; 8258 version(with_xft) { 8259 if(!XftLibrary.attempted) { 8260 XftLibrary.loadDynamicLibrary(); 8261 } 8262 8263 if(!XftLibrary.loadSuccessful) 8264 goto skipXft; 8265 8266 if(!FontConfigLibrary.attempted) 8267 FontConfigLibrary.loadDynamicLibrary(); 8268 if(!FontConfigLibrary.loadSuccessful) 8269 goto skipXft; 8270 8271 { 8272 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8273 if(got is null) 8274 goto skipXft; 8275 scope(exit) FcFontSetDestroy(got); 8276 8277 auto fontPatterns = got.fonts[0 .. got.nfont]; 8278 foreach(candidate; fontPatterns) { 8279 char* where, whereStyle; 8280 8281 char* pmg = FcNameUnparse(candidate); 8282 8283 //FcPatternGetString(candidate, "family", 0, &where); 8284 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8285 //if(where && whereStyle) { 8286 if(pmg) { 8287 if(!handler(pmg.sliceCString)) 8288 return; 8289 //printf("%s || %s %s\n", pmg, where, whereStyle); 8290 } 8291 } 8292 } 8293 } 8294 8295 skipXft: 8296 8297 if(pattern is null) 8298 pattern = "*"; 8299 8300 int count; 8301 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8302 scope(exit) XFreeFontNames(coreFontsRaw); 8303 8304 auto coreFonts = coreFontsRaw[0 .. count]; 8305 8306 foreach(font; coreFonts) { 8307 char[128] tmp; 8308 tmp[0 ..5] = "core:"; 8309 auto cf = font.sliceCString; 8310 if(5 + cf.length > tmp.length) 8311 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8312 tmp[5 .. 5 + cf.length] = cf; 8313 if(!handler(tmp[0 .. 5 + cf.length])) 8314 return; 8315 } 8316 } 8317 } 8318 8319 /++ 8320 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8321 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8322 8323 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8324 underlying system doesn't support returning the raw bytes. 8325 8326 History: 8327 Added September 10, 2021 (dub v10.3) 8328 +/ 8329 ubyte[] getTtfBytes() { 8330 if(isNull) 8331 return null; 8332 8333 version(Windows) { 8334 auto dc = GetDC(null); 8335 auto orig = SelectObject(dc, font); 8336 8337 scope(exit) { 8338 SelectObject(dc, orig); 8339 ReleaseDC(null, dc); 8340 } 8341 8342 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8343 if(res == GDI_ERROR) 8344 return null; 8345 8346 ubyte[] buffer = new ubyte[](res); 8347 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8348 if(res == GDI_ERROR) 8349 return null; // wtf really tbh 8350 8351 return buffer; 8352 } else version(with_xft) { 8353 if(isXft && xftFont) { 8354 if(!FontConfigLibrary.attempted) 8355 FontConfigLibrary.loadDynamicLibrary(); 8356 if(!FontConfigLibrary.loadSuccessful) 8357 return null; 8358 8359 char* file; 8360 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8361 if (file !is null && file[0]) { 8362 import core.stdc.stdio; 8363 auto fp = fopen(file, "rb"); 8364 if(fp is null) 8365 return null; 8366 scope(exit) 8367 fclose(fp); 8368 fseek(fp, 0, SEEK_END); 8369 ubyte[] buffer = new ubyte[](ftell(fp)); 8370 fseek(fp, 0, SEEK_SET); 8371 8372 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8373 if(got != buffer.length) 8374 return null; 8375 8376 return buffer; 8377 } 8378 } 8379 } 8380 return null; 8381 } 8382 } 8383 8384 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8385 8386 private string weightToString(FontWeight weight) { 8387 with(FontWeight) 8388 final switch(weight) { 8389 case dontcare: return "*"; 8390 case thin: return "extralight"; 8391 case extralight: return "extralight"; 8392 case light: return "light"; 8393 case regular: return "regular"; 8394 case medium: return "medium"; 8395 case semibold: return "demibold"; 8396 case bold: return "bold"; 8397 case extrabold: return "demibold"; 8398 case heavy: return "black"; 8399 } 8400 } 8401 8402 /++ 8403 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 8404 8405 History: 8406 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8407 +/ 8408 version(X11) 8409 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8410 unload(); 8411 8412 string xfontstr; 8413 8414 if(name.length > 3 && name[0 .. 3] == "-*-") { 8415 // this is kinda a disgusting hack but if the user sends an exact 8416 // string I'd like to honor it... 8417 xfontstr = name; 8418 } else { 8419 string weightstr = weightToString(weight); 8420 string sizestr; 8421 if(size == 0) 8422 sizestr = "*"; 8423 else 8424 sizestr = toInternal!string(size); 8425 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 8426 } 8427 8428 //import std.stdio; writeln(xfontstr); 8429 8430 auto display = XDisplayConnection.get; 8431 8432 font = XLoadQueryFont(display, xfontstr.ptr); 8433 if(font is null) 8434 return false; 8435 8436 char** lol; 8437 int lol2; 8438 char* lol3; 8439 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8440 8441 prepareFontInfo(); 8442 8443 return !isNull(); 8444 } 8445 8446 version(X11) 8447 private void prepareFontInfo() { 8448 if(font !is null) { 8449 isMonospace_ = stringWidth("l") == stringWidth("M"); 8450 ascent_ = font.max_bounds.ascent; 8451 descent_ = font.max_bounds.descent; 8452 } 8453 } 8454 8455 /++ 8456 Loads a Windows font. You probably want to use [load] instead to be more generic. 8457 8458 History: 8459 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8460 +/ 8461 version(Windows) 8462 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8463 unload(); 8464 8465 WCharzBuffer buffer = WCharzBuffer(name); 8466 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8467 8468 prepareFontInfo(hdc); 8469 8470 return !isNull(); 8471 } 8472 8473 version(Windows) 8474 void prepareFontInfo(HDC hdc = null) { 8475 if(font is null) 8476 return; 8477 8478 TEXTMETRIC tm; 8479 auto dc = hdc ? hdc : GetDC(null); 8480 auto orig = SelectObject(dc, font); 8481 GetTextMetrics(dc, &tm); 8482 SelectObject(dc, orig); 8483 if(hdc is null) 8484 ReleaseDC(null, dc); 8485 8486 width_ = tm.tmAveCharWidth; 8487 height_ = tm.tmHeight; 8488 ascent_ = tm.tmAscent; 8489 descent_ = tm.tmDescent; 8490 // 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. 8491 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 8492 } 8493 8494 8495 /++ 8496 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 8497 8498 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 8499 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 8500 8501 On Windows, it forwards directly to [loadWin32]. 8502 8503 Params: 8504 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 8505 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. 8506 weight = approximate boldness, results may vary. 8507 italic = try to get a slanted version of the given font. 8508 8509 History: 8510 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. 8511 +/ 8512 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8513 this.loadedInfo = LoadedInfo(name, size, weight, italic); 8514 version(X11) { 8515 version(with_xft) { 8516 if(name.length > 5 && name[0 .. 5] == "core:") { 8517 goto core; 8518 } 8519 8520 if(loadXft(name, size, weight, italic)) 8521 return true; 8522 // if xft fails, fallback to core to avoid breaking 8523 // code that already depended on this. 8524 } 8525 8526 core: 8527 8528 if(name.length > 5 && name[0 .. 5] == "core:") { 8529 name = name[5 .. $]; 8530 } 8531 8532 return loadCoreX(name, size, weight, italic); 8533 } else version(Windows) { 8534 return loadWin32(name, size, weight, italic); 8535 } else version(OSXCocoa) { 8536 // FIXME 8537 return false; 8538 } else static assert(0); 8539 } 8540 8541 private struct LoadedInfo { 8542 string name; 8543 int size; 8544 FontWeight weight; 8545 bool italic; 8546 } 8547 private LoadedInfo loadedInfo; 8548 8549 /// 8550 void unload() { 8551 if(isNull()) 8552 return; 8553 8554 version(X11) { 8555 auto display = XDisplayConnection.display; 8556 8557 if(display is null) 8558 return; 8559 8560 version(with_xft) { 8561 if(isXft) { 8562 if(xftFont) 8563 XftFontClose(display, xftFont); 8564 isXft = false; 8565 xftFont = null; 8566 return; 8567 } 8568 } 8569 8570 if(font && font !is ScreenPainterImplementation.defaultfont) 8571 XFreeFont(display, font); 8572 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 8573 XFreeFontSet(display, fontset); 8574 8575 font = null; 8576 fontset = null; 8577 } else version(Windows) { 8578 DeleteObject(font); 8579 font = null; 8580 } else version(OSXCocoa) { 8581 // FIXME 8582 } else static assert(0); 8583 } 8584 8585 private bool isMonospace_; 8586 8587 /++ 8588 History: 8589 Added January 16, 2021 8590 +/ 8591 bool isMonospace() { 8592 return isMonospace_; 8593 } 8594 8595 /++ 8596 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 8597 8598 History: 8599 Added March 26, 2020 8600 Documented January 16, 2021 8601 +/ 8602 int averageWidth() { 8603 version(X11) { 8604 return stringWidth("x"); 8605 } else version(Windows) 8606 return width_; 8607 else assert(0); 8608 } 8609 8610 /++ 8611 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 8612 8613 History: 8614 Added January 16, 2021 8615 +/ 8616 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 8617 // FIXME: what about tab? 8618 if(isNull) 8619 return 0; 8620 8621 version(X11) { 8622 version(with_xft) 8623 if(isXft && xftFont !is null) { 8624 //return xftFont.max_advance_width; 8625 XGlyphInfo extents; 8626 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 8627 //import std.stdio; writeln(extents); 8628 return extents.xOff; 8629 } 8630 if(font is null) 8631 return 0; 8632 else if(fontset) { 8633 XRectangle rect; 8634 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 8635 8636 return rect.width; 8637 } else { 8638 return XTextWidth(font, s.ptr, cast(int) s.length); 8639 } 8640 } else version(Windows) { 8641 WCharzBuffer buffer = WCharzBuffer(s); 8642 8643 return stringWidth(buffer.slice, window); 8644 } 8645 else assert(0); 8646 } 8647 8648 version(Windows) 8649 /// ditto 8650 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 8651 if(isNull) 8652 return 0; 8653 version(Windows) { 8654 SIZE size; 8655 8656 prepareContext(window); 8657 scope(exit) releaseContext(); 8658 8659 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 8660 8661 return size.cx; 8662 } else { 8663 // std.conv can do this easily but it is slow to import and i don't think it is worth it 8664 static assert(0, "not implemented yet"); 8665 //return stringWidth(s, window); 8666 } 8667 } 8668 8669 private { 8670 int prepRefcount; 8671 8672 version(Windows) { 8673 HDC dc; 8674 HANDLE orig; 8675 HWND hwnd; 8676 } 8677 } 8678 /++ 8679 [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. 8680 8681 History: 8682 Added January 23, 2021 8683 +/ 8684 void prepareContext(SimpleWindow window = null) { 8685 prepRefcount++; 8686 if(prepRefcount == 1) { 8687 version(Windows) { 8688 hwnd = window is null ? null : window.impl.hwnd; 8689 dc = GetDC(hwnd); 8690 orig = SelectObject(dc, font); 8691 } 8692 } 8693 } 8694 /// ditto 8695 void releaseContext() { 8696 prepRefcount--; 8697 if(prepRefcount == 0) { 8698 version(Windows) { 8699 SelectObject(dc, orig); 8700 ReleaseDC(hwnd, dc); 8701 hwnd = null; 8702 dc = null; 8703 orig = null; 8704 } 8705 } 8706 } 8707 8708 /+ 8709 FIXME: I think I need advance and kerning pair 8710 8711 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 8712 +/ 8713 8714 /++ 8715 Returns the height of the font. 8716 8717 History: 8718 Added March 26, 2020 8719 Documented January 16, 2021 8720 +/ 8721 int height() { 8722 version(X11) { 8723 version(with_xft) 8724 if(isXft && xftFont !is null) { 8725 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 8726 } 8727 if(font is null) 8728 return 0; 8729 return font.max_bounds.ascent + font.max_bounds.descent; 8730 } else version(Windows) 8731 return height_; 8732 else assert(0); 8733 } 8734 8735 private int ascent_; 8736 private int descent_; 8737 8738 /++ 8739 Max ascent above the baseline. 8740 8741 History: 8742 Added January 22, 2021 8743 +/ 8744 int ascent() { 8745 return ascent_; 8746 } 8747 8748 /++ 8749 Max descent below the baseline. 8750 8751 History: 8752 Added January 22, 2021 8753 +/ 8754 int descent() { 8755 return descent_; 8756 } 8757 8758 /++ 8759 Loads the default font used by [ScreenPainter] if none others are loaded. 8760 8761 Returns: 8762 This method mutates the `this` object, but then returns `this` for 8763 easy chaining like: 8764 8765 --- 8766 auto font = foo.isNull ? foo : foo.loadDefault 8767 --- 8768 8769 History: 8770 Added previously, but left unimplemented until January 24, 2021. 8771 +/ 8772 OperatingSystemFont loadDefault() { 8773 unload(); 8774 8775 loadedInfo = LoadedInfo.init; 8776 8777 version(X11) { 8778 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 8779 // but meh since sdpy does its own thing, this should be ok too 8780 8781 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8782 this.font = ScreenPainterImplementation.defaultfont; 8783 this.fontset = ScreenPainterImplementation.defaultfontset; 8784 8785 prepareFontInfo(); 8786 } else version(Windows) { 8787 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8788 this.font = ScreenPainterImplementation.defaultGuiFont; 8789 8790 prepareFontInfo(); 8791 } else throw new NotYetImplementedException(); 8792 8793 return this; 8794 } 8795 8796 /// 8797 bool isNull() { 8798 version(OSXCocoa) throw new NotYetImplementedException(); else { 8799 version(with_xft) 8800 if(isXft) 8801 return xftFont is null; 8802 return font is null; 8803 } 8804 } 8805 8806 /* Metrics */ 8807 /+ 8808 GetABCWidth 8809 GetKerningPairs 8810 8811 if I do it right, I can size it all here, and match 8812 what happens when I draw the full string with the OS functions. 8813 8814 subclasses might do the same thing while getting the glyphs on images 8815 struct GlyphInfo { 8816 int glyph; 8817 8818 size_t stringIdxStart; 8819 size_t stringIdxEnd; 8820 8821 Rectangle boundingBox; 8822 } 8823 GlyphInfo[] getCharBoxes() { 8824 // XftTextExtentsUtf8 8825 return null; 8826 8827 } 8828 +/ 8829 8830 ~this() { 8831 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 8832 unload(); 8833 } 8834 } 8835 8836 version(Windows) 8837 private string sliceCString(const(wchar)[] w) { 8838 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 8839 } 8840 8841 private inout(char)[] sliceCString(inout(char)* s) { 8842 import core.stdc.string; 8843 auto len = strlen(s); 8844 return s[0 .. len]; 8845 } 8846 8847 /** 8848 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 8849 than constructing it directly. Then, it is reference counted so you can pass it 8850 at around and when the last ref goes out of scope, the buffered drawing activities 8851 are all carried out. 8852 8853 8854 Most functions use the outlineColor instead of taking a color themselves. 8855 ScreenPainter is reference counted and draws its buffer to the screen when its 8856 final reference goes out of scope. 8857 */ 8858 struct ScreenPainter { 8859 CapableOfBeingDrawnUpon window; 8860 this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle, bool manualInvalidations) { 8861 this.window = window; 8862 if(window.closed) 8863 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 8864 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 8865 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 8866 if(window.activeScreenPainter !is null) { 8867 impl = window.activeScreenPainter; 8868 if(impl.referenceCount == 0) { 8869 impl.window = window; 8870 impl.create(handle); 8871 } 8872 impl.manualInvalidations = manualInvalidations; 8873 impl.referenceCount++; 8874 // writeln("refcount ++ ", impl.referenceCount); 8875 } else { 8876 impl = new ScreenPainterImplementation; 8877 impl.window = window; 8878 impl.create(handle); 8879 impl.referenceCount = 1; 8880 impl.manualInvalidations = manualInvalidations; 8881 window.activeScreenPainter = impl; 8882 //import std.stdio; writeln("constructed"); 8883 } 8884 8885 copyActiveOriginals(); 8886 } 8887 8888 /++ 8889 EXPERIMENTAL. subject to change. 8890 8891 When you draw a cursor, you can draw this to notify your window of where it is, 8892 for IME systems to use. 8893 +/ 8894 void notifyCursorPosition(int x, int y, int width, int height) { 8895 if(auto w = cast(SimpleWindow) window) { 8896 w.setIMEPopupLocation(x + _originX + width, y + _originY + height); 8897 } 8898 } 8899 8900 /++ 8901 If you are using manual invalidations, this informs the 8902 window system that a section needs to be redrawn. 8903 8904 If you didn't opt into manual invalidation, you don't 8905 have to call this. 8906 8907 History: 8908 Added December 30, 2021 (dub v10.5) 8909 +/ 8910 void invalidateRect(Rectangle rect) { 8911 if(impl is null) return; 8912 8913 // transform(rect) 8914 rect.left += _originX; 8915 rect.right += _originX; 8916 rect.top += _originY; 8917 rect.bottom += _originY; 8918 8919 impl.invalidateRect(rect); 8920 } 8921 8922 private Pen originalPen; 8923 private Color originalFillColor; 8924 private arsd.color.Rectangle originalClipRectangle; 8925 private OperatingSystemFont originalFont; 8926 void copyActiveOriginals() { 8927 if(impl is null) return; 8928 originalPen = impl._activePen; 8929 originalFillColor = impl._fillColor; 8930 originalClipRectangle = impl._clipRectangle; 8931 originalFont = impl._activeFont; 8932 } 8933 8934 ~this() { 8935 if(impl is null) return; 8936 impl.referenceCount--; 8937 //writeln("refcount -- ", impl.referenceCount); 8938 if(impl.referenceCount == 0) { 8939 //import std.stdio; writeln("destructed"); 8940 impl.dispose(); 8941 *window.activeScreenPainter = ScreenPainterImplementation.init; 8942 //import std.stdio; writeln("paint finished"); 8943 } else { 8944 // there is still an active reference, reset stuff so the 8945 // next user doesn't get weirdness via the reference 8946 this.rasterOp = RasterOp.normal; 8947 pen = originalPen; 8948 fillColor = originalFillColor; 8949 if(originalFont) 8950 setFont(originalFont); 8951 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 8952 } 8953 } 8954 8955 this(this) { 8956 if(impl is null) return; 8957 impl.referenceCount++; 8958 //writeln("refcount ++ ", impl.referenceCount); 8959 8960 copyActiveOriginals(); 8961 } 8962 8963 private int _originX; 8964 private int _originY; 8965 @property int originX() { return _originX; } 8966 @property int originY() { return _originY; } 8967 @property int originX(int a) { 8968 _originX = a; 8969 return _originX; 8970 } 8971 @property int originY(int a) { 8972 _originY = a; 8973 return _originY; 8974 } 8975 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 8976 private void transform(ref Point p) { 8977 if(impl is null) return; 8978 p.x += _originX; 8979 p.y += _originY; 8980 } 8981 8982 // this needs to be checked BEFORE the originX/Y transformation 8983 private bool isClipped(Point p) { 8984 return !currentClipRectangle.contains(p); 8985 } 8986 private bool isClipped(Point p, int width, int height) { 8987 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 8988 } 8989 private bool isClipped(Point p, Size s) { 8990 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 8991 } 8992 private bool isClipped(Point p, Point p2) { 8993 // need to ensure the end points are actually included inside, so the +1 does that 8994 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 8995 } 8996 8997 8998 /++ 8999 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 9000 9001 Returns: 9002 The old clip rectangle. 9003 9004 History: 9005 Return value was `void` prior to May 10, 2021. 9006 9007 +/ 9008 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 9009 if(impl is null) return currentClipRectangle; 9010 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 9011 return currentClipRectangle; // no need to do anything 9012 auto old = currentClipRectangle; 9013 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 9014 transform(pt); 9015 9016 impl.setClipRectangle(pt.x, pt.y, width, height); 9017 9018 return old; 9019 } 9020 9021 /// ditto 9022 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 9023 if(impl is null) return currentClipRectangle; 9024 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 9025 } 9026 9027 /// 9028 void setFont(OperatingSystemFont font) { 9029 if(impl is null) return; 9030 impl.setFont(font); 9031 } 9032 9033 /// 9034 int fontHeight() { 9035 if(impl is null) return 0; 9036 return impl.fontHeight(); 9037 } 9038 9039 private Pen activePen; 9040 9041 /// 9042 @property void pen(Pen p) { 9043 if(impl is null) return; 9044 activePen = p; 9045 impl.pen(p); 9046 } 9047 9048 /// 9049 @scriptable 9050 @property void outlineColor(Color c) { 9051 if(impl is null) return; 9052 if(activePen.color == c) 9053 return; 9054 activePen.color = c; 9055 impl.pen(activePen); 9056 } 9057 9058 /// 9059 @scriptable 9060 @property void fillColor(Color c) { 9061 if(impl is null) return; 9062 impl.fillColor(c); 9063 } 9064 9065 /// 9066 @property void rasterOp(RasterOp op) { 9067 if(impl is null) return; 9068 impl.rasterOp(op); 9069 } 9070 9071 9072 void updateDisplay() { 9073 // FIXME this should do what the dtor does 9074 } 9075 9076 /// 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) 9077 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9078 if(impl is null) return; 9079 if(isClipped(upperLeft, width, height)) return; 9080 transform(upperLeft); 9081 version(Windows) { 9082 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9083 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9084 RECT clip = scroll; 9085 RECT uncovered; 9086 HRGN hrgn; 9087 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9088 throw new WindowsApiException("ScrollDC", GetLastError()); 9089 9090 } else version(X11) { 9091 // FIXME: clip stuff outside this rectangle 9092 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9093 } else version(OSXCocoa) { 9094 throw new NotYetImplementedException(); 9095 } else static assert(0); 9096 } 9097 9098 /// 9099 void clear(Color color = Color.white()) { 9100 if(impl is null) return; 9101 fillColor = color; 9102 outlineColor = color; 9103 drawRectangle(Point(0, 0), window.width, window.height); 9104 } 9105 9106 /++ 9107 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9108 9109 Params: 9110 upperLeft = point on the window where the upper left corner of the image will be drawn 9111 imageUpperLeft = point on the image to start the slice to draw 9112 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. 9113 History: 9114 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9115 +/ 9116 version(OSXCocoa) {} else // NotYetImplementedException 9117 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9118 if(impl is null) return; 9119 if(isClipped(upperLeft, s.width, s.height)) return; 9120 transform(upperLeft); 9121 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9122 } 9123 9124 /// 9125 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9126 if(impl is null) return; 9127 //if(isClipped(upperLeft, w, h)) return; // FIXME 9128 transform(upperLeft); 9129 if(w == 0 || w > i.width) 9130 w = i.width; 9131 if(h == 0 || h > i.height) 9132 h = i.height; 9133 if(upperLeftOfImage.x < 0) 9134 upperLeftOfImage.x = 0; 9135 if(upperLeftOfImage.y < 0) 9136 upperLeftOfImage.y = 0; 9137 9138 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9139 } 9140 9141 /// 9142 Size textSize(in char[] text) { 9143 if(impl is null) return Size(0, 0); 9144 return impl.textSize(text); 9145 } 9146 9147 /++ 9148 Draws a string in the window with the set font (see [setFont] to change it). 9149 9150 Params: 9151 upperLeft = the upper left point of the bounding box of the text 9152 text = the string to draw 9153 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9154 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9155 +/ 9156 @scriptable 9157 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9158 if(impl is null) return; 9159 if(lowerRight.x != 0 || lowerRight.y != 0) { 9160 if(isClipped(upperLeft, lowerRight)) return; 9161 transform(lowerRight); 9162 } else { 9163 if(isClipped(upperLeft, textSize(text))) return; 9164 } 9165 transform(upperLeft); 9166 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9167 } 9168 9169 /++ 9170 Draws text using a custom font. 9171 9172 This is still MAJOR work in progress. 9173 9174 Creating a [DrawableFont] can be tricky and require additional dependencies. 9175 +/ 9176 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9177 if(impl is null) return; 9178 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9179 transform(upperLeft); 9180 font.drawString(this, upperLeft, text); 9181 } 9182 9183 version(Windows) 9184 void drawText(Point upperLeft, scope const(wchar)[] text) { 9185 if(impl is null) return; 9186 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9187 transform(upperLeft); 9188 9189 if(text.length && text[$-1] == '\n') 9190 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9191 9192 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9193 } 9194 9195 static struct TextDrawingContext { 9196 Point boundingBoxUpperLeft; 9197 Point boundingBoxLowerRight; 9198 9199 Point currentLocation; 9200 9201 Point lastDrewUpperLeft; 9202 Point lastDrewLowerRight; 9203 9204 // how do i do right aligned rich text? 9205 // i kinda want to do a pre-made drawing then right align 9206 // draw the whole block. 9207 // 9208 // That's exactly the diff: inline vs block stuff. 9209 9210 // I need to get coordinates of an inline section out too, 9211 // not just a bounding box, but a series of bounding boxes 9212 // should be ok. Consider what's needed to detect a click 9213 // on a link in the middle of a paragraph breaking a line. 9214 // 9215 // Generally, we should be able to get the rectangles of 9216 // any portion we draw. 9217 // 9218 // It also needs to tell what text is left if it overflows 9219 // out of the box, so we can do stuff like float images around 9220 // it. It should not attempt to draw a letter that would be 9221 // clipped. 9222 // 9223 // I might also turn off word wrap stuff. 9224 } 9225 9226 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9227 if(impl is null) return; 9228 // FIXME 9229 } 9230 9231 /// Drawing an individual pixel is slow. Avoid it if possible. 9232 void drawPixel(Point where) { 9233 if(impl is null) return; 9234 if(isClipped(where)) return; 9235 transform(where); 9236 impl.drawPixel(where.x, where.y); 9237 } 9238 9239 9240 /// Draws a pen using the current pen / outlineColor 9241 @scriptable 9242 void drawLine(Point starting, Point ending) { 9243 if(impl is null) return; 9244 if(isClipped(starting, ending)) return; 9245 transform(starting); 9246 transform(ending); 9247 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9248 } 9249 9250 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9251 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9252 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9253 @scriptable 9254 void drawRectangle(Point upperLeft, int width, int height) { 9255 if(impl is null) return; 9256 if(isClipped(upperLeft, width, height)) return; 9257 transform(upperLeft); 9258 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9259 } 9260 9261 /// ditto 9262 void drawRectangle(Point upperLeft, Size size) { 9263 if(impl is null) return; 9264 if(isClipped(upperLeft, size.width, size.height)) return; 9265 transform(upperLeft); 9266 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9267 } 9268 9269 /// ditto 9270 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9271 if(impl is null) return; 9272 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9273 transform(upperLeft); 9274 transform(lowerRightInclusive); 9275 impl.drawRectangle(upperLeft.x, upperLeft.y, 9276 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9277 } 9278 9279 // overload added on May 12, 2021 9280 /// ditto 9281 void drawRectangle(Rectangle rect) { 9282 drawRectangle(rect.upperLeft, rect.size); 9283 } 9284 9285 /// Arguments are the points of the bounding rectangle 9286 void drawEllipse(Point upperLeft, Point lowerRight) { 9287 if(impl is null) return; 9288 if(isClipped(upperLeft, lowerRight)) return; 9289 transform(upperLeft); 9290 transform(lowerRight); 9291 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9292 } 9293 9294 /++ 9295 start and finish are units of degrees * 64 9296 9297 History: 9298 The Windows implementation didn't match the Linux implementation until September 24, 2021. 9299 9300 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9301 +/ 9302 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 9303 if(impl is null) return; 9304 // FIXME: not actually implemented 9305 if(isClipped(upperLeft, width, height)) return; 9306 transform(upperLeft); 9307 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 9308 } 9309 9310 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9311 void drawCircle(Point upperLeft, int diameter) { 9312 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9313 } 9314 9315 /// . 9316 void drawPolygon(Point[] vertexes) { 9317 if(impl is null) return; 9318 assert(vertexes.length); 9319 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 9320 foreach(ref vertex; vertexes) { 9321 if(vertex.x < minX) 9322 minX = vertex.x; 9323 if(vertex.y < minY) 9324 minY = vertex.y; 9325 if(vertex.x > maxX) 9326 maxX = vertex.x; 9327 if(vertex.y > maxY) 9328 maxY = vertex.y; 9329 transform(vertex); 9330 } 9331 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 9332 impl.drawPolygon(vertexes); 9333 } 9334 9335 /// ditto 9336 void drawPolygon(Point[] vertexes...) { 9337 if(impl is null) return; 9338 drawPolygon(vertexes); 9339 } 9340 9341 9342 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 9343 9344 //mixin NativeScreenPainterImplementation!() impl; 9345 9346 9347 // HACK: if I mixin the impl directly, it won't let me override the copy 9348 // constructor! The linker complains about there being multiple definitions. 9349 // I'll make the best of it and reference count it though. 9350 ScreenPainterImplementation* impl; 9351 } 9352 9353 // HACK: I need a pointer to the implementation so it's separate 9354 struct ScreenPainterImplementation { 9355 CapableOfBeingDrawnUpon window; 9356 int referenceCount; 9357 mixin NativeScreenPainterImplementation!(); 9358 } 9359 9360 // FIXME: i haven't actually tested the sprite class on MS Windows 9361 9362 /** 9363 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 9364 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 9365 9366 9367 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 9368 though I'm not sure that's ideal and the implementation might change. 9369 9370 You create one by giving a window and an image. It optimizes for that window, 9371 and copies the image into it to use as the initial picture. Creating a sprite 9372 can be quite slow (especially over a network connection) so you should do it 9373 as little as possible and just hold on to your sprite handles after making them. 9374 simpledisplay does try to do its best though, using the XSHM extension if available, 9375 but you should still write your code as if it will always be slow. 9376 9377 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 9378 a fast operation - much faster than drawing the Image itself every time. 9379 9380 `Sprite` represents a scarce resource which should be freed when you 9381 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 9382 after it has been disposed. If you are unsure about this, don't take chances, 9383 just let the garbage collector do it for you. But ideally, you can manage its 9384 lifetime more efficiently. 9385 9386 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 9387 support alpha blending in its drawing at this time. That might change in the 9388 future, but if you need alpha blending right now, use OpenGL instead. See 9389 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 9390 9391 Update: on April 23, 2021, I finally added alpha blending support. You must opt 9392 in by setting the enableAlpha = true in the constructor. 9393 */ 9394 version(OSXCocoa) {} else // NotYetImplementedException 9395 class Sprite : CapableOfBeingDrawnUpon { 9396 9397 /// 9398 ScreenPainter draw() { 9399 return ScreenPainter(this, handle, false); 9400 } 9401 9402 /++ 9403 Copies the sprite's current state into a [TrueColorImage]. 9404 9405 Be warned: this can be a very slow operation 9406 9407 History: 9408 Actually implemented on March 14, 2021 9409 +/ 9410 TrueColorImage takeScreenshot() { 9411 return trueColorImageFromNativeHandle(handle, width, height); 9412 } 9413 9414 void delegate() paintingFinishedDg() { return null; } 9415 bool closed() { return false; } 9416 ScreenPainterImplementation* activeScreenPainter_; 9417 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 9418 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 9419 9420 version(Windows) 9421 private ubyte* rawData; 9422 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 9423 // ditto on the XPicture stuff 9424 9425 version(X11) { 9426 private static XRenderPictFormat* RGB24; 9427 private static XRenderPictFormat* ARGB32; 9428 9429 private Picture xrenderPicture; 9430 } 9431 9432 version(X11) 9433 private static void requireXRender() { 9434 if(!XRenderLibrary.loadAttempted) { 9435 XRenderLibrary.loadDynamicLibrary(); 9436 } 9437 9438 if(!XRenderLibrary.loadSuccessful) 9439 throw new Exception("XRender library load failure"); 9440 9441 auto display = XDisplayConnection.get; 9442 9443 // FIXME: if we migrate X displays, these need to be changed 9444 if(RGB24 is null) 9445 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 9446 if(ARGB32 is null) 9447 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 9448 } 9449 9450 protected this() {} 9451 9452 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 9453 this._width = width; 9454 this._height = height; 9455 this.enableAlpha = enableAlpha; 9456 9457 version(X11) { 9458 auto display = XDisplayConnection.get(); 9459 9460 if(enableAlpha) { 9461 requireXRender(); 9462 } 9463 9464 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 9465 9466 if(enableAlpha) { 9467 XRenderPictureAttributes attrs; 9468 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 9469 } 9470 } else version(Windows) { 9471 version(CRuntime_DigitalMars) { 9472 //if(enableAlpha) 9473 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 9474 } 9475 9476 BITMAPINFO infoheader; 9477 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 9478 infoheader.bmiHeader.biWidth = width; 9479 infoheader.bmiHeader.biHeight = height; 9480 infoheader.bmiHeader.biPlanes = 1; 9481 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 9482 infoheader.bmiHeader.biCompression = BI_RGB; 9483 9484 // FIXME: this should prolly be a device dependent bitmap... 9485 handle = CreateDIBSection( 9486 null, 9487 &infoheader, 9488 DIB_RGB_COLORS, 9489 cast(void**) &rawData, 9490 null, 9491 0); 9492 9493 if(handle is null) 9494 throw new WindowsApiException("couldn't create pixmap", GetLastError()); 9495 } 9496 } 9497 9498 /// Makes a sprite based on the image with the initial contents from the Image 9499 this(SimpleWindow win, Image i) { 9500 this(win, i.width, i.height, i.enableAlpha); 9501 9502 version(X11) { 9503 auto display = XDisplayConnection.get(); 9504 auto gc = XCreateGC(display, this.handle, 0, null); 9505 scope(exit) XFreeGC(display, gc); 9506 if(i.usingXshm) 9507 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 9508 else 9509 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 9510 } else version(Windows) { 9511 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 9512 auto arrLength = itemsPerLine * height; 9513 rawData[0..arrLength] = i.rawData[0..arrLength]; 9514 } else version(OSXCocoa) { 9515 // FIXME: I have no idea if this is even any good 9516 ubyte* rawData; 9517 9518 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 9519 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 9520 colorSpace, 9521 kCGImageAlphaPremultipliedLast 9522 |kCGBitmapByteOrder32Big); 9523 CGColorSpaceRelease(colorSpace); 9524 rawData = CGBitmapContextGetData(context); 9525 9526 auto rdl = (width * height * 4); 9527 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 9528 } else static assert(0); 9529 } 9530 9531 /++ 9532 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 9533 9534 Params: 9535 where = point on the window where the upper left corner of the image will be drawn 9536 imageUpperLeft = point on the image to start the slice to draw 9537 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. 9538 History: 9539 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9540 +/ 9541 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9542 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 9543 } 9544 9545 /// Call this when you're ready to get rid of it 9546 void dispose() { 9547 version(X11) { 9548 staticDispose(xrenderPicture, handle); 9549 xrenderPicture = None; 9550 handle = None; 9551 } else version(Windows) { 9552 staticDispose(handle); 9553 handle = null; 9554 } else version(OSXCocoa) { 9555 staticDispose(context); 9556 context = null; 9557 } else static assert(0); 9558 9559 } 9560 9561 version(X11) 9562 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 9563 if(xrenderPicture) 9564 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 9565 if(handle) 9566 XFreePixmap(XDisplayConnection.get(), handle); 9567 } 9568 else version(Windows) 9569 static void staticDispose(HBITMAP handle) { 9570 if(handle) 9571 DeleteObject(handle); 9572 } 9573 else version(OSXCocoa) 9574 static void staticDispose(CGContextRef context) { 9575 if(context) 9576 CGContextRelease(context); 9577 } 9578 9579 ~this() { 9580 version(X11) { if(xrenderPicture || handle) 9581 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 9582 } else version(Windows) { if(handle) 9583 cleanupQueue.queue!staticDispose(handle); 9584 } else version(OSXCocoa) { if(context) 9585 cleanupQueue.queue!staticDispose(context); 9586 } else static assert(0); 9587 } 9588 9589 /// 9590 final @property int width() { return _width; } 9591 9592 /// 9593 final @property int height() { return _height; } 9594 9595 /// 9596 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 9597 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 9598 } 9599 9600 auto nativeHandle() { 9601 return handle; 9602 } 9603 9604 private: 9605 9606 int _width; 9607 int _height; 9608 bool enableAlpha; 9609 version(X11) 9610 Pixmap handle; 9611 else version(Windows) 9612 HBITMAP handle; 9613 else version(OSXCocoa) 9614 CGContextRef context; 9615 else static assert(0); 9616 } 9617 9618 /++ 9619 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 9620 9621 History: 9622 Added November 20, 2021 (dub v10.4) 9623 +/ 9624 abstract class Gradient : Sprite { 9625 protected this(int w, int h) { 9626 version(X11) { 9627 Sprite.requireXRender(); 9628 9629 super(); 9630 enableAlpha = true; 9631 _width = w; 9632 _height = h; 9633 } else version(Windows) { 9634 super(null, w, h, true); // on Windows i'm just making a bitmap myself 9635 } 9636 } 9637 9638 version(Windows) 9639 final void forEachPixel(scope Color delegate(int x, int y) dg) { 9640 auto ptr = rawData; 9641 foreach(j; 0 .. _height) 9642 foreach(i; 0 .. _width) { 9643 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 9644 *rawData = (color.a * color.b) / 255; rawData++; 9645 *rawData = (color.a * color.g) / 255; rawData++; 9646 *rawData = (color.a * color.r) / 255; rawData++; 9647 *rawData = color.a; rawData++; 9648 } 9649 } 9650 9651 version(X11) 9652 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 9653 assert(stops.length > 0); 9654 assert(stops.length <= 16, "I got lazy with buffers"); 9655 9656 XFixed[16] stopsPositions = void; 9657 XRenderColor[16] colors = void; 9658 9659 foreach(idx, stop; stops) { 9660 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 9661 auto c = stop.c; 9662 colors[idx] = XRenderColor( 9663 cast(ushort)(c.r * ushort.max / 255), 9664 cast(ushort)(c.g * ushort.max / 255), 9665 cast(ushort)(c.b * ushort.max / 255), 9666 cast(ushort)(c.a * ubyte.max) // max value here is fractional 9667 ); 9668 } 9669 9670 xrenderPicture = dg(stopsPositions, colors); 9671 } 9672 9673 /// 9674 static struct Stop { 9675 float percentage; /// between 0 and 1.0 9676 Color c; 9677 } 9678 } 9679 9680 /++ 9681 Creates a linear gradient between p1 and p2. 9682 9683 X ONLY RIGHT NOW 9684 9685 History: 9686 Added November 20, 2021 (dub v10.4) 9687 9688 Bugs: 9689 Not yet implemented on Windows. 9690 +/ 9691 class LinearGradient : Gradient { 9692 /++ 9693 9694 +/ 9695 this(Point p1, Point p2, Stop[] stops...) { 9696 super(p2.x, p2.y); 9697 9698 version(X11) { 9699 XLinearGradient gradient; 9700 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 9701 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 9702 9703 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9704 return XRenderCreateLinearGradient( 9705 XDisplayConnection.get, 9706 &gradient, 9707 stopsPositions.ptr, 9708 colors.ptr, 9709 cast(int) stops.length); 9710 }); 9711 } else version(Windows) { 9712 // FIXME 9713 forEachPixel((int x, int y) { 9714 import core.stdc.math; 9715 9716 //sqrtf( 9717 9718 return Color.transparent; 9719 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9720 }); 9721 } 9722 } 9723 } 9724 9725 /++ 9726 A conical gradient goes from color to color around a circumference from a center point. 9727 9728 X ONLY RIGHT NOW 9729 9730 History: 9731 Added November 20, 2021 (dub v10.4) 9732 9733 Bugs: 9734 Not yet implemented on Windows. 9735 +/ 9736 class ConicalGradient : Gradient { 9737 /++ 9738 9739 +/ 9740 this(Point center, float angleInDegrees, Stop[] stops...) { 9741 super(center.x * 2, center.y * 2); 9742 9743 version(X11) { 9744 XConicalGradient gradient; 9745 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 9746 gradient.angle = cast(int)(angleInDegrees * ushort.max); 9747 9748 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9749 return XRenderCreateConicalGradient( 9750 XDisplayConnection.get, 9751 &gradient, 9752 stopsPositions.ptr, 9753 colors.ptr, 9754 cast(int) stops.length); 9755 }); 9756 } else version(Windows) { 9757 // FIXME 9758 forEachPixel((int x, int y) { 9759 import core.stdc.math; 9760 9761 //sqrtf( 9762 9763 return Color.transparent; 9764 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9765 }); 9766 9767 } 9768 } 9769 } 9770 9771 /++ 9772 A radial gradient goes from color to color based on distance from the center. 9773 It is like rings of color. 9774 9775 X ONLY RIGHT NOW 9776 9777 9778 More specifically, you create two circles: an inner circle and an outer circle. 9779 The gradient is only drawn in the area outside the inner circle but inside the outer 9780 circle. The closest line between those two circles forms the line for the gradient 9781 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 9782 9783 History: 9784 Added November 20, 2021 (dub v10.4) 9785 9786 Bugs: 9787 Not yet implemented on Windows. 9788 +/ 9789 class RadialGradient : Gradient { 9790 /++ 9791 9792 +/ 9793 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 9794 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 9795 9796 version(X11) { 9797 XRadialGradient gradient; 9798 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 9799 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 9800 9801 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9802 return XRenderCreateRadialGradient( 9803 XDisplayConnection.get, 9804 &gradient, 9805 stopsPositions.ptr, 9806 colors.ptr, 9807 cast(int) stops.length); 9808 }); 9809 } else version(Windows) { 9810 // FIXME 9811 forEachPixel((int x, int y) { 9812 import core.stdc.math; 9813 9814 //sqrtf( 9815 9816 return Color.transparent; 9817 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9818 }); 9819 } 9820 } 9821 } 9822 9823 9824 9825 /+ 9826 NOT IMPLEMENTED 9827 9828 A display-stored image optimized for relatively quick drawing, like 9829 [Sprite], but this one supports alpha channel blending and does NOT 9830 support direct drawing upon it with a [ScreenPainter]. 9831 9832 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 9833 plain [ScreenPainter]... sort of. 9834 9835 On X11, it requires the Xrender extension and library. This is available 9836 almost everywhere though. 9837 9838 History: 9839 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 9840 +/ 9841 version(none) 9842 class AlphaSprite { 9843 /++ 9844 Copies the given image into it. 9845 +/ 9846 this(MemoryImage img) { 9847 9848 if(!XRenderLibrary.loadAttempted) { 9849 XRenderLibrary.loadDynamicLibrary(); 9850 9851 // FIXME: this needs to be reconstructed when the X server changes 9852 repopulateX(); 9853 } 9854 if(!XRenderLibrary.loadSuccessful) 9855 throw new Exception("XRender library load failure"); 9856 9857 // I probably need to put the alpha mask in a separate Picture 9858 // ugh 9859 // maybe the Sprite itself can have an alpha bitmask anyway 9860 9861 9862 auto display = XDisplayConnection.get(); 9863 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 9864 9865 9866 XRenderPictureAttributes attrs; 9867 9868 handle = XRenderCreatePicture( 9869 XDisplayConnection.get, 9870 pixmap, 9871 RGBA, 9872 0, 9873 &attrs 9874 ); 9875 9876 } 9877 9878 // maybe i'll use the create gradient functions too with static factories.. 9879 9880 void drawAt(ScreenPainter painter, Point where) { 9881 //painter.drawPixmap(this, where); 9882 9883 XRenderPictureAttributes attrs; 9884 9885 auto pic = XRenderCreatePicture( 9886 XDisplayConnection.get, 9887 painter.impl.d, 9888 RGB, 9889 0, 9890 &attrs 9891 ); 9892 9893 XRenderComposite( 9894 XDisplayConnection.get, 9895 3, // PictOpOver 9896 handle, 9897 None, 9898 pic, 9899 0, // src 9900 0, 9901 0, // mask 9902 0, 9903 10, // dest 9904 10, 9905 100, // width 9906 100 9907 ); 9908 9909 /+ 9910 XRenderFreePicture( 9911 XDisplayConnection.get, 9912 pic 9913 ); 9914 9915 XRenderFreePicture( 9916 XDisplayConnection.get, 9917 fill 9918 ); 9919 +/ 9920 // on Windows you can stretch but Xrender still can't :( 9921 } 9922 9923 static XRenderPictFormat* RGB; 9924 static XRenderPictFormat* RGBA; 9925 static void repopulateX() { 9926 auto display = XDisplayConnection.get; 9927 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 9928 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 9929 } 9930 9931 XPixmap pixmap; 9932 Picture handle; 9933 } 9934 9935 /// 9936 interface CapableOfBeingDrawnUpon { 9937 /// 9938 ScreenPainter draw(); 9939 /// 9940 int width(); 9941 /// 9942 int height(); 9943 protected ScreenPainterImplementation* activeScreenPainter(); 9944 protected void activeScreenPainter(ScreenPainterImplementation*); 9945 bool closed(); 9946 9947 void delegate() paintingFinishedDg(); 9948 9949 /// Be warned: this can be a very slow operation 9950 TrueColorImage takeScreenshot(); 9951 } 9952 9953 /// 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]. 9954 void flushGui() { 9955 version(X11) { 9956 auto dpy = XDisplayConnection.get(); 9957 XLockDisplay(dpy); 9958 scope(exit) XUnlockDisplay(dpy); 9959 XFlush(dpy); 9960 } 9961 } 9962 9963 /++ 9964 Runs the given code in the GUI thread when its event loop 9965 is available, blocking until it completes. This allows you 9966 to create and manipulate windows from another thread without 9967 invoking undefined behavior. 9968 9969 If this is the gui thread, it runs the code immediately. 9970 9971 If no gui thread exists yet, the current thread is assumed 9972 to be it. Attempting to create windows or run the event loop 9973 in any other thread will cause an assertion failure. 9974 9975 9976 $(TIP 9977 Did you know you can use UFCS on delegate literals? 9978 9979 () { 9980 // code here 9981 }.runInGuiThread; 9982 ) 9983 9984 Returns: 9985 `true` if the function was called, `false` if it was not. 9986 The function may not be called because the gui thread had 9987 already terminated by the time you called this. 9988 9989 History: 9990 Added April 10, 2020 (v7.2.0) 9991 9992 Return value added and implementation tweaked to avoid locking 9993 at program termination on February 24, 2021 (v9.2.1). 9994 +/ 9995 bool runInGuiThread(scope void delegate() dg) @trusted { 9996 claimGuiThread(); 9997 9998 if(thisIsGuiThread) { 9999 dg(); 10000 return true; 10001 } 10002 10003 if(guiThreadTerminating) 10004 return false; 10005 10006 import core.sync.semaphore; 10007 static Semaphore sc; 10008 if(sc is null) 10009 sc = new Semaphore(); 10010 10011 static RunQueueMember* rqm; 10012 if(rqm is null) 10013 rqm = new RunQueueMember; 10014 rqm.dg = cast(typeof(rqm.dg)) dg; 10015 rqm.signal = sc; 10016 rqm.thrown = null; 10017 10018 synchronized(runInGuiThreadLock) { 10019 runInGuiThreadQueue ~= rqm; 10020 } 10021 10022 if(!SimpleWindow.eventWakeUp()) 10023 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10024 10025 rqm.signal.wait(); 10026 auto t = rqm.thrown; 10027 10028 if(t) 10029 throw t; 10030 10031 return true; 10032 } 10033 10034 // note it runs sync if this is the gui thread.... 10035 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10036 claimGuiThread(); 10037 10038 try { 10039 10040 if(thisIsGuiThread) { 10041 dg(); 10042 return; 10043 } 10044 10045 if(guiThreadTerminating) 10046 return; 10047 10048 RunQueueMember* rqm = new RunQueueMember; 10049 rqm.dg = cast(typeof(rqm.dg)) dg; 10050 rqm.signal = null; 10051 rqm.thrown = null; 10052 10053 synchronized(runInGuiThreadLock) { 10054 runInGuiThreadQueue ~= rqm; 10055 } 10056 10057 if(!SimpleWindow.eventWakeUp()) 10058 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10059 } catch(Exception e) { 10060 if(handleError) 10061 handleError(e); 10062 } 10063 } 10064 10065 private void runPendingRunInGuiThreadDelegates() { 10066 more: 10067 RunQueueMember* next; 10068 synchronized(runInGuiThreadLock) { 10069 if(runInGuiThreadQueue.length) { 10070 next = runInGuiThreadQueue[0]; 10071 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10072 } else { 10073 next = null; 10074 } 10075 } 10076 10077 if(next) { 10078 try { 10079 next.dg(); 10080 next.thrown = null; 10081 } catch(Throwable t) { 10082 next.thrown = t; 10083 } 10084 10085 if(next.signal) 10086 next.signal.notify(); 10087 10088 goto more; 10089 } 10090 } 10091 10092 private void claimGuiThread() nothrow { 10093 import core.atomic; 10094 if(cas(&guiThreadExists_, false, true)) 10095 thisIsGuiThread = true; 10096 } 10097 10098 private struct RunQueueMember { 10099 void delegate() dg; 10100 import core.sync.semaphore; 10101 Semaphore signal; 10102 Throwable thrown; 10103 } 10104 10105 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10106 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10107 private bool thisIsGuiThread = false; 10108 private shared bool guiThreadExists_ = false; 10109 private shared bool guiThreadTerminating = false; 10110 10111 /++ 10112 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10113 event loop. All windows must be exclusively created and managed by a single thread. 10114 10115 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10116 when you call one of its constructors. 10117 10118 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10119 one. If so, you can run gui functions on it. If not, don't. The helper functions 10120 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10121 10122 The reason this function is available is in case you want to message pass between a gui 10123 thread and your current thread. If no gui thread exists or if this is the gui thread, 10124 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10125 10126 History: 10127 Added December 3, 2021 (dub v10.5) 10128 +/ 10129 public bool guiThreadExists() { 10130 return guiThreadExists_; 10131 } 10132 10133 /++ 10134 Returns `true` if this thread is either running or set to be running the 10135 simpledisplay.d gui core event loop because it owns windows. 10136 10137 It is important to keep gui-related functionality in the right thread, so you will 10138 want to `runInGuiThread` when you call them (with some specific exceptions called 10139 out in those specific functions' documentation). Notably, all windows must be 10140 created and managed only from the gui thread. 10141 10142 Will return false if simpledisplay's other functions haven't been called 10143 yet; check [guiThreadExists] in addition to this. 10144 10145 History: 10146 Added December 3, 2021 (dub v10.5) 10147 +/ 10148 public bool thisThreadRunningGui() { 10149 return thisIsGuiThread; 10150 } 10151 10152 /++ 10153 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10154 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10155 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10156 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10157 file instead if you are in one of those situations). 10158 10159 It does not support outputting very many types; just strings and ints are likely to actually work. 10160 10161 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10162 is unspecified meaning I can change it at any time. The only point of this function is to help 10163 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10164 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10165 in those contexts. 10166 10167 $(WARNING 10168 I reserve the right to change this function at any time. You can use it if it helps you 10169 but do not rely on it for anything permanent. 10170 ) 10171 10172 History: 10173 Added December 3, 2021. Not formally supported under any stable tag. 10174 +/ 10175 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10176 try { 10177 version(Windows) { 10178 import core.sys.windows.wincon; 10179 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10180 AllocConsole(); 10181 const(char)* fn = "CONOUT$"; 10182 } else version(Posix) { 10183 const(char)* fn = "/dev/tty"; 10184 } else static assert(0, "Function not implemented for your system"); 10185 10186 if(fileOverride.length) 10187 fn = fileOverride.ptr; 10188 10189 import core.stdc.stdio; 10190 auto fp = fopen(fn, "wt"); 10191 if(fp is null) return; 10192 scope(exit) fclose(fp); 10193 10194 string str; 10195 foreach(item; t) { 10196 static if(is(typeof(item) : const(char)[])) 10197 str ~= item; 10198 else 10199 str ~= toInternal!string(item); 10200 str ~= " "; 10201 } 10202 str ~= "\n"; 10203 10204 fwrite(str.ptr, 1, str.length, fp); 10205 fflush(fp); 10206 } catch(Exception e) { 10207 // sorry no hope 10208 } 10209 } 10210 10211 private void guiThreadFinalize() { 10212 assert(thisIsGuiThread); 10213 10214 guiThreadTerminating = true; // don't add any more from this point on 10215 runPendingRunInGuiThreadDelegates(); 10216 } 10217 10218 /+ 10219 interface IPromise { 10220 void reportProgress(int current, int max, string message); 10221 10222 /+ // not formally in cuz of templates but still 10223 IPromise Then(); 10224 IPromise Catch(); 10225 IPromise Finally(); 10226 +/ 10227 } 10228 10229 /+ 10230 auto promise = async({ ... }); 10231 promise.Then(whatever). 10232 Then(whateverelse). 10233 Catch((exception) { }); 10234 10235 10236 A promise is run inside a fiber and it looks something like: 10237 10238 try { 10239 auto res = whatever(); 10240 auto res2 = whateverelse(res); 10241 } catch(Exception e) { 10242 { }(e); 10243 } 10244 10245 When a thing succeeds, it is passed as an arg to the next 10246 +/ 10247 class Promise(T) : IPromise { 10248 auto Then() { return null; } 10249 auto Catch() { return null; } 10250 auto Finally() { return null; } 10251 10252 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10253 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10254 T await(); 10255 } 10256 10257 interface Task { 10258 } 10259 10260 interface Resolvable(T) : Task { 10261 void run(); 10262 10263 void resolve(T); 10264 10265 Resolvable!T then(void delegate(T)); // returns a new promise 10266 Resolvable!T error(Throwable); // js catch 10267 Resolvable!T completed(); // js finally 10268 10269 } 10270 10271 /++ 10272 Runs `work` in a helper thread and sends its return value back to the main gui 10273 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10274 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10275 kill the program. 10276 10277 You can call reportProgress(position, max, message) to update your parent window 10278 on your progress. 10279 10280 I should also use `shared` methods. FIXME 10281 10282 History: 10283 Added March 6, 2021 (dub version 9.3). 10284 +/ 10285 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 10286 uponCompletion(work(null)); 10287 } 10288 10289 +/ 10290 10291 /// Used internal to dispatch events to various classes. 10292 interface CapableOfHandlingNativeEvent { 10293 NativeEventHandler getNativeEventHandler(); 10294 10295 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 10296 10297 version(X11) { 10298 // if this is impossible, you are allowed to just throw from it 10299 // Note: if you call it from another object, set a flag cuz the manger will call you again 10300 void recreateAfterDisconnect(); 10301 // discard any *connection specific* state, but keep enough that you 10302 // can be recreated if possible. discardConnectionState() is always called immediately 10303 // before recreateAfterDisconnect(), so you can set a flag there to decide if 10304 // you need initialization order 10305 void discardConnectionState(); 10306 } 10307 } 10308 10309 version(X11) 10310 /++ 10311 State of keys on mouse events, especially motion. 10312 10313 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 10314 +/ 10315 enum ModifierState : uint { 10316 shift = 1, /// 10317 capsLock = 2, /// 10318 ctrl = 4, /// 10319 alt = 8, /// Not always available on Windows 10320 windows = 64, /// ditto 10321 numLock = 16, /// 10322 10323 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10324 middleButtonDown = 512, /// ditto 10325 rightButtonDown = 1024, /// ditto 10326 } 10327 else version(Windows) 10328 /// ditto 10329 enum ModifierState : uint { 10330 shift = 4, /// 10331 ctrl = 8, /// 10332 10333 // i'm not sure if the next two are available 10334 alt = 256, /// not always available on Windows 10335 windows = 512, /// ditto 10336 10337 capsLock = 1024, /// 10338 numLock = 2048, /// 10339 10340 leftButtonDown = 1, /// not available on key events 10341 middleButtonDown = 16, /// ditto 10342 rightButtonDown = 2, /// ditto 10343 10344 backButtonDown = 0x20, /// not available on X 10345 forwardButtonDown = 0x40, /// ditto 10346 } 10347 else version(OSXCocoa) 10348 // FIXME FIXME NotYetImplementedException 10349 enum ModifierState : uint { 10350 shift = 1, /// 10351 capsLock = 2, /// 10352 ctrl = 4, /// 10353 alt = 8, /// Not always available on Windows 10354 windows = 64, /// ditto 10355 numLock = 16, /// 10356 10357 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10358 middleButtonDown = 512, /// ditto 10359 rightButtonDown = 1024, /// ditto 10360 } 10361 10362 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 10363 enum MouseButton : int { 10364 none = 0, 10365 left = 1, /// 10366 right = 2, /// 10367 middle = 4, /// 10368 wheelUp = 8, /// 10369 wheelDown = 16, /// 10370 backButton = 32, /// often found on the thumb and used for back in browsers 10371 forwardButton = 64, /// often found on the thumb and used for forward in browsers 10372 } 10373 10374 version(X11) { 10375 // FIXME: match ASCII whenever we can. Most of it is already there, 10376 // but there's a few exceptions and mismatches with Windows 10377 10378 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 10379 enum Key { 10380 Escape = 0xff1b, /// 10381 F1 = 0xffbe, /// 10382 F2 = 0xffbf, /// 10383 F3 = 0xffc0, /// 10384 F4 = 0xffc1, /// 10385 F5 = 0xffc2, /// 10386 F6 = 0xffc3, /// 10387 F7 = 0xffc4, /// 10388 F8 = 0xffc5, /// 10389 F9 = 0xffc6, /// 10390 F10 = 0xffc7, /// 10391 F11 = 0xffc8, /// 10392 F12 = 0xffc9, /// 10393 PrintScreen = 0xff61, /// 10394 ScrollLock = 0xff14, /// 10395 Pause = 0xff13, /// 10396 Grave = 0x60, /// The $(BACKTICK) ~ key 10397 // number keys across the top of the keyboard 10398 N1 = 0x31, /// Number key atop the keyboard 10399 N2 = 0x32, /// 10400 N3 = 0x33, /// 10401 N4 = 0x34, /// 10402 N5 = 0x35, /// 10403 N6 = 0x36, /// 10404 N7 = 0x37, /// 10405 N8 = 0x38, /// 10406 N9 = 0x39, /// 10407 N0 = 0x30, /// 10408 Dash = 0x2d, /// 10409 Equals = 0x3d, /// 10410 Backslash = 0x5c, /// The \ | key 10411 Backspace = 0xff08, /// 10412 Insert = 0xff63, /// 10413 Home = 0xff50, /// 10414 PageUp = 0xff55, /// 10415 Delete = 0xffff, /// 10416 End = 0xff57, /// 10417 PageDown = 0xff56, /// 10418 Up = 0xff52, /// 10419 Down = 0xff54, /// 10420 Left = 0xff51, /// 10421 Right = 0xff53, /// 10422 10423 Tab = 0xff09, /// 10424 Q = 0x71, /// 10425 W = 0x77, /// 10426 E = 0x65, /// 10427 R = 0x72, /// 10428 T = 0x74, /// 10429 Y = 0x79, /// 10430 U = 0x75, /// 10431 I = 0x69, /// 10432 O = 0x6f, /// 10433 P = 0x70, /// 10434 LeftBracket = 0x5b, /// the [ { key 10435 RightBracket = 0x5d, /// the ] } key 10436 CapsLock = 0xffe5, /// 10437 A = 0x61, /// 10438 S = 0x73, /// 10439 D = 0x64, /// 10440 F = 0x66, /// 10441 G = 0x67, /// 10442 H = 0x68, /// 10443 J = 0x6a, /// 10444 K = 0x6b, /// 10445 L = 0x6c, /// 10446 Semicolon = 0x3b, /// 10447 Apostrophe = 0x27, /// 10448 Enter = 0xff0d, /// 10449 Shift = 0xffe1, /// 10450 Z = 0x7a, /// 10451 X = 0x78, /// 10452 C = 0x63, /// 10453 V = 0x76, /// 10454 B = 0x62, /// 10455 N = 0x6e, /// 10456 M = 0x6d, /// 10457 Comma = 0x2c, /// 10458 Period = 0x2e, /// 10459 Slash = 0x2f, /// the / ? key 10460 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 10461 Ctrl = 0xffe3, /// 10462 Windows = 0xffeb, /// 10463 Alt = 0xffe9, /// 10464 Space = 0x20, /// 10465 Alt_r = 0xffea, /// ditto of shift_r 10466 Windows_r = 0xffec, /// 10467 Menu = 0xff67, /// 10468 Ctrl_r = 0xffe4, /// 10469 10470 NumLock = 0xff7f, /// 10471 Divide = 0xffaf, /// The / key on the number pad 10472 Multiply = 0xffaa, /// The * key on the number pad 10473 Minus = 0xffad, /// The - key on the number pad 10474 Plus = 0xffab, /// The + key on the number pad 10475 PadEnter = 0xff8d, /// Numberpad enter key 10476 Pad1 = 0xff9c, /// Numberpad keys 10477 Pad2 = 0xff99, /// 10478 Pad3 = 0xff9b, /// 10479 Pad4 = 0xff96, /// 10480 Pad5 = 0xff9d, /// 10481 Pad6 = 0xff98, /// 10482 Pad7 = 0xff95, /// 10483 Pad8 = 0xff97, /// 10484 Pad9 = 0xff9a, /// 10485 Pad0 = 0xff9e, /// 10486 PadDot = 0xff9f, /// 10487 } 10488 } else version(Windows) { 10489 // the character here is for en-us layouts and for illustration only 10490 // if you actually want to get characters, wait for character events 10491 // (the argument to your event handler is simply a dchar) 10492 // those will be converted by the OS for the right locale. 10493 10494 enum Key { 10495 Escape = 0x1b, 10496 F1 = 0x70, 10497 F2 = 0x71, 10498 F3 = 0x72, 10499 F4 = 0x73, 10500 F5 = 0x74, 10501 F6 = 0x75, 10502 F7 = 0x76, 10503 F8 = 0x77, 10504 F9 = 0x78, 10505 F10 = 0x79, 10506 F11 = 0x7a, 10507 F12 = 0x7b, 10508 PrintScreen = 0x2c, 10509 ScrollLock = 0x91, 10510 Pause = 0x13, 10511 Grave = 0xc0, 10512 // number keys across the top of the keyboard 10513 N1 = 0x31, 10514 N2 = 0x32, 10515 N3 = 0x33, 10516 N4 = 0x34, 10517 N5 = 0x35, 10518 N6 = 0x36, 10519 N7 = 0x37, 10520 N8 = 0x38, 10521 N9 = 0x39, 10522 N0 = 0x30, 10523 Dash = 0xbd, 10524 Equals = 0xbb, 10525 Backslash = 0xdc, 10526 Backspace = 0x08, 10527 Insert = 0x2d, 10528 Home = 0x24, 10529 PageUp = 0x21, 10530 Delete = 0x2e, 10531 End = 0x23, 10532 PageDown = 0x22, 10533 Up = 0x26, 10534 Down = 0x28, 10535 Left = 0x25, 10536 Right = 0x27, 10537 10538 Tab = 0x09, 10539 Q = 0x51, 10540 W = 0x57, 10541 E = 0x45, 10542 R = 0x52, 10543 T = 0x54, 10544 Y = 0x59, 10545 U = 0x55, 10546 I = 0x49, 10547 O = 0x4f, 10548 P = 0x50, 10549 LeftBracket = 0xdb, 10550 RightBracket = 0xdd, 10551 CapsLock = 0x14, 10552 A = 0x41, 10553 S = 0x53, 10554 D = 0x44, 10555 F = 0x46, 10556 G = 0x47, 10557 H = 0x48, 10558 J = 0x4a, 10559 K = 0x4b, 10560 L = 0x4c, 10561 Semicolon = 0xba, 10562 Apostrophe = 0xde, 10563 Enter = 0x0d, 10564 Shift = 0x10, 10565 Z = 0x5a, 10566 X = 0x58, 10567 C = 0x43, 10568 V = 0x56, 10569 B = 0x42, 10570 N = 0x4e, 10571 M = 0x4d, 10572 Comma = 0xbc, 10573 Period = 0xbe, 10574 Slash = 0xbf, 10575 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10576 Ctrl = 0x11, 10577 Windows = 0x5b, 10578 Alt = -5, // FIXME 10579 Space = 0x20, 10580 Alt_r = 0xffea, // ditto of shift_r 10581 Windows_r = 0x5c, // ditto of shift_r 10582 Menu = 0x5d, 10583 Ctrl_r = 0xa3, // ditto of shift_r 10584 10585 NumLock = 0x90, 10586 Divide = 0x6f, 10587 Multiply = 0x6a, 10588 Minus = 0x6d, 10589 Plus = 0x6b, 10590 PadEnter = -8, // FIXME 10591 Pad1 = 0x61, 10592 Pad2 = 0x62, 10593 Pad3 = 0x63, 10594 Pad4 = 0x64, 10595 Pad5 = 0x65, 10596 Pad6 = 0x66, 10597 Pad7 = 0x67, 10598 Pad8 = 0x68, 10599 Pad9 = 0x69, 10600 Pad0 = 0x60, 10601 PadDot = 0x6e, 10602 } 10603 10604 // I'm keeping this around for reference purposes 10605 // ideally all these buttons will be listed for all platforms, 10606 // but now now I'm just focusing on my US keyboard 10607 version(none) 10608 enum Key { 10609 LBUTTON = 0x01, 10610 RBUTTON = 0x02, 10611 CANCEL = 0x03, 10612 MBUTTON = 0x04, 10613 //static if (_WIN32_WINNT > = 0x500) { 10614 XBUTTON1 = 0x05, 10615 XBUTTON2 = 0x06, 10616 //} 10617 BACK = 0x08, 10618 TAB = 0x09, 10619 CLEAR = 0x0C, 10620 RETURN = 0x0D, 10621 SHIFT = 0x10, 10622 CONTROL = 0x11, 10623 MENU = 0x12, 10624 PAUSE = 0x13, 10625 CAPITAL = 0x14, 10626 KANA = 0x15, 10627 HANGEUL = 0x15, 10628 HANGUL = 0x15, 10629 JUNJA = 0x17, 10630 FINAL = 0x18, 10631 HANJA = 0x19, 10632 KANJI = 0x19, 10633 ESCAPE = 0x1B, 10634 CONVERT = 0x1C, 10635 NONCONVERT = 0x1D, 10636 ACCEPT = 0x1E, 10637 MODECHANGE = 0x1F, 10638 SPACE = 0x20, 10639 PRIOR = 0x21, 10640 NEXT = 0x22, 10641 END = 0x23, 10642 HOME = 0x24, 10643 LEFT = 0x25, 10644 UP = 0x26, 10645 RIGHT = 0x27, 10646 DOWN = 0x28, 10647 SELECT = 0x29, 10648 PRINT = 0x2A, 10649 EXECUTE = 0x2B, 10650 SNAPSHOT = 0x2C, 10651 INSERT = 0x2D, 10652 DELETE = 0x2E, 10653 HELP = 0x2F, 10654 LWIN = 0x5B, 10655 RWIN = 0x5C, 10656 APPS = 0x5D, 10657 SLEEP = 0x5F, 10658 NUMPAD0 = 0x60, 10659 NUMPAD1 = 0x61, 10660 NUMPAD2 = 0x62, 10661 NUMPAD3 = 0x63, 10662 NUMPAD4 = 0x64, 10663 NUMPAD5 = 0x65, 10664 NUMPAD6 = 0x66, 10665 NUMPAD7 = 0x67, 10666 NUMPAD8 = 0x68, 10667 NUMPAD9 = 0x69, 10668 MULTIPLY = 0x6A, 10669 ADD = 0x6B, 10670 SEPARATOR = 0x6C, 10671 SUBTRACT = 0x6D, 10672 DECIMAL = 0x6E, 10673 DIVIDE = 0x6F, 10674 F1 = 0x70, 10675 F2 = 0x71, 10676 F3 = 0x72, 10677 F4 = 0x73, 10678 F5 = 0x74, 10679 F6 = 0x75, 10680 F7 = 0x76, 10681 F8 = 0x77, 10682 F9 = 0x78, 10683 F10 = 0x79, 10684 F11 = 0x7A, 10685 F12 = 0x7B, 10686 F13 = 0x7C, 10687 F14 = 0x7D, 10688 F15 = 0x7E, 10689 F16 = 0x7F, 10690 F17 = 0x80, 10691 F18 = 0x81, 10692 F19 = 0x82, 10693 F20 = 0x83, 10694 F21 = 0x84, 10695 F22 = 0x85, 10696 F23 = 0x86, 10697 F24 = 0x87, 10698 NUMLOCK = 0x90, 10699 SCROLL = 0x91, 10700 LSHIFT = 0xA0, 10701 RSHIFT = 0xA1, 10702 LCONTROL = 0xA2, 10703 RCONTROL = 0xA3, 10704 LMENU = 0xA4, 10705 RMENU = 0xA5, 10706 //static if (_WIN32_WINNT > = 0x500) { 10707 BROWSER_BACK = 0xA6, 10708 BROWSER_FORWARD = 0xA7, 10709 BROWSER_REFRESH = 0xA8, 10710 BROWSER_STOP = 0xA9, 10711 BROWSER_SEARCH = 0xAA, 10712 BROWSER_FAVORITES = 0xAB, 10713 BROWSER_HOME = 0xAC, 10714 VOLUME_MUTE = 0xAD, 10715 VOLUME_DOWN = 0xAE, 10716 VOLUME_UP = 0xAF, 10717 MEDIA_NEXT_TRACK = 0xB0, 10718 MEDIA_PREV_TRACK = 0xB1, 10719 MEDIA_STOP = 0xB2, 10720 MEDIA_PLAY_PAUSE = 0xB3, 10721 LAUNCH_MAIL = 0xB4, 10722 LAUNCH_MEDIA_SELECT = 0xB5, 10723 LAUNCH_APP1 = 0xB6, 10724 LAUNCH_APP2 = 0xB7, 10725 //} 10726 OEM_1 = 0xBA, 10727 //static if (_WIN32_WINNT > = 0x500) { 10728 OEM_PLUS = 0xBB, 10729 OEM_COMMA = 0xBC, 10730 OEM_MINUS = 0xBD, 10731 OEM_PERIOD = 0xBE, 10732 //} 10733 OEM_2 = 0xBF, 10734 OEM_3 = 0xC0, 10735 OEM_4 = 0xDB, 10736 OEM_5 = 0xDC, 10737 OEM_6 = 0xDD, 10738 OEM_7 = 0xDE, 10739 OEM_8 = 0xDF, 10740 //static if (_WIN32_WINNT > = 0x500) { 10741 OEM_102 = 0xE2, 10742 //} 10743 PROCESSKEY = 0xE5, 10744 //static if (_WIN32_WINNT > = 0x500) { 10745 PACKET = 0xE7, 10746 //} 10747 ATTN = 0xF6, 10748 CRSEL = 0xF7, 10749 EXSEL = 0xF8, 10750 EREOF = 0xF9, 10751 PLAY = 0xFA, 10752 ZOOM = 0xFB, 10753 NONAME = 0xFC, 10754 PA1 = 0xFD, 10755 OEM_CLEAR = 0xFE, 10756 } 10757 10758 } else version(OSXCocoa) { 10759 // FIXME 10760 enum Key { 10761 Escape = 0x1b, 10762 F1 = 0x70, 10763 F2 = 0x71, 10764 F3 = 0x72, 10765 F4 = 0x73, 10766 F5 = 0x74, 10767 F6 = 0x75, 10768 F7 = 0x76, 10769 F8 = 0x77, 10770 F9 = 0x78, 10771 F10 = 0x79, 10772 F11 = 0x7a, 10773 F12 = 0x7b, 10774 PrintScreen = 0x2c, 10775 ScrollLock = -2, // FIXME 10776 Pause = -3, // FIXME 10777 Grave = 0xc0, 10778 // number keys across the top of the keyboard 10779 N1 = 0x31, 10780 N2 = 0x32, 10781 N3 = 0x33, 10782 N4 = 0x34, 10783 N5 = 0x35, 10784 N6 = 0x36, 10785 N7 = 0x37, 10786 N8 = 0x38, 10787 N9 = 0x39, 10788 N0 = 0x30, 10789 Dash = 0xbd, 10790 Equals = 0xbb, 10791 Backslash = 0xdc, 10792 Backspace = 0x08, 10793 Insert = 0x2d, 10794 Home = 0x24, 10795 PageUp = 0x21, 10796 Delete = 0x2e, 10797 End = 0x23, 10798 PageDown = 0x22, 10799 Up = 0x26, 10800 Down = 0x28, 10801 Left = 0x25, 10802 Right = 0x27, 10803 10804 Tab = 0x09, 10805 Q = 0x51, 10806 W = 0x57, 10807 E = 0x45, 10808 R = 0x52, 10809 T = 0x54, 10810 Y = 0x59, 10811 U = 0x55, 10812 I = 0x49, 10813 O = 0x4f, 10814 P = 0x50, 10815 LeftBracket = 0xdb, 10816 RightBracket = 0xdd, 10817 CapsLock = 0x14, 10818 A = 0x41, 10819 S = 0x53, 10820 D = 0x44, 10821 F = 0x46, 10822 G = 0x47, 10823 H = 0x48, 10824 J = 0x4a, 10825 K = 0x4b, 10826 L = 0x4c, 10827 Semicolon = 0xba, 10828 Apostrophe = 0xde, 10829 Enter = 0x0d, 10830 Shift = 0x10, 10831 Z = 0x5a, 10832 X = 0x58, 10833 C = 0x43, 10834 V = 0x56, 10835 B = 0x42, 10836 N = 0x4e, 10837 M = 0x4d, 10838 Comma = 0xbc, 10839 Period = 0xbe, 10840 Slash = 0xbf, 10841 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10842 Ctrl = 0x11, 10843 Windows = 0x5b, 10844 Alt = -5, // FIXME 10845 Space = 0x20, 10846 Alt_r = 0xffea, // ditto of shift_r 10847 Windows_r = -6, // FIXME 10848 Menu = 0x5d, 10849 Ctrl_r = -7, // FIXME 10850 10851 NumLock = 0x90, 10852 Divide = 0x6f, 10853 Multiply = 0x6a, 10854 Minus = 0x6d, 10855 Plus = 0x6b, 10856 PadEnter = -8, // FIXME 10857 // FIXME for the rest of these: 10858 Pad1 = 0xff9c, 10859 Pad2 = 0xff99, 10860 Pad3 = 0xff9b, 10861 Pad4 = 0xff96, 10862 Pad5 = 0xff9d, 10863 Pad6 = 0xff98, 10864 Pad7 = 0xff95, 10865 Pad8 = 0xff97, 10866 Pad9 = 0xff9a, 10867 Pad0 = 0xff9e, 10868 PadDot = 0xff9f, 10869 } 10870 10871 } 10872 10873 /* Additional utilities */ 10874 10875 10876 Color fromHsl(real h, real s, real l) { 10877 return arsd.color.fromHsl([h,s,l]); 10878 } 10879 10880 10881 10882 /* ********** What follows is the system-specific implementations *********/ 10883 version(Windows) { 10884 10885 10886 // helpers for making HICONs from MemoryImages 10887 class WindowsIcon { 10888 struct Win32Icon(int colorCount) { 10889 align(1): 10890 uint biSize; 10891 int biWidth; 10892 int biHeight; 10893 ushort biPlanes; 10894 ushort biBitCount; 10895 uint biCompression; 10896 uint biSizeImage; 10897 int biXPelsPerMeter; 10898 int biYPelsPerMeter; 10899 uint biClrUsed; 10900 uint biClrImportant; 10901 RGBQUAD[colorCount] biColors; 10902 /* Pixels: 10903 Uint8 pixels[] 10904 */ 10905 /* Mask: 10906 Uint8 mask[] 10907 */ 10908 10909 ubyte[4096] data; 10910 10911 void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 10912 width = mi.width; 10913 height = mi.height; 10914 10915 auto indexedImage = cast(IndexedImage) mi; 10916 if(indexedImage is null) 10917 indexedImage = quantize(mi.getAsTrueColorImage()); 10918 10919 assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy 10920 assert(height %4 == 0); 10921 10922 int icon_plen = height*((width+3)&~3); 10923 int icon_mlen = height*((((width+7)/8)+3)&~3); 10924 icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 10925 10926 biSize = 40; 10927 biWidth = width; 10928 biHeight = height*2; 10929 biPlanes = 1; 10930 biBitCount = 8; 10931 biSizeImage = icon_plen+icon_mlen; 10932 10933 int offset = 0; 10934 int andOff = icon_plen * 8; // the and offset is in bits 10935 for(int y = height - 1; y >= 0; y--) { 10936 int off2 = y * width; 10937 foreach(x; 0 .. width) { 10938 const b = indexedImage.data[off2 + x]; 10939 data[offset] = b; 10940 offset++; 10941 10942 const andBit = andOff % 8; 10943 const andIdx = andOff / 8; 10944 assert(b < indexedImage.palette.length); 10945 // this is anded to the destination, since and 0 means erase, 10946 // we want that to be opaque, and 1 for transparent 10947 auto transparent = (indexedImage.palette[b].a <= 127); 10948 data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0); 10949 10950 andOff++; 10951 } 10952 10953 andOff += andOff % 32; 10954 } 10955 10956 foreach(idx, entry; indexedImage.palette) { 10957 if(entry.a > 127) { 10958 biColors[idx].rgbBlue = entry.b; 10959 biColors[idx].rgbGreen = entry.g; 10960 biColors[idx].rgbRed = entry.r; 10961 } else { 10962 biColors[idx].rgbBlue = 255; 10963 biColors[idx].rgbGreen = 255; 10964 biColors[idx].rgbRed = 255; 10965 } 10966 } 10967 10968 /* 10969 data[0..icon_plen] = getFlippedUnfilteredDatastream(png); 10970 data[icon_plen..icon_plen+icon_mlen] = getANDMask(png); 10971 //icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0); 10972 auto pngMap = fetchPaletteWin32(png); 10973 biColors[0..pngMap.length] = pngMap[]; 10974 */ 10975 } 10976 } 10977 10978 10979 Win32Icon!(256) icon_win32; 10980 10981 10982 this(MemoryImage mi) { 10983 int icon_len, width, height; 10984 10985 icon_win32.fromMemoryImage(mi, icon_len, width, height); 10986 10987 /* 10988 PNG* png = readPnpngData); 10989 PNGHeader pngh = getHeader(png); 10990 void* icon_win32; 10991 if(pngh.depth == 4) { 10992 auto i = new Win32Icon!(16); 10993 i.fromPNG(png, pngh, icon_len, width, height); 10994 icon_win32 = i; 10995 } 10996 else if(pngh.depth == 8) { 10997 auto i = new Win32Icon!(256); 10998 i.fromPNG(png, pngh, icon_len, width, height); 10999 icon_win32 = i; 11000 } else assert(0); 11001 */ 11002 11003 hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0); 11004 11005 if(hIcon is null) throw new WindowsApiException("CreateIconFromResourceEx", GetLastError()); 11006 } 11007 11008 ~this() { 11009 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 11010 DestroyIcon(hIcon); 11011 } 11012 11013 HICON hIcon; 11014 } 11015 11016 11017 11018 11019 11020 11021 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 11022 alias HWND NativeWindowHandle; 11023 11024 extern(Windows) 11025 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 11026 try { 11027 if(SimpleWindow.handleNativeGlobalEvent !is null) { 11028 // it returns zero if the message is handled, so we won't do anything more there 11029 // do I like that though? 11030 int mustReturn; 11031 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11032 if(mustReturn) 11033 return ret; 11034 } 11035 11036 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11037 if(window.getNativeEventHandler !is null) { 11038 int mustReturn; 11039 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11040 if(mustReturn) 11041 return ret; 11042 } 11043 if(auto w = cast(SimpleWindow) (*window)) 11044 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11045 else 11046 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11047 } else { 11048 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11049 } 11050 } catch (Exception e) { 11051 try { 11052 sdpy_abort(e); 11053 return 0; 11054 } catch(Exception e) { assert(0); } 11055 } 11056 } 11057 11058 void sdpy_abort(Throwable e) nothrow { 11059 try 11060 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11061 catch(Exception e) 11062 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11063 ExitProcess(1); 11064 } 11065 11066 mixin template NativeScreenPainterImplementation() { 11067 HDC hdc; 11068 HWND hwnd; 11069 //HDC windowHdc; 11070 HBITMAP oldBmp; 11071 11072 void create(NativeWindowHandle window) { 11073 hwnd = window; 11074 11075 if(auto sw = cast(SimpleWindow) this.window) { 11076 // drawing on a window, double buffer 11077 auto windowHdc = GetDC(hwnd); 11078 11079 auto buffer = sw.impl.buffer; 11080 if(buffer is null) { 11081 hdc = windowHdc; 11082 windowDc = true; 11083 } else { 11084 hdc = CreateCompatibleDC(windowHdc); 11085 11086 ReleaseDC(hwnd, windowHdc); 11087 11088 oldBmp = SelectObject(hdc, buffer); 11089 } 11090 } else { 11091 // drawing on something else, draw directly 11092 hdc = CreateCompatibleDC(null); 11093 SelectObject(hdc, window); 11094 } 11095 11096 // X doesn't draw a text background, so neither should we 11097 SetBkMode(hdc, TRANSPARENT); 11098 11099 ensureDefaultFontLoaded(); 11100 11101 if(defaultGuiFont) { 11102 SelectObject(hdc, defaultGuiFont); 11103 // DeleteObject(defaultGuiFont); 11104 } 11105 } 11106 11107 static HFONT defaultGuiFont; 11108 static void ensureDefaultFontLoaded() { 11109 static bool triedDefaultGuiFont = false; 11110 if(!triedDefaultGuiFont) { 11111 NONCLIENTMETRICS params; 11112 params.cbSize = params.sizeof; 11113 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11114 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11115 } 11116 triedDefaultGuiFont = true; 11117 } 11118 } 11119 11120 private OperatingSystemFont _activeFont; 11121 11122 void setFont(OperatingSystemFont font) { 11123 _activeFont = font; 11124 if(font && font.font) { 11125 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11126 // error... how to handle tho? 11127 } else { 11128 11129 } 11130 } 11131 else if(defaultGuiFont) 11132 SelectObject(hdc, defaultGuiFont); 11133 } 11134 11135 arsd.color.Rectangle _clipRectangle; 11136 11137 void setClipRectangle(int x, int y, int width, int height) { 11138 auto old = _clipRectangle; 11139 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11140 if(old == _clipRectangle) 11141 return; 11142 11143 if(width == 0 || height == 0) { 11144 SelectClipRgn(hdc, null); 11145 } else { 11146 auto region = CreateRectRgn(x, y, x + width, y + height); 11147 SelectClipRgn(hdc, region); 11148 DeleteObject(region); 11149 } 11150 } 11151 11152 11153 // just because we can on Windows... 11154 //void create(Image image); 11155 11156 void invalidateRect(Rectangle invalidRect) { 11157 RECT rect; 11158 rect.left = invalidRect.left; 11159 rect.right = invalidRect.right; 11160 rect.top = invalidRect.top; 11161 rect.bottom = invalidRect.bottom; 11162 InvalidateRect(hwnd, &rect, false); 11163 } 11164 bool manualInvalidations; 11165 11166 void dispose() { 11167 // FIXME: this.window.width/height is probably wrong 11168 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11169 // ReleaseDC(hwnd, windowHdc); 11170 11171 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11172 if(cast(SimpleWindow) this.window) { 11173 if(!manualInvalidations) 11174 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11175 } 11176 11177 if(originalPen !is null) 11178 SelectObject(hdc, originalPen); 11179 if(currentPen !is null) 11180 DeleteObject(currentPen); 11181 if(originalBrush !is null) 11182 SelectObject(hdc, originalBrush); 11183 if(currentBrush !is null) 11184 DeleteObject(currentBrush); 11185 11186 SelectObject(hdc, oldBmp); 11187 11188 if(windowDc) 11189 ReleaseDC(hwnd, hdc); 11190 else 11191 DeleteDC(hdc); 11192 11193 if(window.paintingFinishedDg !is null) 11194 window.paintingFinishedDg()(); 11195 } 11196 11197 bool windowDc; 11198 HPEN originalPen; 11199 HPEN currentPen; 11200 11201 Pen _activePen; 11202 11203 Color _outlineColor; 11204 11205 @property void pen(Pen p) { 11206 _activePen = p; 11207 _outlineColor = p.color; 11208 11209 HPEN pen; 11210 if(p.color.a == 0) { 11211 pen = GetStockObject(NULL_PEN); 11212 } else { 11213 int style = PS_SOLID; 11214 final switch(p.style) { 11215 case Pen.Style.Solid: 11216 style = PS_SOLID; 11217 break; 11218 case Pen.Style.Dashed: 11219 style = PS_DASH; 11220 break; 11221 case Pen.Style.Dotted: 11222 style = PS_DOT; 11223 break; 11224 } 11225 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11226 } 11227 auto orig = SelectObject(hdc, pen); 11228 if(originalPen is null) 11229 originalPen = orig; 11230 11231 if(currentPen !is null) 11232 DeleteObject(currentPen); 11233 11234 currentPen = pen; 11235 11236 // the outline is like a foreground since it's done that way on X 11237 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11238 11239 } 11240 11241 @property void rasterOp(RasterOp op) { 11242 int mode; 11243 final switch(op) { 11244 case RasterOp.normal: 11245 mode = R2_COPYPEN; 11246 break; 11247 case RasterOp.xor: 11248 mode = R2_XORPEN; 11249 break; 11250 } 11251 SetROP2(hdc, mode); 11252 } 11253 11254 HBRUSH originalBrush; 11255 HBRUSH currentBrush; 11256 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11257 @property void fillColor(Color c) { 11258 if(c == _fillColor) 11259 return; 11260 _fillColor = c; 11261 HBRUSH brush; 11262 if(c.a == 0) { 11263 brush = GetStockObject(HOLLOW_BRUSH); 11264 } else { 11265 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11266 } 11267 auto orig = SelectObject(hdc, brush); 11268 if(originalBrush is null) 11269 originalBrush = orig; 11270 11271 if(currentBrush !is null) 11272 DeleteObject(currentBrush); 11273 11274 currentBrush = brush; 11275 11276 // background color is NOT set because X doesn't draw text backgrounds 11277 // SetBkColor(hdc, RGB(255, 255, 255)); 11278 } 11279 11280 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11281 BITMAP bm; 11282 11283 HDC hdcMem = CreateCompatibleDC(hdc); 11284 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11285 11286 GetObject(i.handle, bm.sizeof, &bm); 11287 11288 // or should I AlphaBlend!??!?! 11289 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11290 11291 SelectObject(hdcMem, hbmOld); 11292 DeleteDC(hdcMem); 11293 } 11294 11295 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11296 BITMAP bm; 11297 11298 HDC hdcMem = CreateCompatibleDC(hdc); 11299 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 11300 11301 GetObject(s.handle, bm.sizeof, &bm); 11302 11303 version(CRuntime_DigitalMars) goto noalpha; 11304 11305 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 11306 if(s.enableAlpha) { 11307 auto dw = w ? w : bm.bmWidth; 11308 auto dh = h ? h : bm.bmHeight; 11309 BLENDFUNCTION bf; 11310 bf.BlendOp = AC_SRC_OVER; 11311 bf.SourceConstantAlpha = 255; 11312 bf.AlphaFormat = AC_SRC_ALPHA; 11313 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 11314 } else { 11315 noalpha: 11316 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 11317 } 11318 11319 SelectObject(hdcMem, hbmOld); 11320 DeleteDC(hdcMem); 11321 } 11322 11323 Size textSize(scope const(char)[] text) { 11324 bool dummyX; 11325 if(text.length == 0) { 11326 text = " "; 11327 dummyX = true; 11328 } 11329 RECT rect; 11330 WCharzBuffer buffer = WCharzBuffer(text); 11331 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 11332 return Size(dummyX ? 0 : rect.right, rect.bottom); 11333 } 11334 11335 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 11336 if(text.length && text[$-1] == '\n') 11337 text = text[0 .. $-1]; // tailing newlines are weird on windows... 11338 if(text.length && text[$-1] == '\r') 11339 text = text[0 .. $-1]; 11340 11341 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 11342 if(x2 == 0 && y2 == 0) { 11343 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 11344 } else { 11345 RECT rect; 11346 rect.left = x; 11347 rect.top = y; 11348 rect.right = x2; 11349 rect.bottom = y2; 11350 11351 uint mode = DT_LEFT; 11352 if(alignment & TextAlignment.Right) 11353 mode = DT_RIGHT; 11354 else if(alignment & TextAlignment.Center) 11355 mode = DT_CENTER; 11356 11357 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 11358 if(alignment & TextAlignment.VerticalCenter) 11359 mode |= DT_VCENTER | DT_SINGLELINE; 11360 11361 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 11362 } 11363 11364 /* 11365 uint mode; 11366 11367 if(alignment & TextAlignment.Center) 11368 mode = TA_CENTER; 11369 11370 SetTextAlign(hdc, mode); 11371 */ 11372 } 11373 11374 int fontHeight() { 11375 TEXTMETRIC metric; 11376 if(GetTextMetricsW(hdc, &metric)) { 11377 return metric.tmHeight; 11378 } 11379 11380 return 16; // idk just guessing here, maybe we should throw 11381 } 11382 11383 void drawPixel(int x, int y) { 11384 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 11385 } 11386 11387 // The basic shapes, outlined 11388 11389 void drawLine(int x1, int y1, int x2, int y2) { 11390 MoveToEx(hdc, x1, y1, null); 11391 LineTo(hdc, x2, y2); 11392 } 11393 11394 void drawRectangle(int x, int y, int width, int height) { 11395 // FIXME: with a wider pen this might not draw quite right. im not sure. 11396 gdi.Rectangle(hdc, x, y, x + width, y + height); 11397 } 11398 11399 /// Arguments are the points of the bounding rectangle 11400 void drawEllipse(int x1, int y1, int x2, int y2) { 11401 Ellipse(hdc, x1, y1, x2, y2); 11402 } 11403 11404 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11405 if((start % (360*64)) == (finish % (360*64))) 11406 drawEllipse(x1, y1, x1 + width, y1 + height); 11407 else { 11408 import core.stdc.math; 11409 float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323; 11410 float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323; 11411 11412 auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2); 11413 auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2); 11414 auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2); 11415 auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2); 11416 11417 if(_activePen.color.a) 11418 Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11419 if(_fillColor.a) 11420 Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11421 } 11422 } 11423 11424 void drawPolygon(Point[] vertexes) { 11425 POINT[] points; 11426 points.length = vertexes.length; 11427 11428 foreach(i, p; vertexes) { 11429 points[i].x = p.x; 11430 points[i].y = p.y; 11431 } 11432 11433 Polygon(hdc, points.ptr, cast(int) points.length); 11434 } 11435 } 11436 11437 11438 // Mix this into the SimpleWindow class 11439 mixin template NativeSimpleWindowImplementation() { 11440 int curHidden = 0; // counter 11441 __gshared static bool[string] knownWinClasses; 11442 static bool altPressed = false; 11443 11444 HANDLE oldCursor; 11445 11446 void hideCursor () { 11447 if(curHidden == 0) 11448 oldCursor = SetCursor(null); 11449 ++curHidden; 11450 } 11451 11452 void showCursor () { 11453 --curHidden; 11454 if(curHidden == 0) { 11455 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 11456 } 11457 } 11458 11459 11460 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 11461 11462 void setMinSize (int minwidth, int minheight) { 11463 minWidth = minwidth; 11464 minHeight = minheight; 11465 } 11466 void setMaxSize (int maxwidth, int maxheight) { 11467 maxWidth = maxwidth; 11468 maxHeight = maxheight; 11469 } 11470 11471 // FIXME i'm not sure that Windows has this functionality 11472 // though it is nonessential anyway. 11473 void setResizeGranularity (int granx, int grany) {} 11474 11475 ScreenPainter getPainter(bool manualInvalidations) { 11476 return ScreenPainter(this, hwnd, manualInvalidations); 11477 } 11478 11479 HBITMAP buffer; 11480 11481 void setTitle(string title) { 11482 WCharzBuffer bfr = WCharzBuffer(title); 11483 SetWindowTextW(hwnd, bfr.ptr); 11484 } 11485 11486 string getTitle() { 11487 auto len = GetWindowTextLengthW(hwnd); 11488 if (!len) 11489 return null; 11490 wchar[256] tmpBuffer; 11491 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 11492 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 11493 auto str = buffer[0 .. len2]; 11494 return makeUtf8StringFromWindowsString(str); 11495 } 11496 11497 void move(int x, int y) { 11498 RECT rect; 11499 GetWindowRect(hwnd, &rect); 11500 // move it while maintaining the same size... 11501 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 11502 } 11503 11504 void resize(int w, int h) { 11505 RECT rect; 11506 GetWindowRect(hwnd, &rect); 11507 11508 RECT client; 11509 GetClientRect(hwnd, &client); 11510 11511 rect.right = rect.right - client.right + w; 11512 rect.bottom = rect.bottom - client.bottom + h; 11513 11514 // same position, new size for the client rectangle 11515 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 11516 11517 updateOpenglViewportIfNeeded(w, h); 11518 } 11519 11520 void moveResize (int x, int y, int w, int h) { 11521 // what's given is the client rectangle, we need to adjust 11522 11523 RECT rect; 11524 rect.left = x; 11525 rect.top = y; 11526 rect.right = w + x; 11527 rect.bottom = h + y; 11528 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 11529 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11530 11531 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 11532 updateOpenglViewportIfNeeded(w, h); 11533 if (windowResized !is null) windowResized(w, h); 11534 } 11535 11536 version(without_opengl) {} else { 11537 HGLRC ghRC; 11538 HDC ghDC; 11539 } 11540 11541 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 11542 string cnamec; 11543 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 11544 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 11545 cnamec = "DSimpleWindow"; 11546 } else { 11547 cnamec = sdpyWindowClass; 11548 } 11549 11550 WCharzBuffer cn = WCharzBuffer(cnamec); 11551 11552 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 11553 11554 if(cnamec !in knownWinClasses) { 11555 WNDCLASSEX wc; 11556 11557 // FIXME: I might be able to use cbWndExtra to hold the pointer back 11558 // to the object. Maybe. 11559 wc.cbSize = wc.sizeof; 11560 wc.cbClsExtra = 0; 11561 wc.cbWndExtra = 0; 11562 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 11563 wc.hCursor = LoadCursorW(null, IDC_ARROW); 11564 wc.hIcon = LoadIcon(hInstance, null); 11565 wc.hInstance = hInstance; 11566 wc.lpfnWndProc = &WndProc; 11567 wc.lpszClassName = cn.ptr; 11568 wc.hIconSm = null; 11569 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 11570 if(!RegisterClassExW(&wc)) 11571 throw new WindowsApiException("RegisterClassExW", GetLastError()); 11572 knownWinClasses[cnamec] = true; 11573 } 11574 11575 int style; 11576 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 11577 11578 // FIXME: windowType and customizationFlags 11579 final switch(windowType) { 11580 case WindowTypes.normal: 11581 if(resizability == Resizability.fixedSize) { 11582 style = WS_SYSMENU | WS_OVERLAPPED | WS_CAPTION; 11583 } else { 11584 style = WS_OVERLAPPEDWINDOW; 11585 } 11586 break; 11587 case WindowTypes.undecorated: 11588 style = WS_POPUP | WS_SYSMENU; 11589 break; 11590 case WindowTypes.eventOnly: 11591 _hidden = true; 11592 break; 11593 case WindowTypes.dropdownMenu: 11594 case WindowTypes.popupMenu: 11595 case WindowTypes.notification: 11596 style = WS_POPUP; 11597 flags |= WS_EX_NOACTIVATE; 11598 break; 11599 case WindowTypes.nestedChild: 11600 style = WS_CHILD; 11601 break; 11602 case WindowTypes.minimallyWrapped: 11603 assert(0, "construct minimally wrapped through the other ctor overlad"); 11604 } 11605 11606 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11607 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 11608 11609 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 11610 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 11611 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 11612 11613 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11614 setOpacity(255); 11615 11616 SimpleWindow.nativeMapping[hwnd] = this; 11617 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 11618 11619 if(windowType == WindowTypes.eventOnly) 11620 return; 11621 11622 HDC hdc = GetDC(hwnd); 11623 11624 11625 version(without_opengl) {} 11626 else { 11627 if(opengl == OpenGlOptions.yes) { 11628 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 11629 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 11630 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 11631 ghDC = hdc; 11632 PIXELFORMATDESCRIPTOR pfd; 11633 11634 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 11635 pfd.nVersion = 1; 11636 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 11637 pfd.dwLayerMask = PFD_MAIN_PLANE; 11638 pfd.iPixelType = PFD_TYPE_RGBA; 11639 pfd.cColorBits = 24; 11640 pfd.cDepthBits = 24; 11641 pfd.cAccumBits = 0; 11642 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 11643 11644 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 11645 11646 if (pixelformat == 0) 11647 throw new WindowsApiException("ChoosePixelFormat", GetLastError()); 11648 11649 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 11650 throw new WindowsApiException("SetPixelFormat", GetLastError()); 11651 11652 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 11653 // windoze is idiotic: we have to have OpenGL context to get function addresses 11654 // so we will create fake context to get that stupid address 11655 auto tmpcc = wglCreateContext(ghDC); 11656 if (tmpcc !is null) { 11657 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 11658 wglMakeCurrent(ghDC, tmpcc); 11659 wglInitOtherFunctions(); 11660 } 11661 } 11662 11663 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 11664 int[9] contextAttribs = [ 11665 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 11666 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 11667 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 11668 // for modern context, set "forward compatibility" flag too 11669 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 11670 0/*None*/, 11671 ]; 11672 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 11673 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 11674 // activate fallback mode 11675 // 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; 11676 ghRC = wglCreateContext(ghDC); 11677 } 11678 if (ghRC is null) 11679 throw new WindowsApiException("wglCreateContextAttribsARB", GetLastError()); 11680 } else { 11681 // try to do at least something 11682 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 11683 sdpyOpenGLContextVersion = 0; 11684 ghRC = wglCreateContext(ghDC); 11685 } 11686 if (ghRC is null) 11687 throw new WindowsApiException("wglCreateContext", GetLastError()); 11688 } 11689 } 11690 } 11691 11692 if(opengl == OpenGlOptions.no) { 11693 buffer = CreateCompatibleBitmap(hdc, width, height); 11694 11695 auto hdcBmp = CreateCompatibleDC(hdc); 11696 // make sure it's filled with a blank slate 11697 auto oldBmp = SelectObject(hdcBmp, buffer); 11698 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 11699 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 11700 gdi.Rectangle(hdcBmp, 0, 0, width, height); 11701 SelectObject(hdcBmp, oldBmp); 11702 SelectObject(hdcBmp, oldBrush); 11703 SelectObject(hdcBmp, oldPen); 11704 DeleteDC(hdcBmp); 11705 11706 bmpWidth = width; 11707 bmpHeight = height; 11708 11709 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 11710 } 11711 11712 // We want the window's client area to match the image size 11713 RECT rcClient, rcWindow; 11714 POINT ptDiff; 11715 GetClientRect(hwnd, &rcClient); 11716 GetWindowRect(hwnd, &rcWindow); 11717 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 11718 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 11719 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 11720 11721 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 11722 ShowWindow(hwnd, SW_SHOWNORMAL); 11723 } else { 11724 _hidden = true; 11725 } 11726 this._visibleForTheFirstTimeCalled = false; // hack! 11727 } 11728 11729 11730 void dispose() { 11731 if(buffer) 11732 DeleteObject(buffer); 11733 } 11734 11735 void closeWindow() { 11736 DestroyWindow(hwnd); 11737 } 11738 11739 bool setOpacity(ubyte alpha) { 11740 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 11741 } 11742 11743 HANDLE currentCursor; 11744 11745 // returns zero if it recognized the event 11746 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 11747 MouseEvent mouse; 11748 11749 void mouseEvent(bool isScreen, ulong mods) { 11750 auto x = LOWORD(lParam); 11751 auto y = HIWORD(lParam); 11752 if(isScreen) { 11753 POINT p; 11754 p.x = x; 11755 p.y = y; 11756 ScreenToClient(hwnd, &p); 11757 x = cast(ushort) p.x; 11758 y = cast(ushort) p.y; 11759 } 11760 11761 if(wind.resizability == Resizability.automaticallyScaleIfPossible) { 11762 x = cast(ushort)( x * wind._virtualWidth / wind._width ); 11763 y = cast(ushort)( y * wind._virtualHeight / wind._height ); 11764 } 11765 11766 mouse.x = x + offsetX; 11767 mouse.y = y + offsetY; 11768 11769 wind.mdx(mouse); 11770 mouse.modifierState = cast(int) mods; 11771 mouse.window = wind; 11772 11773 if(wind.handleMouseEvent) 11774 wind.handleMouseEvent(mouse); 11775 } 11776 11777 switch(msg) { 11778 case WM_GETMINMAXINFO: 11779 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 11780 11781 if(wind.minWidth > 0) { 11782 RECT rect; 11783 rect.left = 100; 11784 rect.top = 100; 11785 rect.right = wind.minWidth + 100; 11786 rect.bottom = wind.minHeight + 100; 11787 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11788 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11789 11790 mmi.ptMinTrackSize.x = rect.right - rect.left; 11791 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 11792 } 11793 11794 if(wind.maxWidth < int.max) { 11795 RECT rect; 11796 rect.left = 100; 11797 rect.top = 100; 11798 rect.right = wind.maxWidth + 100; 11799 rect.bottom = wind.maxHeight + 100; 11800 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11801 throw new WindowsApiException("AdjustWindowRect", GetLastError()); 11802 11803 mmi.ptMaxTrackSize.x = rect.right - rect.left; 11804 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 11805 } 11806 break; 11807 case WM_CHAR: 11808 wchar c = cast(wchar) wParam; 11809 if(wind.handleCharEvent) 11810 wind.handleCharEvent(cast(dchar) c); 11811 break; 11812 case WM_SETFOCUS: 11813 case WM_KILLFOCUS: 11814 wind._focused = (msg == WM_SETFOCUS); 11815 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 11816 if(wind.onFocusChange) 11817 wind.onFocusChange(msg == WM_SETFOCUS); 11818 break; 11819 11820 case WM_SYSKEYDOWN: 11821 goto case; 11822 case WM_SYSKEYUP: 11823 if(lParam & (1 << 29)) { 11824 goto case; 11825 } else { 11826 // no window has keyboard focus 11827 goto default; 11828 } 11829 case WM_KEYDOWN: 11830 case WM_KEYUP: 11831 KeyEvent ev; 11832 ev.key = cast(Key) wParam; 11833 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 11834 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 11835 11836 ev.hardwareCode = (lParam & 0xff0000) >> 16; 11837 11838 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 11839 ev.modifierState |= ModifierState.shift; 11840 //k8: this doesn't work; thanks for nothing, windows 11841 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 11842 ev.modifierState |= ModifierState.alt;*/ 11843 // this never seems to actually be set 11844 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11845 11846 if (wParam == 0x12) { 11847 altPressed = (msg == WM_SYSKEYDOWN); 11848 } 11849 11850 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 11851 altPressed = false; 11852 } 11853 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 11854 11855 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11856 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 11857 ev.modifierState |= ModifierState.ctrl; 11858 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 11859 ev.modifierState |= ModifierState.windows; 11860 if(GetKeyState(Key.NumLock)) 11861 ev.modifierState |= ModifierState.numLock; 11862 if(GetKeyState(Key.CapsLock)) 11863 ev.modifierState |= ModifierState.capsLock; 11864 11865 /+ 11866 // we always want to send the character too, so let's convert it 11867 ubyte[256] state; 11868 wchar[16] buffer; 11869 GetKeyboardState(state.ptr); 11870 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 11871 11872 foreach(dchar d; buffer) { 11873 ev.character = d; 11874 break; 11875 } 11876 +/ 11877 11878 ev.window = wind; 11879 if(wind.handleKeyEvent) 11880 wind.handleKeyEvent(ev); 11881 break; 11882 case 0x020a /*WM_MOUSEWHEEL*/: 11883 // send click 11884 mouse.type = cast(MouseEventType) 1; 11885 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 11886 mouseEvent(true, LOWORD(wParam)); 11887 11888 // also send release 11889 mouse.type = cast(MouseEventType) 2; 11890 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 11891 mouseEvent(true, LOWORD(wParam)); 11892 break; 11893 case WM_MOUSEMOVE: 11894 mouse.type = cast(MouseEventType) 0; 11895 mouseEvent(false, wParam); 11896 break; 11897 case WM_LBUTTONDOWN: 11898 case WM_LBUTTONDBLCLK: 11899 mouse.type = cast(MouseEventType) 1; 11900 mouse.button = MouseButton.left; 11901 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 11902 mouseEvent(false, wParam); 11903 break; 11904 case WM_LBUTTONUP: 11905 mouse.type = cast(MouseEventType) 2; 11906 mouse.button = MouseButton.left; 11907 mouseEvent(false, wParam); 11908 break; 11909 case WM_RBUTTONDOWN: 11910 case WM_RBUTTONDBLCLK: 11911 mouse.type = cast(MouseEventType) 1; 11912 mouse.button = MouseButton.right; 11913 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 11914 mouseEvent(false, wParam); 11915 break; 11916 case WM_RBUTTONUP: 11917 mouse.type = cast(MouseEventType) 2; 11918 mouse.button = MouseButton.right; 11919 mouseEvent(false, wParam); 11920 break; 11921 case WM_MBUTTONDOWN: 11922 case WM_MBUTTONDBLCLK: 11923 mouse.type = cast(MouseEventType) 1; 11924 mouse.button = MouseButton.middle; 11925 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 11926 mouseEvent(false, wParam); 11927 break; 11928 case WM_MBUTTONUP: 11929 mouse.type = cast(MouseEventType) 2; 11930 mouse.button = MouseButton.middle; 11931 mouseEvent(false, wParam); 11932 break; 11933 case WM_XBUTTONDOWN: 11934 case WM_XBUTTONDBLCLK: 11935 mouse.type = cast(MouseEventType) 1; 11936 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 11937 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 11938 mouseEvent(false, wParam); 11939 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 11940 case WM_XBUTTONUP: 11941 mouse.type = cast(MouseEventType) 2; 11942 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 11943 mouseEvent(false, wParam); 11944 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 11945 11946 default: return 1; 11947 } 11948 return 0; 11949 } 11950 11951 HWND hwnd; 11952 private int oldWidth; 11953 private int oldHeight; 11954 private bool inSizeMove; 11955 11956 /++ 11957 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. 11958 11959 History: 11960 Added November 23, 2021 11961 11962 Not fully stable, may be moved out of the impl struct. 11963 11964 Default value changed to `true` on February 15, 2021 11965 +/ 11966 bool doLiveResizing = true; 11967 11968 package int bmpWidth; 11969 package int bmpHeight; 11970 11971 // the extern(Windows) wndproc should just forward to this 11972 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 11973 try { 11974 assert(hwnd is this.hwnd); 11975 11976 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 11977 switch(msg) { 11978 case WM_MENUCHAR: // menu active but key not associated with a thing. 11979 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 11980 // The main things we can do are select, execute, close, or ignore 11981 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 11982 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 11983 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 11984 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 11985 11986 // returns the value in the *high order word* of the return value 11987 // hence the << 16 11988 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 11989 case WM_SETCURSOR: 11990 if(cast(HWND) wParam !is hwnd) 11991 return 0; // further processing elsewhere 11992 11993 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 11994 SetCursor(this.curHidden > 0 ? null : currentCursor); 11995 return 1; 11996 } else { 11997 return DefWindowProc(hwnd, msg, wParam, lParam); 11998 } 11999 //break; 12000 12001 case WM_CLOSE: 12002 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 12003 break; 12004 case WM_DESTROY: 12005 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 12006 SimpleWindow.nativeMapping.remove(hwnd); 12007 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 12008 12009 bool anyImportant = false; 12010 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 12011 if(w.beingOpenKeepsAppOpen) { 12012 anyImportant = true; 12013 break; 12014 } 12015 if(!anyImportant) { 12016 PostQuitMessage(0); 12017 } 12018 break; 12019 case 0x02E0 /*WM_DPICHANGED*/: 12020 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 12021 12022 RECT* prcNewWindow = cast(RECT*)lParam; 12023 // docs say this is the recommended position and we should honor it 12024 SetWindowPos(hwnd, 12025 null, 12026 prcNewWindow.left, 12027 prcNewWindow.top, 12028 prcNewWindow.right - prcNewWindow.left, 12029 prcNewWindow.bottom - prcNewWindow.top, 12030 SWP_NOZORDER | SWP_NOACTIVATE); 12031 12032 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 12033 // im not sure it is completely correct 12034 // but without it the tabs and such do look weird as things change. 12035 if(SystemParametersInfoForDpi) { 12036 LOGFONT lfText; 12037 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 12038 HFONT hFontNew = CreateFontIndirect(&lfText); 12039 if (hFontNew) 12040 { 12041 //DeleteObject(hFontOld); 12042 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 12043 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 12044 return TRUE; 12045 } 12046 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 12047 } 12048 } 12049 12050 if(this.onDpiChanged) 12051 this.onDpiChanged(); 12052 break; 12053 case WM_ENTERIDLE: 12054 // when a menu is up, it stops normal event processing (modal message loop) 12055 // but this at least gives us a chance to SOMETIMES catch up 12056 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12057 SimpleWindow.processAllCustomEvents; 12058 SimpleWindow.processAllCustomEvents; 12059 SleepEx(0, true); 12060 break; 12061 case WM_SIZE: 12062 if(wParam == 1 /* SIZE_MINIMIZED */) 12063 break; 12064 _width = LOWORD(lParam); 12065 _height = HIWORD(lParam); 12066 12067 // I want to avoid tearing in the windows (my code is inefficient 12068 // so this is a hack around that) so while sizing, we don't trigger, 12069 // but we do want to trigger on events like mazimize. 12070 if(!inSizeMove || doLiveResizing) 12071 goto size_changed; 12072 break; 12073 /+ 12074 case WM_SIZING: 12075 import std.stdio; writeln("size"); 12076 break; 12077 +/ 12078 // I don't like the tearing I get when redrawing on WM_SIZE 12079 // (I know there's other ways to fix that but I don't like that behavior anyway) 12080 // so instead it is going to redraw only at the end of a size. 12081 case 0x0231: /* WM_ENTERSIZEMOVE */ 12082 inSizeMove = true; 12083 break; 12084 case 0x0232: /* WM_EXITSIZEMOVE */ 12085 inSizeMove = false; 12086 12087 size_changed: 12088 12089 // nothing relevant changed, don't bother redrawing 12090 if(oldWidth == _width && oldHeight == _height) { 12091 break; 12092 } 12093 12094 // note: OpenGL windows don't use a backing bmp, so no need to change them 12095 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12096 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12097 // gotta get the double buffer bmp to match the window 12098 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12099 12100 // if it is auto-scaled, we keep the backing bitmap the same size all the time 12101 if(resizability != Resizability.automaticallyScaleIfPossible) 12102 if(_width > bmpWidth || _height > bmpHeight) { 12103 auto hdc = GetDC(hwnd); 12104 auto oldBuffer = buffer; 12105 buffer = CreateCompatibleBitmap(hdc, _width, _height); 12106 12107 auto hdcBmp = CreateCompatibleDC(hdc); 12108 auto oldBmp = SelectObject(hdcBmp, buffer); 12109 12110 auto hdcOldBmp = CreateCompatibleDC(hdc); 12111 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12112 12113 /+ 12114 RECT r; 12115 r.left = 0; 12116 r.top = 0; 12117 r.right = width; 12118 r.bottom = height; 12119 auto c = Color.green; 12120 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12121 FillRect(hdcBmp, &r, brush); 12122 DeleteObject(brush); 12123 +/ 12124 12125 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12126 12127 bmpWidth = _width; 12128 bmpHeight = _height; 12129 12130 SelectObject(hdcOldBmp, oldOldBmp); 12131 DeleteDC(hdcOldBmp); 12132 12133 SelectObject(hdcBmp, oldBmp); 12134 DeleteDC(hdcBmp); 12135 12136 ReleaseDC(hwnd, hdc); 12137 12138 DeleteObject(oldBuffer); 12139 } 12140 } 12141 12142 updateOpenglViewportIfNeeded(_width, _height); 12143 12144 if(resizability != Resizability.automaticallyScaleIfPossible) 12145 if(windowResized !is null) 12146 windowResized(_width, _height); 12147 12148 if(inSizeMove) { 12149 SimpleWindow.processAllCustomEvents(); 12150 SimpleWindow.processAllCustomEvents(); 12151 } else { 12152 // when it is all done, make sure everything is freshly drawn or there might be 12153 // weird bugs left. 12154 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12155 } 12156 12157 oldWidth = this._width; 12158 oldHeight = this._height; 12159 break; 12160 case WM_ERASEBKGND: 12161 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12162 if (!this._visibleForTheFirstTimeCalled) { 12163 this._visibleForTheFirstTimeCalled = true; 12164 if (this.visibleForTheFirstTime !is null) { 12165 this.visibleForTheFirstTime(); 12166 } 12167 } 12168 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12169 version(without_opengl) {} else { 12170 if (openglMode == OpenGlOptions.yes) return 1; 12171 } 12172 // call windows default handler, so it can paint standard controls 12173 goto default; 12174 case WM_CTLCOLORBTN: 12175 case WM_CTLCOLORSTATIC: 12176 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12177 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12178 GetSysColorBrush(COLOR_3DFACE); 12179 //break; 12180 case WM_SHOWWINDOW: 12181 this._visible = (wParam != 0); 12182 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12183 this._visibleForTheFirstTimeCalled = true; 12184 if (this.visibleForTheFirstTime !is null) { 12185 this.visibleForTheFirstTime(); 12186 } 12187 } 12188 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12189 break; 12190 case WM_PAINT: { 12191 if (!this._visibleForTheFirstTimeCalled) { 12192 this._visibleForTheFirstTimeCalled = true; 12193 if (this.visibleForTheFirstTime !is null) { 12194 this.visibleForTheFirstTime(); 12195 } 12196 } 12197 12198 BITMAP bm; 12199 PAINTSTRUCT ps; 12200 12201 HDC hdc = BeginPaint(hwnd, &ps); 12202 12203 if(openglMode == OpenGlOptions.no) { 12204 12205 HDC hdcMem = CreateCompatibleDC(hdc); 12206 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12207 12208 GetObject(buffer, bm.sizeof, &bm); 12209 12210 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12211 if(resizability == Resizability.automaticallyScaleIfPossible) 12212 StretchBlt(hdc, 0, 0, this._width, this._height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12213 else 12214 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12215 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12216 12217 SelectObject(hdcMem, hbmOld); 12218 DeleteDC(hdcMem); 12219 EndPaint(hwnd, &ps); 12220 } else { 12221 EndPaint(hwnd, &ps); 12222 version(without_opengl) {} else 12223 redrawOpenGlSceneSoon(); 12224 } 12225 } break; 12226 default: 12227 return DefWindowProc(hwnd, msg, wParam, lParam); 12228 } 12229 return 0; 12230 12231 } 12232 catch(Throwable t) { 12233 sdpyPrintDebugString(t.toString); 12234 return 0; 12235 } 12236 } 12237 } 12238 12239 mixin template NativeImageImplementation() { 12240 HBITMAP handle; 12241 ubyte* rawData; 12242 12243 final: 12244 12245 Color getPixel(int x, int y) { 12246 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12247 // remember, bmps are upside down 12248 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12249 12250 Color c; 12251 if(enableAlpha) 12252 c.a = rawData[offset + 3]; 12253 else 12254 c.a = 255; 12255 c.b = rawData[offset + 0]; 12256 c.g = rawData[offset + 1]; 12257 c.r = rawData[offset + 2]; 12258 c.unPremultiply(); 12259 return c; 12260 } 12261 12262 void setPixel(int x, int y, Color c) { 12263 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12264 // remember, bmps are upside down 12265 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12266 12267 if(enableAlpha) 12268 c.premultiply(); 12269 12270 rawData[offset + 0] = c.b; 12271 rawData[offset + 1] = c.g; 12272 rawData[offset + 2] = c.r; 12273 if(enableAlpha) 12274 rawData[offset + 3] = c.a; 12275 } 12276 12277 void convertToRgbaBytes(ubyte[] where) { 12278 assert(where.length == this.width * this.height * 4); 12279 12280 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12281 int idx = 0; 12282 int offset = itemsPerLine * (height - 1); 12283 // remember, bmps are upside down 12284 for(int y = height - 1; y >= 0; y--) { 12285 auto offsetStart = offset; 12286 for(int x = 0; x < width; x++) { 12287 where[idx + 0] = rawData[offset + 2]; // r 12288 where[idx + 1] = rawData[offset + 1]; // g 12289 where[idx + 2] = rawData[offset + 0]; // b 12290 if(enableAlpha) { 12291 where[idx + 3] = rawData[offset + 3]; // a 12292 unPremultiplyRgba(where[idx .. idx + 4]); 12293 offset++; 12294 } else 12295 where[idx + 3] = 255; // a 12296 idx += 4; 12297 offset += 3; 12298 } 12299 12300 offset = offsetStart - itemsPerLine; 12301 } 12302 } 12303 12304 void setFromRgbaBytes(in ubyte[] what) { 12305 assert(what.length == this.width * this.height * 4); 12306 12307 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 12308 int idx = 0; 12309 int offset = itemsPerLine * (height - 1); 12310 // remember, bmps are upside down 12311 for(int y = height - 1; y >= 0; y--) { 12312 auto offsetStart = offset; 12313 for(int x = 0; x < width; x++) { 12314 if(enableAlpha) { 12315 auto a = what[idx + 3]; 12316 12317 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 12318 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 12319 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 12320 rawData[offset + 3] = a; // a 12321 //premultiplyBgra(rawData[offset .. offset + 4]); 12322 offset++; 12323 } else { 12324 rawData[offset + 2] = what[idx + 0]; // r 12325 rawData[offset + 1] = what[idx + 1]; // g 12326 rawData[offset + 0] = what[idx + 2]; // b 12327 } 12328 idx += 4; 12329 offset += 3; 12330 } 12331 12332 offset = offsetStart - itemsPerLine; 12333 } 12334 } 12335 12336 12337 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 12338 BITMAPINFO infoheader; 12339 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 12340 infoheader.bmiHeader.biWidth = width; 12341 infoheader.bmiHeader.biHeight = height; 12342 infoheader.bmiHeader.biPlanes = 1; 12343 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 12344 infoheader.bmiHeader.biCompression = BI_RGB; 12345 12346 handle = CreateDIBSection( 12347 null, 12348 &infoheader, 12349 DIB_RGB_COLORS, 12350 cast(void**) &rawData, 12351 null, 12352 0); 12353 if(handle is null) 12354 throw new WindowsApiException("create image failed", GetLastError()); 12355 12356 } 12357 12358 void dispose() { 12359 DeleteObject(handle); 12360 } 12361 } 12362 12363 enum KEY_ESCAPE = 27; 12364 } 12365 version(X11) { 12366 /// This is the default font used. You might change this before doing anything else with 12367 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 12368 /// for cross-platform compatibility. 12369 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12370 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12371 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 12372 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 12373 12374 alias int delegate(XEvent) NativeEventHandler; 12375 alias Window NativeWindowHandle; 12376 12377 enum KEY_ESCAPE = 9; 12378 12379 mixin template NativeScreenPainterImplementation() { 12380 Display* display; 12381 Drawable d; 12382 Drawable destiny; 12383 12384 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 12385 GC gc; 12386 12387 __gshared bool fontAttempted; 12388 12389 __gshared XFontStruct* defaultfont; 12390 __gshared XFontSet defaultfontset; 12391 12392 XFontStruct* font; 12393 XFontSet fontset; 12394 12395 void create(NativeWindowHandle window) { 12396 this.display = XDisplayConnection.get(); 12397 12398 Drawable buffer = None; 12399 if(auto sw = cast(SimpleWindow) this.window) { 12400 buffer = sw.impl.buffer; 12401 this.destiny = cast(Drawable) window; 12402 } else { 12403 buffer = cast(Drawable) window; 12404 this.destiny = None; 12405 } 12406 12407 this.d = cast(Drawable) buffer; 12408 12409 auto dgc = DefaultGC(display, DefaultScreen(display)); 12410 12411 this.gc = XCreateGC(display, d, 0, null); 12412 12413 XCopyGC(display, dgc, 0xffffffff, this.gc); 12414 12415 ensureDefaultFontLoaded(); 12416 12417 font = defaultfont; 12418 fontset = defaultfontset; 12419 12420 if(font) { 12421 XSetFont(display, gc, font.fid); 12422 } 12423 } 12424 12425 static void ensureDefaultFontLoaded() { 12426 if(!fontAttempted) { 12427 auto display = XDisplayConnection.get; 12428 auto font = XLoadQueryFont(display, xfontstr.ptr); 12429 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 12430 if(font is null) { 12431 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 12432 font = XLoadQueryFont(display, xfontstr.ptr); 12433 } 12434 12435 char** lol; 12436 int lol2; 12437 char* lol3; 12438 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 12439 12440 fontAttempted = true; 12441 12442 defaultfont = font; 12443 defaultfontset = fontset; 12444 } 12445 } 12446 12447 arsd.color.Rectangle _clipRectangle; 12448 void setClipRectangle(int x, int y, int width, int height) { 12449 auto old = _clipRectangle; 12450 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12451 if(old == _clipRectangle) 12452 return; 12453 12454 if(width == 0 || height == 0) { 12455 XSetClipMask(display, gc, None); 12456 12457 if(xrenderPicturePainter) { 12458 12459 XRectangle[1] rects; 12460 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 12461 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12462 } 12463 12464 version(with_xft) { 12465 if(xftFont is null || xftDraw is null) 12466 return; 12467 XftDrawSetClip(xftDraw, null); 12468 } 12469 } else { 12470 XRectangle[1] rects; 12471 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 12472 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 12473 12474 if(xrenderPicturePainter) 12475 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12476 12477 version(with_xft) { 12478 if(xftFont is null || xftDraw is null) 12479 return; 12480 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 12481 } 12482 } 12483 } 12484 12485 version(with_xft) { 12486 XftFont* xftFont; 12487 XftDraw* xftDraw; 12488 12489 XftColor xftColor; 12490 12491 void updateXftColor() { 12492 if(xftFont is null) 12493 return; 12494 12495 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 12496 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 12497 12498 XftColorAllocValue( 12499 display, 12500 DefaultVisual(display, DefaultScreen(display)), 12501 DefaultColormap(display, 0), 12502 &colorIn, 12503 &xftColor 12504 ); 12505 } 12506 } 12507 12508 private OperatingSystemFont _activeFont; 12509 void setFont(OperatingSystemFont font) { 12510 _activeFont = font; 12511 version(with_xft) { 12512 if(font && font.isXft && font.xftFont) 12513 this.xftFont = font.xftFont; 12514 else 12515 this.xftFont = null; 12516 12517 if(this.xftFont) { 12518 if(xftDraw is null) { 12519 xftDraw = XftDrawCreate( 12520 display, 12521 d, 12522 DefaultVisual(display, DefaultScreen(display)), 12523 DefaultColormap(display, 0) 12524 ); 12525 12526 updateXftColor(); 12527 } 12528 12529 return; 12530 } 12531 } 12532 12533 if(font && font.font) { 12534 this.font = font.font; 12535 this.fontset = font.fontset; 12536 XSetFont(display, gc, font.font.fid); 12537 } else { 12538 this.font = defaultfont; 12539 this.fontset = defaultfontset; 12540 } 12541 12542 } 12543 12544 private Picture xrenderPicturePainter; 12545 12546 bool manualInvalidations; 12547 void invalidateRect(Rectangle invalidRect) { 12548 // FIXME if manualInvalidations 12549 } 12550 12551 void dispose() { 12552 this.rasterOp = RasterOp.normal; 12553 12554 if(xrenderPicturePainter) { 12555 XRenderFreePicture(display, xrenderPicturePainter); 12556 xrenderPicturePainter = None; 12557 } 12558 12559 // FIXME: this.window.width/height is probably wrong 12560 12561 // src x,y then dest x, y 12562 if(destiny != None) { 12563 // FIXME: if manual invalidations we can actually only copy some of the area. 12564 // if(manualInvalidations) 12565 XSetClipMask(display, gc, None); 12566 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 12567 } 12568 12569 XFreeGC(display, gc); 12570 12571 version(with_xft) 12572 if(xftDraw) { 12573 XftDrawDestroy(xftDraw); 12574 xftDraw = null; 12575 } 12576 12577 /+ 12578 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 12579 if(font && font !is defaultfont) { 12580 XFreeFont(display, font); 12581 font = null; 12582 } 12583 if(fontset && fontset !is defaultfontset) { 12584 XFreeFontSet(display, fontset); 12585 fontset = null; 12586 } 12587 +/ 12588 XFlush(display); 12589 12590 if(window.paintingFinishedDg !is null) 12591 window.paintingFinishedDg()(); 12592 } 12593 12594 bool backgroundIsNotTransparent = true; 12595 bool foregroundIsNotTransparent = true; 12596 12597 bool _penInitialized = false; 12598 Pen _activePen; 12599 12600 Color _outlineColor; 12601 Color _fillColor; 12602 12603 @property void pen(Pen p) { 12604 if(_penInitialized && p == _activePen) { 12605 return; 12606 } 12607 _penInitialized = true; 12608 _activePen = p; 12609 _outlineColor = p.color; 12610 12611 int style; 12612 12613 byte dashLength; 12614 12615 final switch(p.style) { 12616 case Pen.Style.Solid: 12617 style = 0 /*LineSolid*/; 12618 break; 12619 case Pen.Style.Dashed: 12620 style = 1 /*LineOnOffDash*/; 12621 dashLength = 4; 12622 break; 12623 case Pen.Style.Dotted: 12624 style = 1 /*LineOnOffDash*/; 12625 dashLength = 1; 12626 break; 12627 } 12628 12629 XSetLineAttributes(display, gc, p.width, style, 0, 0); 12630 if(dashLength) 12631 XSetDashes(display, gc, 0, &dashLength, 1); 12632 12633 if(p.color.a == 0) { 12634 foregroundIsNotTransparent = false; 12635 return; 12636 } 12637 12638 foregroundIsNotTransparent = true; 12639 12640 XSetForeground(display, gc, colorToX(p.color, display)); 12641 12642 version(with_xft) 12643 updateXftColor(); 12644 } 12645 12646 RasterOp _currentRasterOp; 12647 bool _currentRasterOpInitialized = false; 12648 @property void rasterOp(RasterOp op) { 12649 if(_currentRasterOpInitialized && _currentRasterOp == op) 12650 return; 12651 _currentRasterOp = op; 12652 _currentRasterOpInitialized = true; 12653 int mode; 12654 final switch(op) { 12655 case RasterOp.normal: 12656 mode = GXcopy; 12657 break; 12658 case RasterOp.xor: 12659 mode = GXxor; 12660 break; 12661 } 12662 XSetFunction(display, gc, mode); 12663 } 12664 12665 12666 bool _fillColorInitialized = false; 12667 12668 @property void fillColor(Color c) { 12669 if(_fillColorInitialized && _fillColor == c) 12670 return; // already good, no need to waste time calling it 12671 _fillColor = c; 12672 _fillColorInitialized = true; 12673 if(c.a == 0) { 12674 backgroundIsNotTransparent = false; 12675 return; 12676 } 12677 12678 backgroundIsNotTransparent = true; 12679 12680 XSetBackground(display, gc, colorToX(c, display)); 12681 12682 } 12683 12684 void swapColors() { 12685 auto tmp = _fillColor; 12686 fillColor = _outlineColor; 12687 auto newPen = _activePen; 12688 newPen.color = tmp; 12689 pen(newPen); 12690 } 12691 12692 uint colorToX(Color c, Display* display) { 12693 auto visual = DefaultVisual(display, DefaultScreen(display)); 12694 import core.bitop; 12695 uint color = 0; 12696 { 12697 auto startBit = bsf(visual.red_mask); 12698 auto lastBit = bsr(visual.red_mask); 12699 auto r = cast(uint) c.r; 12700 r >>= 7 - (lastBit - startBit); 12701 r <<= startBit; 12702 color |= r; 12703 } 12704 { 12705 auto startBit = bsf(visual.green_mask); 12706 auto lastBit = bsr(visual.green_mask); 12707 auto g = cast(uint) c.g; 12708 g >>= 7 - (lastBit - startBit); 12709 g <<= startBit; 12710 color |= g; 12711 } 12712 { 12713 auto startBit = bsf(visual.blue_mask); 12714 auto lastBit = bsr(visual.blue_mask); 12715 auto b = cast(uint) c.b; 12716 b >>= 7 - (lastBit - startBit); 12717 b <<= startBit; 12718 color |= b; 12719 } 12720 12721 12722 12723 return color; 12724 } 12725 12726 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12727 // source x, source y 12728 if(ix >= i.width) return; 12729 if(iy >= i.height) return; 12730 if(ix + w > i.width) w = i.width - ix; 12731 if(iy + h > i.height) h = i.height - iy; 12732 if(i.usingXshm) 12733 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 12734 else 12735 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 12736 } 12737 12738 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12739 if(s.enableAlpha) { 12740 // the Sprite must be created first, meaning if we're here, XRender is already loaded 12741 if(this.xrenderPicturePainter == None) { 12742 XRenderPictureAttributes attrs; 12743 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 12744 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 12745 12746 // need to initialize the clip 12747 XRectangle[1] rects; 12748 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 12749 12750 if(_clipRectangle != Rectangle.init) 12751 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12752 } 12753 12754 XRenderComposite( 12755 display, 12756 3, // PicOpOver 12757 s.xrenderPicture, 12758 None, 12759 this.xrenderPicturePainter, 12760 ix, 12761 iy, 12762 0, 12763 0, 12764 x, 12765 y, 12766 w ? w : s.width, 12767 h ? h : s.height 12768 ); 12769 } else { 12770 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 12771 } 12772 } 12773 12774 int fontHeight() { 12775 version(with_xft) 12776 if(xftFont !is null) 12777 return xftFont.height; 12778 if(font) 12779 return font.max_bounds.ascent + font.max_bounds.descent; 12780 return 12; // pretty common default... 12781 } 12782 12783 int textWidth(in char[] line) { 12784 version(with_xft) 12785 if(xftFont) { 12786 if(line.length == 0) 12787 return 0; 12788 XGlyphInfo extents; 12789 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 12790 return extents.width; 12791 } 12792 12793 if(fontset) { 12794 if(line.length == 0) 12795 return 0; 12796 XRectangle rect; 12797 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 12798 12799 return rect.width; 12800 } 12801 12802 if(font) 12803 // FIXME: unicode 12804 return XTextWidth( font, line.ptr, cast(int) line.length); 12805 else 12806 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 12807 } 12808 12809 Size textSize(in char[] text) { 12810 auto maxWidth = 0; 12811 auto lineHeight = fontHeight; 12812 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 12813 foreach(line; text.split('\n')) { 12814 int textWidth = this.textWidth(line); 12815 if(textWidth > maxWidth) 12816 maxWidth = textWidth; 12817 h += lineHeight + 4; 12818 } 12819 return Size(maxWidth, h); 12820 } 12821 12822 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 12823 const(char)[] text; 12824 version(with_xft) 12825 if(xftFont) { 12826 text = originalText; 12827 goto loaded; 12828 } 12829 12830 if(fontset) 12831 text = originalText; 12832 else { 12833 text.reserve(originalText.length); 12834 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 12835 // then strip the rest so there isn't garbage 12836 foreach(dchar ch; originalText) 12837 if(ch < 256) 12838 text ~= cast(ubyte) ch; 12839 else 12840 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 12841 } 12842 loaded: 12843 if(text.length == 0) 12844 return; 12845 12846 // FIXME: should we clip it to the bounding box? 12847 int textHeight = fontHeight; 12848 12849 auto lines = text.split('\n'); 12850 12851 const lineHeight = textHeight; 12852 textHeight *= lines.length; 12853 12854 int cy = y; 12855 12856 if(alignment & TextAlignment.VerticalBottom) { 12857 if(y2 <= 0) 12858 return; 12859 auto h = y2 - y; 12860 if(h > textHeight) { 12861 cy += h - textHeight; 12862 cy -= lineHeight / 2; 12863 } 12864 } else if(alignment & TextAlignment.VerticalCenter) { 12865 if(y2 <= 0) 12866 return; 12867 auto h = y2 - y; 12868 if(textHeight < h) { 12869 cy += (h - textHeight) / 2; 12870 //cy -= lineHeight / 4; 12871 } 12872 } 12873 12874 foreach(line; text.split('\n')) { 12875 int textWidth = this.textWidth(line); 12876 12877 int px = x, py = cy; 12878 12879 if(alignment & TextAlignment.Center) { 12880 if(x2 <= 0) 12881 return; 12882 auto w = x2 - x; 12883 if(w > textWidth) 12884 px += (w - textWidth) / 2; 12885 } else if(alignment & TextAlignment.Right) { 12886 if(x2 <= 0) 12887 return; 12888 auto pos = x2 - textWidth; 12889 if(pos > x) 12890 px = pos; 12891 } 12892 12893 version(with_xft) 12894 if(xftFont) { 12895 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 12896 12897 goto carry_on; 12898 } 12899 12900 if(fontset) 12901 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 12902 else 12903 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 12904 carry_on: 12905 cy += lineHeight + 4; 12906 } 12907 } 12908 12909 void drawPixel(int x, int y) { 12910 XDrawPoint(display, d, gc, x, y); 12911 } 12912 12913 // The basic shapes, outlined 12914 12915 void drawLine(int x1, int y1, int x2, int y2) { 12916 if(foregroundIsNotTransparent) 12917 XDrawLine(display, d, gc, x1, y1, x2, y2); 12918 } 12919 12920 void drawRectangle(int x, int y, int width, int height) { 12921 if(backgroundIsNotTransparent) { 12922 swapColors(); 12923 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 12924 swapColors(); 12925 } 12926 // 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 12927 if(foregroundIsNotTransparent) 12928 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 12929 } 12930 12931 /// Arguments are the points of the bounding rectangle 12932 void drawEllipse(int x1, int y1, int x2, int y2) { 12933 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 12934 } 12935 12936 // NOTE: start and finish are in units of degrees * 64 12937 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 12938 if(backgroundIsNotTransparent) { 12939 swapColors(); 12940 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 12941 swapColors(); 12942 } 12943 if(foregroundIsNotTransparent) { 12944 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 12945 // Windows draws the straight lines on the edges too so FIXME sort of 12946 } 12947 } 12948 12949 void drawPolygon(Point[] vertexes) { 12950 XPoint[16] pointsBuffer; 12951 XPoint[] points; 12952 if(vertexes.length <= pointsBuffer.length) 12953 points = pointsBuffer[0 .. vertexes.length]; 12954 else 12955 points.length = vertexes.length; 12956 12957 foreach(i, p; vertexes) { 12958 points[i].x = cast(short) p.x; 12959 points[i].y = cast(short) p.y; 12960 } 12961 12962 if(backgroundIsNotTransparent) { 12963 swapColors(); 12964 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 12965 swapColors(); 12966 } 12967 if(foregroundIsNotTransparent) { 12968 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 12969 } 12970 } 12971 } 12972 12973 /* XRender { */ 12974 12975 struct XRenderColor { 12976 ushort red; 12977 ushort green; 12978 ushort blue; 12979 ushort alpha; 12980 } 12981 12982 alias Picture = XID; 12983 alias PictFormat = XID; 12984 12985 struct XGlyphInfo { 12986 ushort width; 12987 ushort height; 12988 short x; 12989 short y; 12990 short xOff; 12991 short yOff; 12992 } 12993 12994 struct XRenderDirectFormat { 12995 short red; 12996 short redMask; 12997 short green; 12998 short greenMask; 12999 short blue; 13000 short blueMask; 13001 short alpha; 13002 short alphaMask; 13003 } 13004 13005 struct XRenderPictFormat { 13006 PictFormat id; 13007 int type; 13008 int depth; 13009 XRenderDirectFormat direct; 13010 Colormap colormap; 13011 } 13012 13013 enum PictFormatID = (1 << 0); 13014 enum PictFormatType = (1 << 1); 13015 enum PictFormatDepth = (1 << 2); 13016 enum PictFormatRed = (1 << 3); 13017 enum PictFormatRedMask =(1 << 4); 13018 enum PictFormatGreen = (1 << 5); 13019 enum PictFormatGreenMask=(1 << 6); 13020 enum PictFormatBlue = (1 << 7); 13021 enum PictFormatBlueMask =(1 << 8); 13022 enum PictFormatAlpha = (1 << 9); 13023 enum PictFormatAlphaMask=(1 << 10); 13024 enum PictFormatColormap =(1 << 11); 13025 13026 struct XRenderPictureAttributes { 13027 int repeat; 13028 Picture alpha_map; 13029 int alpha_x_origin; 13030 int alpha_y_origin; 13031 int clip_x_origin; 13032 int clip_y_origin; 13033 Pixmap clip_mask; 13034 Bool graphics_exposures; 13035 int subwindow_mode; 13036 int poly_edge; 13037 int poly_mode; 13038 Atom dither; 13039 Bool component_alpha; 13040 } 13041 13042 alias int XFixed; 13043 13044 struct XPointFixed { 13045 XFixed x, y; 13046 } 13047 13048 struct XCircle { 13049 XFixed x; 13050 XFixed y; 13051 XFixed radius; 13052 } 13053 13054 struct XTransform { 13055 XFixed[3][3] matrix; 13056 } 13057 13058 struct XFilters { 13059 int nfilter; 13060 char **filter; 13061 int nalias; 13062 short *alias_; 13063 } 13064 13065 struct XIndexValue { 13066 c_ulong pixel; 13067 ushort red, green, blue, alpha; 13068 } 13069 13070 struct XAnimCursor { 13071 Cursor cursor; 13072 c_ulong delay; 13073 } 13074 13075 struct XLinearGradient { 13076 XPointFixed p1; 13077 XPointFixed p2; 13078 } 13079 13080 struct XRadialGradient { 13081 XCircle inner; 13082 XCircle outer; 13083 } 13084 13085 struct XConicalGradient { 13086 XPointFixed center; 13087 XFixed angle; /* in degrees */ 13088 } 13089 13090 enum PictStandardARGB32 = 0; 13091 enum PictStandardRGB24 = 1; 13092 enum PictStandardA8 = 2; 13093 enum PictStandardA4 = 3; 13094 enum PictStandardA1 = 4; 13095 enum PictStandardNUM = 5; 13096 13097 interface XRender { 13098 extern(C) @nogc: 13099 13100 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13101 13102 Status XRenderQueryVersion (Display *dpy, 13103 int *major_versionp, 13104 int *minor_versionp); 13105 13106 Status XRenderQueryFormats (Display *dpy); 13107 13108 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13109 13110 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13111 13112 XRenderPictFormat * 13113 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13114 13115 XRenderPictFormat * 13116 XRenderFindFormat (Display *dpy, 13117 c_ulong mask, 13118 const XRenderPictFormat *templ, 13119 int count); 13120 XRenderPictFormat * 13121 XRenderFindStandardFormat (Display *dpy, 13122 int format); 13123 13124 XIndexValue * 13125 XRenderQueryPictIndexValues(Display *dpy, 13126 const XRenderPictFormat *format, 13127 int *num); 13128 13129 Picture XRenderCreatePicture( 13130 Display *dpy, 13131 Drawable drawable, 13132 const XRenderPictFormat *format, 13133 c_ulong valuemask, 13134 const XRenderPictureAttributes *attributes); 13135 13136 void XRenderChangePicture (Display *dpy, 13137 Picture picture, 13138 c_ulong valuemask, 13139 const XRenderPictureAttributes *attributes); 13140 13141 void 13142 XRenderSetPictureClipRectangles (Display *dpy, 13143 Picture picture, 13144 int xOrigin, 13145 int yOrigin, 13146 const XRectangle *rects, 13147 int n); 13148 13149 void 13150 XRenderSetPictureClipRegion (Display *dpy, 13151 Picture picture, 13152 Region r); 13153 13154 void 13155 XRenderSetPictureTransform (Display *dpy, 13156 Picture picture, 13157 XTransform *transform); 13158 13159 void 13160 XRenderFreePicture (Display *dpy, 13161 Picture picture); 13162 13163 void 13164 XRenderComposite (Display *dpy, 13165 int op, 13166 Picture src, 13167 Picture mask, 13168 Picture dst, 13169 int src_x, 13170 int src_y, 13171 int mask_x, 13172 int mask_y, 13173 int dst_x, 13174 int dst_y, 13175 uint width, 13176 uint height); 13177 13178 13179 Picture XRenderCreateSolidFill (Display *dpy, 13180 const XRenderColor *color); 13181 13182 Picture XRenderCreateLinearGradient (Display *dpy, 13183 const XLinearGradient *gradient, 13184 const XFixed *stops, 13185 const XRenderColor *colors, 13186 int nstops); 13187 13188 Picture XRenderCreateRadialGradient (Display *dpy, 13189 const XRadialGradient *gradient, 13190 const XFixed *stops, 13191 const XRenderColor *colors, 13192 int nstops); 13193 13194 Picture XRenderCreateConicalGradient (Display *dpy, 13195 const XConicalGradient *gradient, 13196 const XFixed *stops, 13197 const XRenderColor *colors, 13198 int nstops); 13199 13200 13201 13202 Cursor 13203 XRenderCreateCursor (Display *dpy, 13204 Picture source, 13205 uint x, 13206 uint y); 13207 13208 XFilters * 13209 XRenderQueryFilters (Display *dpy, Drawable drawable); 13210 13211 void 13212 XRenderSetPictureFilter (Display *dpy, 13213 Picture picture, 13214 const char *filter, 13215 XFixed *params, 13216 int nparams); 13217 13218 Cursor 13219 XRenderCreateAnimCursor (Display *dpy, 13220 int ncursor, 13221 XAnimCursor *cursors); 13222 } 13223 13224 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 13225 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 13226 13227 /* XRender } */ 13228 13229 /* Xrandr { */ 13230 13231 struct XRRMonitorInfo { 13232 Atom name; 13233 Bool primary; 13234 Bool automatic; 13235 int noutput; 13236 int x; 13237 int y; 13238 int width; 13239 int height; 13240 int mwidth; 13241 int mheight; 13242 /*RROutput*/ void *outputs; 13243 } 13244 13245 struct XRRScreenChangeNotifyEvent { 13246 int type; /* event base */ 13247 c_ulong serial; /* # of last request processed by server */ 13248 Bool send_event; /* true if this came from a SendEvent request */ 13249 Display *display; /* Display the event was read from */ 13250 Window window; /* window which selected for this event */ 13251 Window root; /* Root window for changed screen */ 13252 Time timestamp; /* when the screen change occurred */ 13253 Time config_timestamp; /* when the last configuration change */ 13254 ushort/*SizeID*/ size_index; 13255 ushort/*SubpixelOrder*/ subpixel_order; 13256 ushort/*Rotation*/ rotation; 13257 int width; 13258 int height; 13259 int mwidth; 13260 int mheight; 13261 } 13262 13263 enum RRScreenChangeNotify = 0; 13264 13265 enum RRScreenChangeNotifyMask = 1; 13266 13267 __gshared int xrrEventBase = -1; 13268 13269 13270 interface XRandr { 13271 extern(C) @nogc: 13272 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 13273 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 13274 13275 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 13276 void XRRFreeMonitors(XRRMonitorInfo *monitors); 13277 13278 void XRRSelectInput(Display *dpy, Window window, int mask); 13279 } 13280 13281 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 13282 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 13283 /* Xrandr } */ 13284 13285 /* Xft { */ 13286 13287 // actually freetype 13288 alias void FT_Face; 13289 13290 // actually fontconfig 13291 private alias FcBool = int; 13292 alias void FcCharSet; 13293 alias void FcPattern; 13294 alias void FcResult; 13295 enum FcEndian { FcEndianBig, FcEndianLittle } 13296 struct FcFontSet { 13297 int nfont; 13298 int sfont; 13299 FcPattern** fonts; 13300 } 13301 13302 // actually XRegion 13303 struct BOX { 13304 short x1, x2, y1, y2; 13305 } 13306 struct _XRegion { 13307 c_long size; 13308 c_long numRects; 13309 BOX* rects; 13310 BOX extents; 13311 } 13312 13313 alias Region = _XRegion*; 13314 13315 // ok actually Xft 13316 13317 struct XftFontInfo; 13318 13319 struct XftFont { 13320 int ascent; 13321 int descent; 13322 int height; 13323 int max_advance_width; 13324 FcCharSet* charset; 13325 FcPattern* pattern; 13326 } 13327 13328 struct XftDraw; 13329 13330 struct XftColor { 13331 c_ulong pixel; 13332 XRenderColor color; 13333 } 13334 13335 struct XftCharSpec { 13336 dchar ucs4; 13337 short x; 13338 short y; 13339 } 13340 13341 struct XftCharFontSpec { 13342 XftFont *font; 13343 dchar ucs4; 13344 short x; 13345 short y; 13346 } 13347 13348 struct XftGlyphSpec { 13349 uint glyph; 13350 short x; 13351 short y; 13352 } 13353 13354 struct XftGlyphFontSpec { 13355 XftFont *font; 13356 uint glyph; 13357 short x; 13358 short y; 13359 } 13360 13361 interface Xft { 13362 extern(C) @nogc pure: 13363 13364 Bool XftColorAllocName (Display *dpy, 13365 const Visual *visual, 13366 Colormap cmap, 13367 const char *name, 13368 XftColor *result); 13369 13370 Bool XftColorAllocValue (Display *dpy, 13371 Visual *visual, 13372 Colormap cmap, 13373 const XRenderColor *color, 13374 XftColor *result); 13375 13376 void XftColorFree (Display *dpy, 13377 Visual *visual, 13378 Colormap cmap, 13379 XftColor *color); 13380 13381 Bool XftDefaultHasRender (Display *dpy); 13382 13383 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 13384 13385 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 13386 13387 XftDraw * XftDrawCreate (Display *dpy, 13388 Drawable drawable, 13389 Visual *visual, 13390 Colormap colormap); 13391 13392 XftDraw * XftDrawCreateBitmap (Display *dpy, 13393 Pixmap bitmap); 13394 13395 XftDraw * XftDrawCreateAlpha (Display *dpy, 13396 Pixmap pixmap, 13397 int depth); 13398 13399 void XftDrawChange (XftDraw *draw, 13400 Drawable drawable); 13401 13402 Display * XftDrawDisplay (XftDraw *draw); 13403 13404 Drawable XftDrawDrawable (XftDraw *draw); 13405 13406 Colormap XftDrawColormap (XftDraw *draw); 13407 13408 Visual * XftDrawVisual (XftDraw *draw); 13409 13410 void XftDrawDestroy (XftDraw *draw); 13411 13412 Picture XftDrawPicture (XftDraw *draw); 13413 13414 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 13415 13416 void XftDrawGlyphs (XftDraw *draw, 13417 const XftColor *color, 13418 XftFont *pub, 13419 int x, 13420 int y, 13421 const uint *glyphs, 13422 int nglyphs); 13423 13424 void XftDrawString8 (XftDraw *draw, 13425 const XftColor *color, 13426 XftFont *pub, 13427 int x, 13428 int y, 13429 const char *string, 13430 int len); 13431 13432 void XftDrawString16 (XftDraw *draw, 13433 const XftColor *color, 13434 XftFont *pub, 13435 int x, 13436 int y, 13437 const wchar *string, 13438 int len); 13439 13440 void XftDrawString32 (XftDraw *draw, 13441 const XftColor *color, 13442 XftFont *pub, 13443 int x, 13444 int y, 13445 const dchar *string, 13446 int len); 13447 13448 void XftDrawStringUtf8 (XftDraw *draw, 13449 const XftColor *color, 13450 XftFont *pub, 13451 int x, 13452 int y, 13453 const char *string, 13454 int len); 13455 void XftDrawStringUtf16 (XftDraw *draw, 13456 const XftColor *color, 13457 XftFont *pub, 13458 int x, 13459 int y, 13460 const char *string, 13461 FcEndian endian, 13462 int len); 13463 13464 void XftDrawCharSpec (XftDraw *draw, 13465 const XftColor *color, 13466 XftFont *pub, 13467 const XftCharSpec *chars, 13468 int len); 13469 13470 void XftDrawCharFontSpec (XftDraw *draw, 13471 const XftColor *color, 13472 const XftCharFontSpec *chars, 13473 int len); 13474 13475 void XftDrawGlyphSpec (XftDraw *draw, 13476 const XftColor *color, 13477 XftFont *pub, 13478 const XftGlyphSpec *glyphs, 13479 int len); 13480 13481 void XftDrawGlyphFontSpec (XftDraw *draw, 13482 const XftColor *color, 13483 const XftGlyphFontSpec *glyphs, 13484 int len); 13485 13486 void XftDrawRect (XftDraw *draw, 13487 const XftColor *color, 13488 int x, 13489 int y, 13490 uint width, 13491 uint height); 13492 13493 Bool XftDrawSetClip (XftDraw *draw, 13494 Region r); 13495 13496 13497 Bool XftDrawSetClipRectangles (XftDraw *draw, 13498 int xOrigin, 13499 int yOrigin, 13500 const XRectangle *rects, 13501 int n); 13502 13503 void XftDrawSetSubwindowMode (XftDraw *draw, 13504 int mode); 13505 13506 void XftGlyphExtents (Display *dpy, 13507 XftFont *pub, 13508 const uint *glyphs, 13509 int nglyphs, 13510 XGlyphInfo *extents); 13511 13512 void XftTextExtents8 (Display *dpy, 13513 XftFont *pub, 13514 const char *string, 13515 int len, 13516 XGlyphInfo *extents); 13517 13518 void XftTextExtents16 (Display *dpy, 13519 XftFont *pub, 13520 const wchar *string, 13521 int len, 13522 XGlyphInfo *extents); 13523 13524 void XftTextExtents32 (Display *dpy, 13525 XftFont *pub, 13526 const dchar *string, 13527 int len, 13528 XGlyphInfo *extents); 13529 13530 void XftTextExtentsUtf8 (Display *dpy, 13531 XftFont *pub, 13532 const char *string, 13533 int len, 13534 XGlyphInfo *extents); 13535 13536 void XftTextExtentsUtf16 (Display *dpy, 13537 XftFont *pub, 13538 const char *string, 13539 FcEndian endian, 13540 int len, 13541 XGlyphInfo *extents); 13542 13543 FcPattern * XftFontMatch (Display *dpy, 13544 int screen, 13545 const FcPattern *pattern, 13546 FcResult *result); 13547 13548 XftFont * XftFontOpen (Display *dpy, int screen, ...); 13549 13550 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 13551 13552 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 13553 13554 FT_Face XftLockFace (XftFont *pub); 13555 13556 void XftUnlockFace (XftFont *pub); 13557 13558 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 13559 13560 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 13561 13562 dchar XftFontInfoHash (const XftFontInfo *fi); 13563 13564 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 13565 13566 XftFont * XftFontOpenInfo (Display *dpy, 13567 FcPattern *pattern, 13568 XftFontInfo *fi); 13569 13570 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 13571 13572 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 13573 13574 void XftFontClose (Display *dpy, XftFont *pub); 13575 13576 FcBool XftInitFtLibrary(); 13577 void XftFontLoadGlyphs (Display *dpy, 13578 XftFont *pub, 13579 FcBool need_bitmaps, 13580 const uint *glyphs, 13581 int nglyph); 13582 13583 void XftFontUnloadGlyphs (Display *dpy, 13584 XftFont *pub, 13585 const uint *glyphs, 13586 int nglyph); 13587 13588 FcBool XftFontCheckGlyph (Display *dpy, 13589 XftFont *pub, 13590 FcBool need_bitmaps, 13591 uint glyph, 13592 uint *missing, 13593 int *nmissing); 13594 13595 FcBool XftCharExists (Display *dpy, 13596 XftFont *pub, 13597 dchar ucs4); 13598 13599 uint XftCharIndex (Display *dpy, 13600 XftFont *pub, 13601 dchar ucs4); 13602 FcBool XftInit (const char *config); 13603 13604 int XftGetVersion (); 13605 13606 FcFontSet * XftListFonts (Display *dpy, 13607 int screen, 13608 ...); 13609 13610 FcPattern *XftNameParse (const char *name); 13611 13612 void XftGlyphRender (Display *dpy, 13613 int op, 13614 Picture src, 13615 XftFont *pub, 13616 Picture dst, 13617 int srcx, 13618 int srcy, 13619 int x, 13620 int y, 13621 const uint *glyphs, 13622 int nglyphs); 13623 13624 void XftGlyphSpecRender (Display *dpy, 13625 int op, 13626 Picture src, 13627 XftFont *pub, 13628 Picture dst, 13629 int srcx, 13630 int srcy, 13631 const XftGlyphSpec *glyphs, 13632 int nglyphs); 13633 13634 void XftCharSpecRender (Display *dpy, 13635 int op, 13636 Picture src, 13637 XftFont *pub, 13638 Picture dst, 13639 int srcx, 13640 int srcy, 13641 const XftCharSpec *chars, 13642 int len); 13643 void XftGlyphFontSpecRender (Display *dpy, 13644 int op, 13645 Picture src, 13646 Picture dst, 13647 int srcx, 13648 int srcy, 13649 const XftGlyphFontSpec *glyphs, 13650 int nglyphs); 13651 13652 void XftCharFontSpecRender (Display *dpy, 13653 int op, 13654 Picture src, 13655 Picture dst, 13656 int srcx, 13657 int srcy, 13658 const XftCharFontSpec *chars, 13659 int len); 13660 13661 void XftTextRender8 (Display *dpy, 13662 int op, 13663 Picture src, 13664 XftFont *pub, 13665 Picture dst, 13666 int srcx, 13667 int srcy, 13668 int x, 13669 int y, 13670 const char *string, 13671 int len); 13672 void XftTextRender16 (Display *dpy, 13673 int op, 13674 Picture src, 13675 XftFont *pub, 13676 Picture dst, 13677 int srcx, 13678 int srcy, 13679 int x, 13680 int y, 13681 const wchar *string, 13682 int len); 13683 13684 void XftTextRender16BE (Display *dpy, 13685 int op, 13686 Picture src, 13687 XftFont *pub, 13688 Picture dst, 13689 int srcx, 13690 int srcy, 13691 int x, 13692 int y, 13693 const char *string, 13694 int len); 13695 13696 void XftTextRender16LE (Display *dpy, 13697 int op, 13698 Picture src, 13699 XftFont *pub, 13700 Picture dst, 13701 int srcx, 13702 int srcy, 13703 int x, 13704 int y, 13705 const char *string, 13706 int len); 13707 13708 void XftTextRender32 (Display *dpy, 13709 int op, 13710 Picture src, 13711 XftFont *pub, 13712 Picture dst, 13713 int srcx, 13714 int srcy, 13715 int x, 13716 int y, 13717 const dchar *string, 13718 int len); 13719 13720 void XftTextRender32BE (Display *dpy, 13721 int op, 13722 Picture src, 13723 XftFont *pub, 13724 Picture dst, 13725 int srcx, 13726 int srcy, 13727 int x, 13728 int y, 13729 const char *string, 13730 int len); 13731 13732 void XftTextRender32LE (Display *dpy, 13733 int op, 13734 Picture src, 13735 XftFont *pub, 13736 Picture dst, 13737 int srcx, 13738 int srcy, 13739 int x, 13740 int y, 13741 const char *string, 13742 int len); 13743 13744 void XftTextRenderUtf8 (Display *dpy, 13745 int op, 13746 Picture src, 13747 XftFont *pub, 13748 Picture dst, 13749 int srcx, 13750 int srcy, 13751 int x, 13752 int y, 13753 const char *string, 13754 int len); 13755 13756 void XftTextRenderUtf16 (Display *dpy, 13757 int op, 13758 Picture src, 13759 XftFont *pub, 13760 Picture dst, 13761 int srcx, 13762 int srcy, 13763 int x, 13764 int y, 13765 const char *string, 13766 FcEndian endian, 13767 int len); 13768 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 13769 13770 } 13771 13772 interface FontConfig { 13773 extern(C) @nogc pure: 13774 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 13775 void FcFontSetDestroy(FcFontSet*); 13776 char* FcNameUnparse(const FcPattern *); 13777 } 13778 13779 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 13780 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 13781 13782 13783 /* Xft } */ 13784 13785 class XDisconnectException : Exception { 13786 bool userRequested; 13787 this(bool userRequested = true) { 13788 this.userRequested = userRequested; 13789 super("X disconnected"); 13790 } 13791 } 13792 13793 /++ 13794 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 13795 13796 Please note that it returns 13797 +/ 13798 XErrorEvent[] trapXErrors(scope void delegate() dg) { 13799 13800 static XErrorEvent[] errorBuffer; 13801 13802 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 13803 errorBuffer ~= *evt; 13804 return 0; 13805 } 13806 13807 auto savedErrorHandler = XSetErrorHandler(&handler); 13808 13809 try { 13810 dg(); 13811 } finally { 13812 XSync(XDisplayConnection.get, 0/*False*/); 13813 XSetErrorHandler(savedErrorHandler); 13814 } 13815 13816 auto bfr = errorBuffer; 13817 errorBuffer = null; 13818 13819 return bfr; 13820 } 13821 13822 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 13823 class XDisplayConnection { 13824 private __gshared Display* display; 13825 private __gshared XIM xim; 13826 private __gshared char* displayName; 13827 13828 private __gshared int connectionSequence_; 13829 private __gshared bool isLocal_; 13830 13831 /// use this for lazy caching when reconnection 13832 static int connectionSequenceNumber() { return connectionSequence_; } 13833 13834 /++ 13835 Guesses if the connection appears to be local. 13836 13837 History: 13838 Added June 3, 2021 13839 +/ 13840 static @property bool isLocal() nothrow @trusted @nogc { 13841 return isLocal_; 13842 } 13843 13844 /// Attempts recreation of state, may require application assistance 13845 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 13846 /// then call this, and if successful, reenter the loop. 13847 static void discardAndRecreate(string newDisplayString = null) { 13848 if(insideXEventLoop) 13849 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 13850 13851 // 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 13852 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 13853 13854 foreach(handle; chnenhm) { 13855 handle.discardConnectionState(); 13856 } 13857 13858 discardState(); 13859 13860 if(newDisplayString !is null) 13861 setDisplayName(newDisplayString); 13862 13863 auto display = get(); 13864 13865 foreach(handle; chnenhm) { 13866 handle.recreateAfterDisconnect(); 13867 } 13868 } 13869 13870 private __gshared EventMask rootEventMask; 13871 13872 /++ 13873 Requests the specified input from the root window on the connection, in addition to any other request. 13874 13875 13876 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. 13877 13878 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 13879 +/ 13880 static void addRootInput(EventMask mask) { 13881 auto old = rootEventMask; 13882 rootEventMask |= mask; 13883 get(); // to ensure display connected 13884 if(display !is null && rootEventMask != old) 13885 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 13886 } 13887 13888 static void discardState() { 13889 freeImages(); 13890 13891 foreach(atomPtr; interredAtoms) 13892 *atomPtr = 0; 13893 interredAtoms = null; 13894 interredAtoms.assumeSafeAppend(); 13895 13896 ScreenPainterImplementation.fontAttempted = false; 13897 ScreenPainterImplementation.defaultfont = null; 13898 ScreenPainterImplementation.defaultfontset = null; 13899 13900 Image.impl.xshmQueryCompleted = false; 13901 Image.impl._xshmAvailable = false; 13902 13903 SimpleWindow.nativeMapping = null; 13904 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 13905 // GlobalHotkeyManager 13906 13907 display = null; 13908 xim = null; 13909 } 13910 13911 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 13912 private static void createXIM () { 13913 import core.stdc.locale : setlocale, LC_ALL; 13914 import core.stdc.stdio : stderr, fprintf; 13915 import core.stdc.stdlib : free; 13916 import core.stdc.string : strdup; 13917 13918 static immutable string[3] mtry = [ "", "@im=local", "@im=" ]; 13919 13920 auto olocale = strdup(setlocale(LC_ALL, null)); 13921 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 13922 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 13923 13924 //fprintf(stderr, "opening IM...\n"); 13925 foreach (string s; mtry) { 13926 XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 13927 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 13928 } 13929 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 13930 } 13931 13932 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 13933 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 13934 static struct ImgList { 13935 size_t img; // class; hide it from GC 13936 ImgList* next; 13937 } 13938 13939 static __gshared ImgList* imglist = null; 13940 static __gshared bool imglistLocked = false; // true: don't register and unregister images 13941 13942 static void registerImage (Image img) { 13943 if (!imglistLocked && img !is null) { 13944 import core.stdc.stdlib : malloc; 13945 auto it = cast(ImgList*)malloc(ImgList.sizeof); 13946 assert(it !is null); // do proper checks 13947 it.img = cast(size_t)cast(void*)img; 13948 it.next = imglist; 13949 imglist = it; 13950 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 13951 } 13952 } 13953 13954 static void unregisterImage (Image img) { 13955 if (!imglistLocked && img !is null) { 13956 import core.stdc.stdlib : free; 13957 ImgList* prev = null; 13958 ImgList* cur = imglist; 13959 while (cur !is null) { 13960 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 13961 prev = cur; 13962 cur = cur.next; 13963 } 13964 if (cur !is null) { 13965 if (prev is null) imglist = cur.next; else prev.next = cur.next; 13966 free(cur); 13967 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 13968 } else { 13969 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 13970 } 13971 } 13972 } 13973 13974 static void freeImages () { // needed for discardAndRecreate 13975 imglistLocked = true; 13976 scope(exit) imglistLocked = false; 13977 ImgList* cur = imglist; 13978 ImgList* next = null; 13979 while (cur !is null) { 13980 import core.stdc.stdlib : free; 13981 next = cur.next; 13982 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 13983 (cast(Image)cast(void*)cur.img).dispose(); 13984 free(cur); 13985 cur = next; 13986 } 13987 imglist = null; 13988 } 13989 13990 /// can be used to override normal handling of display name 13991 /// from environment and/or command line 13992 static setDisplayName(string newDisplayName) { 13993 displayName = cast(char*) (newDisplayName ~ '\0'); 13994 } 13995 13996 /// resets to the default display string 13997 static resetDisplayName() { 13998 displayName = null; 13999 } 14000 14001 /// 14002 static Display* get() { 14003 if(display is null) { 14004 if(!librariesSuccessfullyLoaded) 14005 throw new Exception("Unable to load X11 client libraries"); 14006 display = XOpenDisplay(displayName); 14007 14008 isLocal_ = false; 14009 14010 connectionSequence_++; 14011 if(display is null) 14012 throw new Exception("Unable to open X display"); 14013 14014 auto str = display.display_name; 14015 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 14016 // and otherwise it probably isn't 14017 if(str is null || (str[0] != ':' && str[0] != '/')) 14018 isLocal_ = false; 14019 else 14020 isLocal_ = true; 14021 14022 debug(sdpy_x_errors) { 14023 XSetErrorHandler(&adrlogger); 14024 XSynchronize(display, true); 14025 14026 extern(C) int wtf() { 14027 if(errorHappened) { 14028 asm { int 3; } 14029 errorHappened = false; 14030 } 14031 return 0; 14032 } 14033 XSetAfterFunction(display, &wtf); 14034 } 14035 14036 14037 XSetIOErrorHandler(&x11ioerrCB); 14038 Bool sup; 14039 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 14040 createXIM(); 14041 version(with_eventloop) { 14042 import arsd.eventloop; 14043 addFileEventListeners(display.fd, &eventListener, null, null); 14044 } 14045 } 14046 14047 return display; 14048 } 14049 14050 extern(C) 14051 static int x11ioerrCB(Display* dpy) { 14052 throw new XDisconnectException(false); 14053 } 14054 14055 version(with_eventloop) { 14056 import arsd.eventloop; 14057 static void eventListener(OsFileHandle fd) { 14058 //this.mtLock(); 14059 //scope(exit) this.mtUnlock(); 14060 while(XPending(display)) 14061 doXNextEvent(display); 14062 } 14063 } 14064 14065 // close connection on program exit -- we need this to properly free all images 14066 static ~this () { 14067 // the gui thread must clean up after itself or else Xlib might deadlock 14068 // using this flag on any thread destruction is the easiest way i know of 14069 // (shared static this is run by the LAST thread to exit, which may not be 14070 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 14071 if(thisIsGuiThread) 14072 close(); 14073 } 14074 14075 /// 14076 static void close() { 14077 if(display is null) 14078 return; 14079 14080 version(with_eventloop) { 14081 import arsd.eventloop; 14082 removeFileEventListeners(display.fd); 14083 } 14084 14085 // now remove all registered images to prevent shared memory leaks 14086 freeImages(); 14087 14088 // tbh I don't know why it is doing this but like if this happens to run 14089 // from the other thread there's frequent hanging inside here. 14090 if(thisIsGuiThread) 14091 XCloseDisplay(display); 14092 display = null; 14093 } 14094 } 14095 14096 mixin template NativeImageImplementation() { 14097 XImage* handle; 14098 ubyte* rawData; 14099 14100 XShmSegmentInfo shminfo; 14101 14102 __gshared bool xshmQueryCompleted; 14103 __gshared bool _xshmAvailable; 14104 public static @property bool xshmAvailable() { 14105 if(!xshmQueryCompleted) { 14106 int i1, i2, i3; 14107 xshmQueryCompleted = true; 14108 14109 if(!XDisplayConnection.isLocal) 14110 _xshmAvailable = false; 14111 else 14112 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14113 } 14114 return _xshmAvailable; 14115 } 14116 14117 bool usingXshm; 14118 final: 14119 14120 private __gshared bool xshmfailed; 14121 14122 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14123 auto display = XDisplayConnection.get(); 14124 assert(display !is null); 14125 auto screen = DefaultScreen(display); 14126 14127 // it will only use shared memory for somewhat largish images, 14128 // since otherwise we risk wasting shared memory handles on a lot of little ones 14129 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14130 14131 14132 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14133 // the actual use still fails. For example, if the program is in a container and permission denied 14134 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14135 // 14136 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14137 14138 14139 // synchronize so preexisting buffers are clear 14140 XSync(display, false); 14141 xshmfailed = false; 14142 14143 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14144 14145 14146 usingXshm = true; 14147 handle = XShmCreateImage( 14148 display, 14149 DefaultVisual(display, screen), 14150 enableAlpha ? 32: 24, 14151 ImageFormat.ZPixmap, 14152 null, 14153 &shminfo, 14154 width, height); 14155 if(handle is null) 14156 goto abortXshm1; 14157 14158 if(handle.bytes_per_line != 4 * width) 14159 goto abortXshm2; 14160 14161 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14162 if(shminfo.shmid < 0) 14163 goto abortXshm3; 14164 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14165 if(rawData == cast(ubyte*) -1) 14166 goto abortXshm4; 14167 shminfo.readOnly = 0; 14168 XShmAttach(display, &shminfo); 14169 14170 // and now to the final error check to ensure it actually worked. 14171 XSync(display, false); 14172 if(xshmfailed) 14173 goto abortXshm5; 14174 14175 XSetErrorHandler(oldErrorHandler); 14176 14177 XDisplayConnection.registerImage(this); 14178 // if I don't flush here there's a chance the dtor will run before the 14179 // ctor and lead to a bad value X error. While this hurts the efficiency 14180 // it is local anyway so prolly better to keep it simple 14181 XFlush(display); 14182 14183 return; 14184 14185 abortXshm5: 14186 shmdt(shminfo.shmaddr); 14187 rawData = null; 14188 14189 abortXshm4: 14190 shmctl(shminfo.shmid, IPC_RMID, null); 14191 14192 abortXshm3: 14193 // nothing needed, the shmget failed so there's nothing to free 14194 14195 abortXshm2: 14196 XDestroyImage(handle); 14197 handle = null; 14198 14199 abortXshm1: 14200 XSetErrorHandler(oldErrorHandler); 14201 usingXshm = false; 14202 handle = null; 14203 14204 shminfo = typeof(shminfo).init; 14205 14206 _xshmAvailable = false; // don't try again in the future 14207 14208 //import std.stdio; writeln("fallingback"); 14209 14210 goto fallback; 14211 14212 } else { 14213 fallback: 14214 14215 if (forcexshm) throw new Exception("can't create XShm Image"); 14216 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 14217 import core.stdc.stdlib : malloc; 14218 rawData = cast(ubyte*) malloc(width * height * 4); 14219 14220 handle = XCreateImage( 14221 display, 14222 DefaultVisual(display, screen), 14223 enableAlpha ? 32 : 24, // bpp 14224 ImageFormat.ZPixmap, 14225 0, // offset 14226 rawData, 14227 width, height, 14228 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 14229 } 14230 } 14231 14232 void dispose() { 14233 // note: this calls free(rawData) for us 14234 if(handle) { 14235 if (usingXshm) { 14236 XDisplayConnection.unregisterImage(this); 14237 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 14238 } 14239 XDestroyImage(handle); 14240 if(usingXshm) { 14241 shmdt(shminfo.shmaddr); 14242 shmctl(shminfo.shmid, IPC_RMID, null); 14243 } 14244 handle = null; 14245 } 14246 } 14247 14248 Color getPixel(int x, int y) { 14249 auto offset = (y * width + x) * 4; 14250 Color c; 14251 c.a = enableAlpha ? rawData[offset + 3] : 255; 14252 c.b = rawData[offset + 0]; 14253 c.g = rawData[offset + 1]; 14254 c.r = rawData[offset + 2]; 14255 if(enableAlpha) 14256 c.unPremultiply; 14257 return c; 14258 } 14259 14260 void setPixel(int x, int y, Color c) { 14261 if(enableAlpha) 14262 c.premultiply(); 14263 auto offset = (y * width + x) * 4; 14264 rawData[offset + 0] = c.b; 14265 rawData[offset + 1] = c.g; 14266 rawData[offset + 2] = c.r; 14267 if(enableAlpha) 14268 rawData[offset + 3] = c.a; 14269 } 14270 14271 void convertToRgbaBytes(ubyte[] where) { 14272 assert(where.length == this.width * this.height * 4); 14273 14274 // if rawData had a length.... 14275 //assert(rawData.length == where.length); 14276 for(int idx = 0; idx < where.length; idx += 4) { 14277 where[idx + 0] = rawData[idx + 2]; // r 14278 where[idx + 1] = rawData[idx + 1]; // g 14279 where[idx + 2] = rawData[idx + 0]; // b 14280 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 14281 14282 if(enableAlpha) 14283 unPremultiplyRgba(where[idx .. idx + 4]); 14284 } 14285 } 14286 14287 void setFromRgbaBytes(in ubyte[] where) { 14288 assert(where.length == this.width * this.height * 4); 14289 14290 // if rawData had a length.... 14291 //assert(rawData.length == where.length); 14292 for(int idx = 0; idx < where.length; idx += 4) { 14293 rawData[idx + 2] = where[idx + 0]; // r 14294 rawData[idx + 1] = where[idx + 1]; // g 14295 rawData[idx + 0] = where[idx + 2]; // b 14296 if(enableAlpha) { 14297 rawData[idx + 3] = where[idx + 3]; // a 14298 premultiplyBgra(rawData[idx .. idx + 4]); 14299 } 14300 } 14301 } 14302 14303 } 14304 14305 mixin template NativeSimpleWindowImplementation() { 14306 GC gc; 14307 Window window; 14308 Display* display; 14309 14310 Pixmap buffer; 14311 int bufferw, bufferh; // size of the buffer; can be bigger than window 14312 XIC xic; // input context 14313 int curHidden = 0; // counter 14314 Cursor blankCurPtr = 0; 14315 int cursorSequenceNumber = 0; 14316 int warpEventCount = 0; // number of mouse movement events to eat 14317 14318 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 14319 X11GetSelectionHandler[Atom] getSelectionHandlers; 14320 14321 version(without_opengl) {} else 14322 GLXContext glc; 14323 14324 private void fixFixedSize(bool forced=false) (int width, int height) { 14325 if (forced || this.resizability == Resizability.fixedSize) { 14326 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 14327 XSizeHints sh; 14328 static if (!forced) { 14329 c_long spr; 14330 XGetWMNormalHints(display, window, &sh, &spr); 14331 sh.flags |= PMaxSize | PMinSize; 14332 } else { 14333 sh.flags = PMaxSize | PMinSize; 14334 } 14335 sh.min_width = width; 14336 sh.min_height = height; 14337 sh.max_width = width; 14338 sh.max_height = height; 14339 XSetWMNormalHints(display, window, &sh); 14340 //XFlush(display); 14341 } 14342 } 14343 14344 ScreenPainter getPainter(bool manualInvalidations) { 14345 return ScreenPainter(this, window, manualInvalidations); 14346 } 14347 14348 void move(int x, int y) { 14349 XMoveWindow(display, window, x, y); 14350 } 14351 14352 void resize(int w, int h) { 14353 if (w < 1) w = 1; 14354 if (h < 1) h = 1; 14355 XResizeWindow(display, window, w, h); 14356 14357 // calling this now to avoid waiting for the server to 14358 // acknowledge the resize; draws without returning to the 14359 // event loop will thus actually work. the server's event 14360 // btw might overrule this and resize it again 14361 recordX11Resize(display, this, w, h); 14362 14363 updateOpenglViewportIfNeeded(w, h); 14364 } 14365 14366 void moveResize (int x, int y, int w, int h) { 14367 if (w < 1) w = 1; 14368 if (h < 1) h = 1; 14369 XMoveResizeWindow(display, window, x, y, w, h); 14370 updateOpenglViewportIfNeeded(w, h); 14371 } 14372 14373 void hideCursor () { 14374 if (curHidden++ == 0) { 14375 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 14376 static const(char)[1] cmbmp = 0; 14377 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 14378 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 14379 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 14380 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 14381 XFreePixmap(display, pm); 14382 } 14383 XDefineCursor(display, window, blankCurPtr); 14384 } 14385 } 14386 14387 void showCursor () { 14388 if (--curHidden == 0) XUndefineCursor(display, window); 14389 } 14390 14391 void warpMouse (int x, int y) { 14392 // here i will send dummy "ignore next mouse motion" event, 14393 // 'cause `XWarpPointer()` sends synthesised mouse motion, 14394 // and we don't need to report it to the user (as warping is 14395 // used when the user needs movement deltas). 14396 //XClientMessageEvent xclient; 14397 XEvent e; 14398 e.xclient.type = EventType.ClientMessage; 14399 e.xclient.window = window; 14400 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14401 e.xclient.format = 32; 14402 e.xclient.data.l[0] = 0; 14403 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 14404 //{ 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]); } 14405 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14406 // now warp pointer... 14407 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 14408 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 14409 // ...and flush 14410 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 14411 XFlush(display); 14412 } 14413 14414 void sendDummyEvent () { 14415 // here i will send dummy event to ping event queue 14416 XEvent e; 14417 e.xclient.type = EventType.ClientMessage; 14418 e.xclient.window = window; 14419 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14420 e.xclient.format = 32; 14421 e.xclient.data.l[0] = 0; 14422 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14423 XFlush(display); 14424 } 14425 14426 void setTitle(string title) { 14427 if (title.ptr is null) title = ""; 14428 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14429 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14430 XTextProperty windowName; 14431 windowName.value = title.ptr; 14432 windowName.encoding = XA_UTF8; //XA_STRING; 14433 windowName.format = 8; 14434 windowName.nitems = cast(uint)title.length; 14435 XSetWMName(display, window, &windowName); 14436 char[1024] namebuf = 0; 14437 auto maxlen = namebuf.length-1; 14438 if (maxlen > title.length) maxlen = title.length; 14439 namebuf[0..maxlen] = title[0..maxlen]; 14440 XStoreName(display, window, namebuf.ptr); 14441 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 14442 flushGui(); // without this OpenGL windows has a LONG delay before changing title 14443 } 14444 14445 string[] getTitles() { 14446 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14447 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14448 XTextProperty textProp; 14449 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 14450 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 14451 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 14452 } else 14453 return []; 14454 } else 14455 return null; 14456 } 14457 14458 string getTitle() { 14459 auto titles = getTitles(); 14460 return titles.length ? titles[0] : null; 14461 } 14462 14463 void setMinSize (int minwidth, int minheight) { 14464 import core.stdc.config : c_long; 14465 if (minwidth < 1) minwidth = 1; 14466 if (minheight < 1) minheight = 1; 14467 XSizeHints sh; 14468 c_long spr; 14469 XGetWMNormalHints(display, window, &sh, &spr); 14470 sh.min_width = minwidth; 14471 sh.min_height = minheight; 14472 sh.flags |= PMinSize; 14473 XSetWMNormalHints(display, window, &sh); 14474 flushGui(); 14475 } 14476 14477 void setMaxSize (int maxwidth, int maxheight) { 14478 import core.stdc.config : c_long; 14479 if (maxwidth < 1) maxwidth = 1; 14480 if (maxheight < 1) maxheight = 1; 14481 XSizeHints sh; 14482 c_long spr; 14483 XGetWMNormalHints(display, window, &sh, &spr); 14484 sh.max_width = maxwidth; 14485 sh.max_height = maxheight; 14486 sh.flags |= PMaxSize; 14487 XSetWMNormalHints(display, window, &sh); 14488 flushGui(); 14489 } 14490 14491 void setResizeGranularity (int granx, int grany) { 14492 import core.stdc.config : c_long; 14493 if (granx < 1) granx = 1; 14494 if (grany < 1) grany = 1; 14495 XSizeHints sh; 14496 c_long spr; 14497 XGetWMNormalHints(display, window, &sh, &spr); 14498 sh.width_inc = granx; 14499 sh.height_inc = grany; 14500 sh.flags |= PResizeInc; 14501 XSetWMNormalHints(display, window, &sh); 14502 flushGui(); 14503 } 14504 14505 void setOpacity (uint opacity) { 14506 arch_ulong o = opacity; 14507 if (opacity == uint.max) 14508 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 14509 else 14510 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 14511 XA_CARDINAL, 32, PropModeReplace, &o, 1); 14512 } 14513 14514 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 14515 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 14516 display = XDisplayConnection.get(); 14517 auto screen = DefaultScreen(display); 14518 14519 bool overrideRedirect = false; 14520 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 14521 overrideRedirect = true; 14522 14523 version(without_opengl) {} 14524 else { 14525 if(opengl == OpenGlOptions.yes) { 14526 GLXFBConfig fbconf = null; 14527 XVisualInfo* vi = null; 14528 bool useLegacy = false; 14529 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 14530 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 14531 int[23] visualAttribs = [ 14532 GLX_X_RENDERABLE , 1/*True*/, 14533 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 14534 GLX_RENDER_TYPE , GLX_RGBA_BIT, 14535 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 14536 GLX_RED_SIZE , 8, 14537 GLX_GREEN_SIZE , 8, 14538 GLX_BLUE_SIZE , 8, 14539 GLX_ALPHA_SIZE , 8, 14540 GLX_DEPTH_SIZE , 24, 14541 GLX_STENCIL_SIZE , 8, 14542 GLX_DOUBLEBUFFER , 1/*True*/, 14543 0/*None*/, 14544 ]; 14545 int fbcount; 14546 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 14547 if (fbcount == 0) { 14548 useLegacy = true; // try to do at least something 14549 } else { 14550 // pick the FB config/visual with the most samples per pixel 14551 int bestidx = -1, bestns = -1; 14552 foreach (int fbi; 0..fbcount) { 14553 int sb, samples; 14554 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 14555 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 14556 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 14557 } 14558 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 14559 fbconf = fbc[bestidx]; 14560 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 14561 XFree(fbc); 14562 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 14563 } 14564 } 14565 if (vi is null || useLegacy) { 14566 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 14567 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 14568 useLegacy = true; 14569 } 14570 if (vi is null) throw new Exception("no open gl visual found"); 14571 14572 XSetWindowAttributes swa; 14573 auto root = RootWindow(display, screen); 14574 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 14575 14576 swa.override_redirect = overrideRedirect; 14577 14578 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14579 0, 0, width, height, 14580 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 14581 14582 // now try to use `glXCreateContextAttribsARB()` if it's here 14583 if (!useLegacy) { 14584 // request fairly advanced context, even with stencil buffer! 14585 int[9] contextAttribs = [ 14586 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 14587 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 14588 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 14589 // for modern context, set "forward compatibility" flag too 14590 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 14591 0/*None*/, 14592 ]; 14593 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 14594 if (glc is null && sdpyOpenGLContextAllowFallback) { 14595 sdpyOpenGLContextVersion = 0; 14596 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14597 } 14598 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 14599 } else { 14600 // fallback to old GLX call 14601 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 14602 sdpyOpenGLContextVersion = 0; 14603 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14604 } 14605 } 14606 // sync to ensure any errors generated are processed 14607 XSync(display, 0/*False*/); 14608 //{ import core.stdc.stdio; printf("ogl is here\n"); } 14609 if(glc is null) 14610 throw new Exception("glc"); 14611 } 14612 } 14613 14614 if(opengl == OpenGlOptions.no) { 14615 14616 XSetWindowAttributes swa; 14617 swa.background_pixel = WhitePixel(display, screen); 14618 swa.border_pixel = BlackPixel(display, screen); 14619 swa.override_redirect = overrideRedirect; 14620 auto root = RootWindow(display, screen); 14621 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 14622 14623 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14624 0, 0, width, height, 14625 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 14626 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 14627 14628 14629 14630 /* 14631 window = XCreateSimpleWindow( 14632 display, 14633 parent is null ? RootWindow(display, screen) : parent.impl.window, 14634 0, 0, // x, y 14635 width, height, 14636 1, // border width 14637 BlackPixel(display, screen), // border 14638 WhitePixel(display, screen)); // background 14639 */ 14640 14641 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 14642 bufferw = width; 14643 bufferh = height; 14644 14645 gc = DefaultGC(display, screen); 14646 14647 // clear out the buffer to get us started... 14648 XSetForeground(display, gc, WhitePixel(display, screen)); 14649 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 14650 XSetForeground(display, gc, BlackPixel(display, screen)); 14651 } 14652 14653 // input context 14654 //TODO: create this only for top-level windows, and reuse that? 14655 populateXic(); 14656 14657 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 14658 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 14659 // window class 14660 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 14661 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 14662 XClassHint klass; 14663 XWMHints wh; 14664 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14665 wh.input = true; 14666 wh.flags |= InputHint; 14667 } 14668 XSizeHints size; 14669 klass.res_name = sdpyWindowClassStr; 14670 klass.res_class = sdpyWindowClassStr; 14671 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 14672 } 14673 14674 setTitle(title); 14675 SimpleWindow.nativeMapping[window] = this; 14676 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 14677 14678 // This gives our window a close button 14679 if (windowType != WindowTypes.eventOnly) { 14680 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 14681 int useAtoms; 14682 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14683 useAtoms = 2; 14684 } else { 14685 useAtoms = 1; 14686 } 14687 assert(useAtoms <= atoms.length); 14688 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 14689 } 14690 14691 // FIXME: windowType and customizationFlags 14692 Atom[8] wsatoms; // here, due to goto 14693 int wmsacount = 0; // here, due to goto 14694 14695 try 14696 final switch(windowType) { 14697 case WindowTypes.normal: 14698 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14699 break; 14700 case WindowTypes.undecorated: 14701 motifHideDecorations(); 14702 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14703 break; 14704 case WindowTypes.eventOnly: 14705 _hidden = true; 14706 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 14707 goto hiddenWindow; 14708 //break; 14709 case WindowTypes.nestedChild: 14710 // handled in XCreateWindow calls 14711 break; 14712 14713 case WindowTypes.dropdownMenu: 14714 motifHideDecorations(); 14715 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 14716 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14717 break; 14718 case WindowTypes.popupMenu: 14719 motifHideDecorations(); 14720 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 14721 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14722 break; 14723 case WindowTypes.notification: 14724 motifHideDecorations(); 14725 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 14726 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14727 break; 14728 case WindowTypes.minimallyWrapped: 14729 assert(0, "don't create a minimallyWrapped thing explicitly!"); 14730 /+ 14731 case WindowTypes.menu: 14732 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14733 motifHideDecorations(); 14734 break; 14735 case WindowTypes.desktop: 14736 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 14737 break; 14738 case WindowTypes.dock: 14739 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 14740 break; 14741 case WindowTypes.toolbar: 14742 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 14743 break; 14744 case WindowTypes.menu: 14745 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14746 break; 14747 case WindowTypes.utility: 14748 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 14749 break; 14750 case WindowTypes.splash: 14751 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 14752 break; 14753 case WindowTypes.dialog: 14754 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 14755 break; 14756 case WindowTypes.tooltip: 14757 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 14758 break; 14759 case WindowTypes.notification: 14760 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 14761 break; 14762 case WindowTypes.combo: 14763 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 14764 break; 14765 case WindowTypes.dnd: 14766 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 14767 break; 14768 +/ 14769 } 14770 catch(Exception e) { 14771 // XInternAtom failed, prolly a WM 14772 // that doesn't support these things 14773 } 14774 14775 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 14776 // the two following flags may be ignored by WM 14777 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 14778 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 14779 14780 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 14781 14782 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 14783 14784 // What would be ideal here is if they only were 14785 // selected if there was actually an event handler 14786 // for them... 14787 14788 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 14789 14790 hiddenWindow: 14791 14792 // set the pid property for lookup later by window managers 14793 // a standard convenience 14794 import core.sys.posix.unistd; 14795 arch_ulong pid = getpid(); 14796 14797 XChangeProperty( 14798 display, 14799 impl.window, 14800 GetAtom!("_NET_WM_PID", true)(display), 14801 XA_CARDINAL, 14802 32 /* bits */, 14803 0 /*PropModeReplace*/, 14804 &pid, 14805 1); 14806 14807 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 14808 if(parent is null) assert(0); 14809 XChangeProperty( 14810 display, 14811 impl.window, 14812 GetAtom!("WM_TRANSIENT_FOR", true)(display), 14813 XA_WINDOW, 14814 32 /* bits */, 14815 0 /*PropModeReplace*/, 14816 &parent.impl.window, 14817 1); 14818 14819 } 14820 14821 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 14822 XMapWindow(display, window); 14823 } else { 14824 _hidden = true; 14825 } 14826 } 14827 14828 void populateXic() { 14829 if (XDisplayConnection.xim !is null) { 14830 xic = XCreateIC(XDisplayConnection.xim, 14831 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 14832 /*XNClientWindow*/"clientWindow".ptr, window, 14833 /*XNFocusWindow*/"focusWindow".ptr, window, 14834 null); 14835 if (xic is null) { 14836 import core.stdc.stdio : stderr, fprintf; 14837 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 14838 } 14839 } 14840 } 14841 14842 void selectDefaultInput(bool forceIncludeMouseMotion) { 14843 auto mask = EventMask.ExposureMask | 14844 EventMask.KeyPressMask | 14845 EventMask.KeyReleaseMask | 14846 EventMask.PropertyChangeMask | 14847 EventMask.FocusChangeMask | 14848 EventMask.StructureNotifyMask | 14849 EventMask.SubstructureNotifyMask | 14850 EventMask.VisibilityChangeMask 14851 | EventMask.ButtonPressMask 14852 | EventMask.ButtonReleaseMask 14853 ; 14854 14855 // xshm is our shortcut for local connections 14856 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 14857 mask |= EventMask.PointerMotionMask; 14858 else 14859 mask |= EventMask.ButtonMotionMask; 14860 14861 XSelectInput(display, window, mask); 14862 } 14863 14864 14865 void setNetWMWindowType(Atom type) { 14866 Atom[2] atoms; 14867 14868 atoms[0] = type; 14869 // generic fallback 14870 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 14871 14872 XChangeProperty( 14873 display, 14874 impl.window, 14875 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 14876 XA_ATOM, 14877 32 /* bits */, 14878 0 /*PropModeReplace*/, 14879 atoms.ptr, 14880 cast(int) atoms.length); 14881 } 14882 14883 void motifHideDecorations(bool hide = true) { 14884 MwmHints hints; 14885 hints.flags = MWM_HINTS_DECORATIONS; 14886 hints.decorations = hide ? 0 : 1; 14887 14888 XChangeProperty( 14889 display, 14890 impl.window, 14891 GetAtom!"_MOTIF_WM_HINTS"(display), 14892 GetAtom!"_MOTIF_WM_HINTS"(display), 14893 32 /* bits */, 14894 0 /*PropModeReplace*/, 14895 &hints, 14896 hints.sizeof / 4); 14897 } 14898 14899 /*k8: unused 14900 void createOpenGlContext() { 14901 14902 } 14903 */ 14904 14905 void closeWindow() { 14906 // I can't close this or a child window closing will 14907 // break events for everyone. So I'm just leaking it right 14908 // now and that is probably perfectly fine... 14909 version(none) 14910 if (customEventFDRead != -1) { 14911 import core.sys.posix.unistd : close; 14912 auto same = customEventFDRead == customEventFDWrite; 14913 14914 close(customEventFDRead); 14915 if(!same) 14916 close(customEventFDWrite); 14917 customEventFDRead = -1; 14918 customEventFDWrite = -1; 14919 } 14920 if(buffer) 14921 XFreePixmap(display, buffer); 14922 bufferw = bufferh = 0; 14923 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 14924 XDestroyWindow(display, window); 14925 XFlush(display); 14926 } 14927 14928 void dispose() { 14929 } 14930 14931 bool destroyed = false; 14932 } 14933 14934 bool insideXEventLoop; 14935 } 14936 14937 version(X11) { 14938 14939 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 14940 14941 private class ResizeEvent { 14942 int width, height; 14943 } 14944 14945 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 14946 if(win.windowType == WindowTypes.minimallyWrapped) 14947 return; 14948 14949 if(win.pendingResizeEvent is null) { 14950 win.pendingResizeEvent = new ResizeEvent(); 14951 win.addEventListener((ResizeEvent re) { 14952 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 14953 }); 14954 } 14955 win.pendingResizeEvent.width = width; 14956 win.pendingResizeEvent.height = height; 14957 if(!win.eventQueued!ResizeEvent) { 14958 win.postEvent(win.pendingResizeEvent); 14959 } 14960 } 14961 14962 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 14963 if(win.windowType == WindowTypes.minimallyWrapped) 14964 return; 14965 if(win.closed) 14966 return; 14967 14968 if(width != win.width || height != win.height) { 14969 14970 // import std.stdio; writeln("RESIZE: ", width, "x", height, " was ", win._width, "x", win._height, " window: ", win.windowType, "-", win.title, " ", win.window); 14971 win._width = width; 14972 win._height = height; 14973 14974 if(win.openglMode == OpenGlOptions.no) { 14975 // FIXME: could this be more efficient? 14976 14977 if (win.bufferw < width || win.bufferh < height) { 14978 //{ 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); } 14979 // grow the internal buffer to match the window... 14980 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 14981 { 14982 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 14983 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 14984 scope(exit) XFreeGC(win.display, xgc); 14985 XSetClipMask(win.display, xgc, None); 14986 XSetForeground(win.display, xgc, 0); 14987 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 14988 } 14989 XCopyArea(display, 14990 cast(Drawable) win.buffer, 14991 cast(Drawable) newPixmap, 14992 win.gc, 0, 0, 14993 win.bufferw < width ? win.bufferw : win.width, 14994 win.bufferh < height ? win.bufferh : win.height, 14995 0, 0); 14996 14997 XFreePixmap(display, win.buffer); 14998 win.buffer = newPixmap; 14999 win.bufferw = width; 15000 win.bufferh = height; 15001 } 15002 15003 // clear unused parts of the buffer 15004 if (win.bufferw > width || win.bufferh > height) { 15005 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 15006 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 15007 scope(exit) XFreeGC(win.display, xgc); 15008 XSetClipMask(win.display, xgc, None); 15009 XSetForeground(win.display, xgc, 0); 15010 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 15011 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 15012 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 15013 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 15014 } 15015 15016 } 15017 15018 win.updateOpenglViewportIfNeeded(width, height); 15019 15020 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 15021 15022 if(win.resizability != Resizability.automaticallyScaleIfPossible) 15023 if(win.windowResized !is null) { 15024 XUnlockDisplay(display); 15025 scope(exit) XLockDisplay(display); 15026 win.windowResized(width, height); 15027 } 15028 } 15029 } 15030 15031 15032 /// Platform-specific, you might use it when doing a custom event loop. 15033 bool doXNextEvent(Display* display) { 15034 bool done; 15035 XEvent e; 15036 XNextEvent(display, &e); 15037 version(sddddd) { 15038 import std.stdio, std.conv : to; 15039 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15040 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 15041 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 15042 } 15043 } 15044 15045 // filter out compose events 15046 if (XFilterEvent(&e, None)) { 15047 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 15048 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 15049 return false; 15050 } 15051 // process keyboard mapping changes 15052 if (e.type == EventType.KeymapNotify) { 15053 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 15054 XRefreshKeyboardMapping(&e.xmapping); 15055 return false; 15056 } 15057 15058 version(with_eventloop) 15059 import arsd.eventloop; 15060 15061 if(SimpleWindow.handleNativeGlobalEvent !is null) { 15062 // see windows impl's comments 15063 XUnlockDisplay(display); 15064 scope(exit) XLockDisplay(display); 15065 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 15066 if(ret == 0) 15067 return done; 15068 } 15069 15070 15071 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15072 if(win.getNativeEventHandler !is null) { 15073 XUnlockDisplay(display); 15074 scope(exit) XLockDisplay(display); 15075 auto ret = win.getNativeEventHandler()(e); 15076 if(ret == 0) 15077 return done; 15078 } 15079 } 15080 15081 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 15082 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 15083 // we get this because of the RRScreenChangeNotifyMask 15084 15085 // this isn't actually an ideal way to do it since it wastes time 15086 // but meh it is simple and it works. 15087 win.actualDpiLoadAttempted = false; 15088 SimpleWindow.xRandrInfoLoadAttemped = false; 15089 win.updateActualDpi(); // trigger a reload 15090 } 15091 } 15092 15093 switch(e.type) { 15094 case EventType.SelectionClear: 15095 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15096 // FIXME so it is supposed to finish any in progress transfers... but idk... 15097 //import std.stdio; writeln("SelectionClear"); 15098 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15099 } 15100 break; 15101 case EventType.SelectionRequest: 15102 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15103 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15104 // import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15105 XUnlockDisplay(display); 15106 scope(exit) XLockDisplay(display); 15107 (*ssh).handleRequest(e); 15108 } 15109 break; 15110 case EventType.PropertyNotify: 15111 // import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15112 15113 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15114 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15115 ssh.sendMoreIncr(&e.xproperty); 15116 } 15117 15118 15119 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15120 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15121 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15122 Atom target; 15123 int format; 15124 arch_ulong bytesafter, length; 15125 void* value; 15126 15127 ubyte[] s; 15128 Atom targetToKeep; 15129 15130 XGetWindowProperty( 15131 e.xproperty.display, 15132 e.xproperty.window, 15133 e.xproperty.atom, 15134 0, 15135 100000 /* length */, 15136 true, /* erase it to signal we got it and want more */ 15137 0 /*AnyPropertyType*/, 15138 &target, &format, &length, &bytesafter, &value); 15139 15140 if(!targetToKeep) 15141 targetToKeep = target; 15142 15143 auto id = (cast(ubyte*) value)[0 .. length]; 15144 15145 handler.handleIncrData(targetToKeep, id); 15146 15147 XFree(value); 15148 } 15149 } 15150 break; 15151 case EventType.SelectionNotify: 15152 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15153 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15154 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15155 XUnlockDisplay(display); 15156 scope(exit) XLockDisplay(display); 15157 handler.handleData(None, null); 15158 } else { 15159 Atom target; 15160 int format; 15161 arch_ulong bytesafter, length; 15162 void* value; 15163 XGetWindowProperty( 15164 e.xselection.display, 15165 e.xselection.requestor, 15166 e.xselection.property, 15167 0, 15168 100000 /* length */, 15169 //false, /* don't erase it */ 15170 true, /* do erase it lol */ 15171 0 /*AnyPropertyType*/, 15172 &target, &format, &length, &bytesafter, &value); 15173 15174 // FIXME: I don't have to copy it now since it is in char[] instead of string 15175 15176 { 15177 XUnlockDisplay(display); 15178 scope(exit) XLockDisplay(display); 15179 15180 if(target == XA_ATOM) { 15181 // initial request, see what they are able to work with and request the best one 15182 // we can handle, if available 15183 15184 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15185 Atom best = handler.findBestFormat(answer); 15186 15187 /+ 15188 writeln("got ", answer); 15189 foreach(a; answer) 15190 printf("%s\n", XGetAtomName(display, a)); 15191 writeln("best ", best); 15192 +/ 15193 15194 if(best != None) { 15195 // actually request the best format 15196 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15197 } 15198 } else if(target == GetAtom!"INCR"(display)) { 15199 // incremental 15200 15201 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 15202 15203 // signal the sending program that we see 15204 // the incr and are ready to receive more. 15205 XDeleteProperty( 15206 e.xselection.display, 15207 e.xselection.requestor, 15208 e.xselection.property); 15209 } else { 15210 // unsupported type... maybe, forward 15211 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 15212 } 15213 } 15214 XFree(value); 15215 /* 15216 XDeleteProperty( 15217 e.xselection.display, 15218 e.xselection.requestor, 15219 e.xselection.property); 15220 */ 15221 } 15222 } 15223 break; 15224 case EventType.ConfigureNotify: 15225 auto event = e.xconfigure; 15226 if(auto win = event.window in SimpleWindow.nativeMapping) { 15227 if(win.windowType == WindowTypes.minimallyWrapped) 15228 break; 15229 //version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); } 15230 15231 /+ 15232 The ICCCM says window managers must send a synthetic event when the window 15233 is moved but NOT when it is resized. In the resize case, an event is sent 15234 with position (0, 0) which can be wrong and break the dpi calculations. 15235 15236 So we only consider the synthetic events from the WM and otherwise 15237 need to wait for some other event to get the position which... sucks. 15238 15239 I'd rather not have windows changing their layout on mouse motion after 15240 switching monitors... might be forced to but for now just ignoring it. 15241 15242 Easiest way to switch monitors without sending a size position is by 15243 maximize or fullscreen in a setup like mine, but on most setups those 15244 work on the monitor it is already living on, so it should be ok most the 15245 time. 15246 +/ 15247 if(event.send_event) { 15248 win.screenPositionKnown = true; 15249 win.screenPositionX = event.x; 15250 win.screenPositionY = event.y; 15251 win.updateActualDpi(); 15252 } 15253 15254 win.updateIMEPopupLocation(); 15255 recordX11ResizeAsync(display, *win, event.width, event.height); 15256 } 15257 break; 15258 case EventType.Expose: 15259 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 15260 if(win.windowType == WindowTypes.minimallyWrapped) 15261 break; 15262 // if it is closing from a popup menu, it can get 15263 // an Expose event right by the end and trigger a 15264 // BadDrawable error ... we'll just check 15265 // closed to handle that. 15266 if((*win).closed) break; 15267 if((*win).openglMode == OpenGlOptions.no) { 15268 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 15269 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 15270 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); 15271 } else { 15272 // need to redraw the scene somehow 15273 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 15274 XUnlockDisplay(display); 15275 scope(exit) XLockDisplay(display); 15276 version(without_opengl) {} else 15277 win.redrawOpenGlSceneSoon(); 15278 } 15279 } 15280 } 15281 break; 15282 case EventType.FocusIn: 15283 case EventType.FocusOut: 15284 15285 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15286 /+ 15287 15288 void info(string detail) { 15289 string s; 15290 import std.conv; 15291 import std.datetime; 15292 s ~= to!string(Clock.currTime); 15293 s ~= " "; 15294 s ~= e.type == EventType.FocusIn ? "in " : "out"; 15295 s ~= " "; 15296 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 15297 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 15298 s ~= detail; 15299 s ~= " "; 15300 15301 sdpyPrintDebugString(s); 15302 15303 } 15304 15305 switch(e.xfocus.detail) { 15306 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 15307 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 15308 case NotifyDetail.NotifyInferior: info("Inferior"); break; 15309 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 15310 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 15311 case NotifyDetail.NotifyPointer: info("pointer"); break; 15312 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 15313 case NotifyDetail.NotifyDetailNone: info("none"); break; 15314 default: 15315 15316 } 15317 +/ 15318 15319 15320 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 15321 break; // just ignore these they seem irrelevant 15322 15323 auto old = win._focused; 15324 win._focused = e.type == EventType.FocusIn; 15325 15326 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 15327 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 15328 win._focused = true; 15329 15330 if(win.demandingAttention) 15331 demandAttention(*win, false); 15332 15333 win.updateIMEFocused(); 15334 15335 if(old != win._focused && win.onFocusChange) { 15336 XUnlockDisplay(display); 15337 scope(exit) XLockDisplay(display); 15338 win.onFocusChange(win._focused); 15339 } 15340 } 15341 break; 15342 case EventType.VisibilityNotify: 15343 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15344 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 15345 if (win.visibilityChanged !is null) { 15346 XUnlockDisplay(display); 15347 scope(exit) XLockDisplay(display); 15348 win.visibilityChanged(false); 15349 } 15350 } else { 15351 if (win.visibilityChanged !is null) { 15352 XUnlockDisplay(display); 15353 scope(exit) XLockDisplay(display); 15354 win.visibilityChanged(true); 15355 } 15356 } 15357 } 15358 break; 15359 case EventType.ClientMessage: 15360 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 15361 // "ignore next mouse motion" event, increment ignore counter for teh window 15362 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15363 ++(*win).warpEventCount; 15364 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 15365 } else { 15366 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 15367 } 15368 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 15369 // user clicked the close button on the window manager 15370 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15371 XUnlockDisplay(display); 15372 scope(exit) XLockDisplay(display); 15373 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 15374 } 15375 15376 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 15377 //import std.stdio; writeln("HAPPENED"); 15378 // user clicked the close button on the window manager 15379 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15380 XUnlockDisplay(display); 15381 scope(exit) XLockDisplay(display); 15382 15383 auto setTo = *win; 15384 15385 if(win.setRequestedInputFocus !is null) { 15386 auto s = win.setRequestedInputFocus(); 15387 if(s !is null) { 15388 setTo = s; 15389 } 15390 } 15391 15392 assert(setTo !is null); 15393 15394 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 15395 15396 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 15397 } 15398 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 15399 foreach(nai; NotificationAreaIcon.activeIcons) 15400 nai.newManager(); 15401 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15402 15403 bool xDragWindow = true; 15404 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 15405 //XDefineCursor(display, xDragWindow.impl.window, 15406 //import std.stdio; writeln("XdndStatus ", e.xclient.data.l); 15407 } 15408 if(auto dh = win.dropHandler) { 15409 15410 static Atom[3] xFormatsBuffer; 15411 static Atom[] xFormats; 15412 15413 void resetXFormats() { 15414 xFormatsBuffer[] = 0; 15415 xFormats = xFormatsBuffer[]; 15416 } 15417 15418 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 15419 // on Windows it is supposed to return the effect you actually do FIXME 15420 15421 auto sourceWindow = e.xclient.data.l[0]; 15422 15423 xFormatsBuffer[0] = e.xclient.data.l[2]; 15424 xFormatsBuffer[1] = e.xclient.data.l[3]; 15425 xFormatsBuffer[2] = e.xclient.data.l[4]; 15426 15427 if(e.xclient.data.l[1] & 1) { 15428 // can just grab it all but like we don't necessarily need them... 15429 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 15430 } else { 15431 int len; 15432 foreach(fmt; xFormatsBuffer) 15433 if(fmt) len++; 15434 xFormats = xFormatsBuffer[0 .. len]; 15435 } 15436 15437 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 15438 15439 dh.dragEnter(&pkg); 15440 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 15441 15442 auto pack = e.xclient.data.l[2]; 15443 15444 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 15445 15446 15447 XClientMessageEvent xclient; 15448 15449 xclient.type = EventType.ClientMessage; 15450 xclient.window = e.xclient.data.l[0]; 15451 xclient.message_type = GetAtom!"XdndStatus"(display); 15452 xclient.format = 32; 15453 xclient.data.l[0] = win.impl.window; 15454 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 15455 auto r = result.consistentWithin; 15456 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 15457 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 15458 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 15459 15460 XSendEvent( 15461 display, 15462 e.xclient.data.l[0], 15463 false, 15464 EventMask.NoEventMask, 15465 cast(XEvent*) &xclient 15466 ); 15467 15468 15469 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 15470 //import std.stdio; writeln("XdndLeave"); 15471 // drop cancelled. 15472 // data.l[0] is the source window 15473 dh.dragLeave(); 15474 15475 resetXFormats(); 15476 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 15477 // drop happening, should fetch data, then send finished 15478 //import std.stdio; writeln("XdndDrop"); 15479 15480 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 15481 15482 dh.drop(&pkg); 15483 15484 resetXFormats(); 15485 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 15486 // import std.stdio; writeln("XdndFinished"); 15487 15488 dh.finish(); 15489 } 15490 15491 } 15492 } 15493 break; 15494 case EventType.MapNotify: 15495 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 15496 (*win)._visible = true; 15497 if (!(*win)._visibleForTheFirstTimeCalled) { 15498 (*win)._visibleForTheFirstTimeCalled = true; 15499 if ((*win).visibleForTheFirstTime !is null) { 15500 XUnlockDisplay(display); 15501 scope(exit) XLockDisplay(display); 15502 (*win).visibleForTheFirstTime(); 15503 } 15504 } 15505 if ((*win).visibilityChanged !is null) { 15506 XUnlockDisplay(display); 15507 scope(exit) XLockDisplay(display); 15508 (*win).visibilityChanged(true); 15509 } 15510 } 15511 break; 15512 case EventType.UnmapNotify: 15513 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 15514 win._visible = false; 15515 if (win.visibilityChanged !is null) { 15516 XUnlockDisplay(display); 15517 scope(exit) XLockDisplay(display); 15518 win.visibilityChanged(false); 15519 } 15520 } 15521 break; 15522 case EventType.DestroyNotify: 15523 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 15524 if(win.destroyed) 15525 break; // might get a notification both for itself and from its parent 15526 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 15527 win._closed = true; // just in case 15528 win.destroyed = true; 15529 if (win.xic !is null) { 15530 XDestroyIC(win.xic); 15531 win.xic = null; // just in case 15532 } 15533 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 15534 bool anyImportant = false; 15535 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 15536 if(w.beingOpenKeepsAppOpen) { 15537 anyImportant = true; 15538 break; 15539 } 15540 if(!anyImportant) { 15541 EventLoop.quitApplication(); 15542 done = true; 15543 } 15544 } 15545 auto window = e.xdestroywindow.window; 15546 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 15547 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 15548 15549 version(with_eventloop) { 15550 if(done) exit(); 15551 } 15552 break; 15553 15554 case EventType.MotionNotify: 15555 MouseEvent mouse; 15556 auto event = e.xmotion; 15557 15558 mouse.type = MouseEventType.motion; 15559 mouse.x = event.x; 15560 mouse.y = event.y; 15561 mouse.modifierState = event.state; 15562 15563 mouse.timestamp = event.time; 15564 15565 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 15566 mouse.window = *win; 15567 if (win.warpEventCount > 0) { 15568 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 15569 --(*win).warpEventCount; 15570 (*win).mdx(mouse); // so deltas will be correctly updated 15571 } else { 15572 win.warpEventCount = 0; // just in case 15573 (*win).mdx(mouse); 15574 if((*win).handleMouseEvent) { 15575 XUnlockDisplay(display); 15576 scope(exit) XLockDisplay(display); 15577 (*win).handleMouseEvent(mouse); 15578 } 15579 } 15580 } 15581 15582 version(with_eventloop) 15583 send(mouse); 15584 break; 15585 case EventType.ButtonPress: 15586 case EventType.ButtonRelease: 15587 MouseEvent mouse; 15588 auto event = e.xbutton; 15589 15590 mouse.timestamp = event.time; 15591 15592 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 15593 mouse.x = event.x; 15594 mouse.y = event.y; 15595 15596 static Time lastMouseDownTime = 0; 15597 static int lastMouseDownButton = -1; 15598 15599 mouse.doubleClick = e.type == EventType.ButtonPress && event.button == lastMouseDownButton && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 15600 if(e.type == EventType.ButtonPress) { 15601 lastMouseDownTime = event.time; 15602 lastMouseDownButton = event.button; 15603 } 15604 15605 switch(event.button) { 15606 case 1: mouse.button = MouseButton.left; break; // left 15607 case 2: mouse.button = MouseButton.middle; break; // middle 15608 case 3: mouse.button = MouseButton.right; break; // right 15609 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 15610 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 15611 case 6: break; // idk 15612 case 7: break; // idk 15613 case 8: mouse.button = MouseButton.backButton; break; 15614 case 9: mouse.button = MouseButton.forwardButton; break; 15615 default: 15616 } 15617 15618 // FIXME: double check this 15619 mouse.modifierState = event.state; 15620 15621 //mouse.modifierState = event.detail; 15622 15623 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 15624 mouse.window = *win; 15625 (*win).mdx(mouse); 15626 if((*win).handleMouseEvent) { 15627 XUnlockDisplay(display); 15628 scope(exit) XLockDisplay(display); 15629 (*win).handleMouseEvent(mouse); 15630 } 15631 } 15632 version(with_eventloop) 15633 send(mouse); 15634 break; 15635 15636 case EventType.KeyPress: 15637 case EventType.KeyRelease: 15638 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 15639 KeyEvent ke; 15640 ke.pressed = e.type == EventType.KeyPress; 15641 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 15642 15643 auto sym = XKeycodeToKeysym( 15644 XDisplayConnection.get(), 15645 e.xkey.keycode, 15646 0); 15647 15648 ke.key = cast(Key) sym;//e.xkey.keycode; 15649 15650 ke.modifierState = e.xkey.state; 15651 15652 // import std.stdio; writefln("%x", sym); 15653 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 15654 int charbuflen = 0; // return value of XwcLookupString 15655 if (ke.pressed) { 15656 auto win = e.xkey.window in SimpleWindow.nativeMapping; 15657 if (win !is null && win.xic !is null) { 15658 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 15659 Status status; 15660 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 15661 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 15662 } else { 15663 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 15664 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 15665 char[16] buffer; 15666 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 15667 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 15668 } 15669 } 15670 15671 // if there's no char, subst one 15672 if (charbuflen == 0) { 15673 switch (sym) { 15674 case 0xff09: charbuf[charbuflen++] = '\t'; break; 15675 case 0xff8d: // keypad enter 15676 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 15677 default : // ignore 15678 } 15679 } 15680 15681 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 15682 ke.window = *win; 15683 15684 15685 if(win.inputProxy) 15686 win = &win.inputProxy; 15687 15688 // char events are separate since they are on Windows too 15689 // also, xcompose can generate long char sequences 15690 // don't send char events if Meta and/or Hyper is pressed 15691 // TODO: ctrl+char should only send control chars; not yet 15692 if ((e.xkey.state&ModifierState.ctrl) != 0) { 15693 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 15694 } 15695 15696 dchar[32] charsComingBuffer; 15697 int charsComingPosition; 15698 dchar[] charsComing = charsComingBuffer[]; 15699 15700 if (ke.pressed && charbuflen > 0) { 15701 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 15702 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 15703 if(charsComingPosition >= charsComing.length) 15704 charsComing.length = charsComingPosition + 8; 15705 15706 charsComing[charsComingPosition++] = ch; 15707 } 15708 15709 charsComing = charsComing[0 .. charsComingPosition]; 15710 } else { 15711 charsComing = null; 15712 } 15713 15714 ke.charsPossible = charsComing; 15715 15716 if (win.handleKeyEvent) { 15717 XUnlockDisplay(display); 15718 scope(exit) XLockDisplay(display); 15719 win.handleKeyEvent(ke); 15720 } 15721 15722 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 15723 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 15724 XUnlockDisplay(display); 15725 scope(exit) XLockDisplay(display); 15726 foreach(ch; charsComing) 15727 win.handleCharEvent(ch); 15728 } 15729 } 15730 15731 version(with_eventloop) 15732 send(ke); 15733 break; 15734 default: 15735 } 15736 15737 return done; 15738 } 15739 } 15740 15741 /* *************************************** */ 15742 /* Done with simpledisplay stuff */ 15743 /* *************************************** */ 15744 15745 // Necessary C library bindings follow 15746 version(Windows) {} else 15747 version(X11) { 15748 15749 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 15750 15751 // X11 bindings needed here 15752 /* 15753 A little of this is from the bindings project on 15754 D Source and some of it is copy/paste from the C 15755 header. 15756 15757 The DSource listing consistently used D's long 15758 where C used long. That's wrong - C long is 32 bit, so 15759 it should be int in D. I changed that here. 15760 15761 Note: 15762 This isn't complete, just took what I needed for myself. 15763 */ 15764 15765 import core.stdc.stddef : wchar_t; 15766 15767 interface XLib { 15768 extern(C) nothrow @nogc { 15769 char* XResourceManagerString(Display*); 15770 void XrmInitialize(); 15771 XrmDatabase XrmGetStringDatabase(char* data); 15772 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 15773 15774 Cursor XCreateFontCursor(Display*, uint shape); 15775 int XDefineCursor(Display* display, Window w, Cursor cursor); 15776 int XUndefineCursor(Display* display, Window w); 15777 15778 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 15779 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 15780 int XFreeCursor(Display* display, Cursor cursor); 15781 15782 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 15783 15784 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 15785 15786 XVaNestedList XVaCreateNestedList(int unused, ...); 15787 15788 char *XKeysymToString(KeySym keysym); 15789 KeySym XKeycodeToKeysym( 15790 Display* /* display */, 15791 KeyCode /* keycode */, 15792 int /* index */ 15793 ); 15794 15795 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 15796 15797 int XFree(void*); 15798 int XDeleteProperty(Display *display, Window w, Atom property); 15799 15800 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements); 15801 15802 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 15803 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 15804 *actual_type_return, int *actual_format_return, arch_ulong 15805 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 15806 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 15807 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 15808 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 15809 15810 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 15811 15812 Window XGetSelectionOwner(Display *display, Atom selection); 15813 15814 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 15815 15816 char** XListFonts(Display*, const char*, int, int*); 15817 void XFreeFontNames(char**); 15818 15819 Display* XOpenDisplay(const char*); 15820 int XCloseDisplay(Display*); 15821 15822 int function() XSynchronize(Display*, bool); 15823 int function() XSetAfterFunction(Display*, int function() proc); 15824 15825 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 15826 15827 Bool XSupportsLocale(); 15828 char* XSetLocaleModifiers(const(char)* modifier_list); 15829 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 15830 Status XCloseOM(XOM om); 15831 15832 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 15833 Status XCloseIM(XIM im); 15834 15835 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 15836 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 15837 Display* XDisplayOfIM(XIM im); 15838 char* XLocaleOfIM(XIM im); 15839 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 15840 void XDestroyIC(XIC ic); 15841 void XSetICFocus(XIC ic); 15842 void XUnsetICFocus(XIC ic); 15843 //wchar_t* XwcResetIC(XIC ic); 15844 char* XmbResetIC(XIC ic); 15845 char* Xutf8ResetIC(XIC ic); 15846 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 15847 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 15848 XIM XIMOfIC(XIC ic); 15849 15850 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 15851 15852 15853 XFontStruct *XLoadQueryFont(Display *display, in char *name); 15854 int XFreeFont(Display *display, XFontStruct *font_struct); 15855 int XSetFont(Display* display, GC gc, Font font); 15856 int XTextWidth(XFontStruct*, in char*, int); 15857 15858 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 15859 int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n); 15860 15861 Window XCreateSimpleWindow( 15862 Display* /* display */, 15863 Window /* parent */, 15864 int /* x */, 15865 int /* y */, 15866 uint /* width */, 15867 uint /* height */, 15868 uint /* border_width */, 15869 uint /* border */, 15870 uint /* background */ 15871 ); 15872 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); 15873 15874 int XReparentWindow(Display*, Window, Window, int, int); 15875 int XClearWindow(Display*, Window); 15876 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 15877 int XMoveWindow(Display*, Window, int, int); 15878 int XResizeWindow(Display *display, Window w, uint width, uint height); 15879 15880 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 15881 15882 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 15883 15884 XImage *XCreateImage( 15885 Display* /* display */, 15886 Visual* /* visual */, 15887 uint /* depth */, 15888 int /* format */, 15889 int /* offset */, 15890 ubyte* /* data */, 15891 uint /* width */, 15892 uint /* height */, 15893 int /* bitmap_pad */, 15894 int /* bytes_per_line */ 15895 ); 15896 15897 Status XInitImage (XImage* image); 15898 15899 Atom XInternAtom( 15900 Display* /* display */, 15901 const char* /* atom_name */, 15902 Bool /* only_if_exists */ 15903 ); 15904 15905 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 15906 char* XGetAtomName(Display*, Atom); 15907 Status XGetAtomNames(Display*, Atom*, int count, char**); 15908 15909 int XPutImage( 15910 Display* /* display */, 15911 Drawable /* d */, 15912 GC /* gc */, 15913 XImage* /* image */, 15914 int /* src_x */, 15915 int /* src_y */, 15916 int /* dest_x */, 15917 int /* dest_y */, 15918 uint /* width */, 15919 uint /* height */ 15920 ); 15921 15922 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 15923 15924 15925 int XDestroyWindow( 15926 Display* /* display */, 15927 Window /* w */ 15928 ); 15929 15930 int XDestroyImage(XImage*); 15931 15932 int XSelectInput( 15933 Display* /* display */, 15934 Window /* w */, 15935 EventMask /* event_mask */ 15936 ); 15937 15938 int XMapWindow( 15939 Display* /* display */, 15940 Window /* w */ 15941 ); 15942 15943 Status XIconifyWindow(Display*, Window, int); 15944 int XMapRaised(Display*, Window); 15945 int XMapSubwindows(Display*, Window); 15946 15947 int XNextEvent( 15948 Display* /* display */, 15949 XEvent* /* event_return */ 15950 ); 15951 15952 int XMaskEvent(Display*, arch_long, XEvent*); 15953 15954 Bool XFilterEvent(XEvent *event, Window window); 15955 int XRefreshKeyboardMapping(XMappingEvent *event_map); 15956 15957 Status XSetWMProtocols( 15958 Display* /* display */, 15959 Window /* w */, 15960 Atom* /* protocols */, 15961 int /* count */ 15962 ); 15963 15964 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 15965 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 15966 15967 15968 Status XInitThreads(); 15969 void XLockDisplay (Display* display); 15970 void XUnlockDisplay (Display* display); 15971 15972 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 15973 15974 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 15975 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 15976 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 15977 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 15978 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 15979 15980 15981 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 15982 int XDrawString(Display*, Drawable, GC, int, int, in char*, int); 15983 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 15984 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 15985 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 15986 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 15987 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 15988 int XDrawPoint(Display*, Drawable, GC, int, int); 15989 int XSetForeground(Display*, GC, uint); 15990 int XSetBackground(Display*, GC, uint); 15991 15992 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 15993 void XFreeFontSet(Display*, XFontSet); 15994 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int); 15995 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 15996 15997 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 15998 15999 16000 //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); 16001 16002 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 16003 int XSetFunction(Display*, GC, int); 16004 16005 GC XCreateGC(Display*, Drawable, uint, void*); 16006 int XCopyGC(Display*, GC, uint, GC); 16007 int XFreeGC(Display*, GC); 16008 16009 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 16010 bool XCheckMaskEvent(Display*, int, XEvent*); 16011 16012 int XPending(Display*); 16013 int XEventsQueued(Display* display, int mode); 16014 16015 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 16016 int XFreePixmap(Display*, Pixmap); 16017 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 16018 int XFlush(Display*); 16019 int XBell(Display*, int); 16020 int XSync(Display*, bool); 16021 16022 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 16023 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 16024 16025 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 16026 int XUngrabKeyboard(Display*, Time); 16027 16028 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 16029 16030 KeySym XStringToKeysym(const char *string); 16031 16032 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 16033 16034 Window XDefaultRootWindow(Display*); 16035 16036 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); 16037 16038 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 16039 16040 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 16041 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 16042 16043 Status XAllocColor(Display*, Colormap, XColor*); 16044 16045 int XWithdrawWindow(Display*, Window, int); 16046 int XUnmapWindow(Display*, Window); 16047 int XLowerWindow(Display*, Window); 16048 int XRaiseWindow(Display*, Window); 16049 16050 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); 16051 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); 16052 16053 int XGetInputFocus(Display*, Window*, int*); 16054 int XSetInputFocus(Display*, Window, int, Time); 16055 16056 XErrorHandler XSetErrorHandler(XErrorHandler); 16057 16058 int XGetErrorText(Display*, int, char*, int); 16059 16060 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 16061 16062 16063 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); 16064 int XUngrabPointer(Display *display, Time time); 16065 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 16066 16067 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 16068 16069 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 16070 int XSetClipMask(Display*, GC, Pixmap); 16071 int XSetClipOrigin(Display*, GC, int, int); 16072 16073 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 16074 16075 void XSetWMName(Display*, Window, XTextProperty*); 16076 Status XGetWMName(Display*, Window, XTextProperty*); 16077 int XStoreName(Display* display, Window w, const(char)* window_name); 16078 16079 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 16080 16081 } 16082 } 16083 16084 interface Xext { 16085 extern(C) nothrow @nogc { 16086 Status XShmAttach(Display*, XShmSegmentInfo*); 16087 Status XShmDetach(Display*, XShmSegmentInfo*); 16088 Status XShmPutImage( 16089 Display* /* dpy */, 16090 Drawable /* d */, 16091 GC /* gc */, 16092 XImage* /* image */, 16093 int /* src_x */, 16094 int /* src_y */, 16095 int /* dst_x */, 16096 int /* dst_y */, 16097 uint /* src_width */, 16098 uint /* src_height */, 16099 Bool /* send_event */ 16100 ); 16101 16102 Status XShmQueryExtension(Display*); 16103 16104 XImage *XShmCreateImage( 16105 Display* /* dpy */, 16106 Visual* /* visual */, 16107 uint /* depth */, 16108 int /* format */, 16109 char* /* data */, 16110 XShmSegmentInfo* /* shminfo */, 16111 uint /* width */, 16112 uint /* height */ 16113 ); 16114 16115 Pixmap XShmCreatePixmap( 16116 Display* /* dpy */, 16117 Drawable /* d */, 16118 char* /* data */, 16119 XShmSegmentInfo* /* shminfo */, 16120 uint /* width */, 16121 uint /* height */, 16122 uint /* depth */ 16123 ); 16124 16125 } 16126 } 16127 16128 // this requires -lXpm 16129 //int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16130 16131 16132 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16133 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16134 shared static this() { 16135 xlib.loadDynamicLibrary(); 16136 xext.loadDynamicLibrary(); 16137 } 16138 16139 16140 extern(C) nothrow @nogc { 16141 16142 alias XrmDatabase = void*; 16143 struct XrmValue { 16144 uint size; 16145 void* addr; 16146 } 16147 16148 struct XVisualInfo { 16149 Visual* visual; 16150 VisualID visualid; 16151 int screen; 16152 uint depth; 16153 int c_class; 16154 c_ulong red_mask; 16155 c_ulong green_mask; 16156 c_ulong blue_mask; 16157 int colormap_size; 16158 int bits_per_rgb; 16159 } 16160 16161 enum VisualNoMask= 0x0; 16162 enum VisualIDMask= 0x1; 16163 enum VisualScreenMask=0x2; 16164 enum VisualDepthMask= 0x4; 16165 enum VisualClassMask= 0x8; 16166 enum VisualRedMaskMask=0x10; 16167 enum VisualGreenMaskMask=0x20; 16168 enum VisualBlueMaskMask=0x40; 16169 enum VisualColormapSizeMask=0x80; 16170 enum VisualBitsPerRGBMask=0x100; 16171 enum VisualAllMask= 0x1FF; 16172 16173 enum AnyKey = 0; 16174 enum AnyModifier = 1 << 15; 16175 16176 // XIM and other crap 16177 struct _XOM {} 16178 struct _XIM {} 16179 struct _XIC {} 16180 alias XOM = _XOM*; 16181 alias XIM = _XIM*; 16182 alias XIC = _XIC*; 16183 16184 alias XVaNestedList = void*; 16185 16186 alias XIMStyle = arch_ulong; 16187 enum : arch_ulong { 16188 XIMPreeditArea = 0x0001, 16189 XIMPreeditCallbacks = 0x0002, 16190 XIMPreeditPosition = 0x0004, 16191 XIMPreeditNothing = 0x0008, 16192 XIMPreeditNone = 0x0010, 16193 XIMStatusArea = 0x0100, 16194 XIMStatusCallbacks = 0x0200, 16195 XIMStatusNothing = 0x0400, 16196 XIMStatusNone = 0x0800, 16197 } 16198 16199 16200 /* X Shared Memory Extension functions */ 16201 //pragma(lib, "Xshm"); 16202 alias arch_ulong ShmSeg; 16203 struct XShmSegmentInfo { 16204 ShmSeg shmseg; 16205 int shmid; 16206 ubyte* shmaddr; 16207 Bool readOnly; 16208 } 16209 16210 // and the necessary OS functions 16211 int shmget(int, size_t, int); 16212 void* shmat(int, in void*, int); 16213 int shmdt(in void*); 16214 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 16215 16216 enum IPC_PRIVATE = 0; 16217 enum IPC_CREAT = 512; 16218 enum IPC_RMID = 0; 16219 16220 /* MIT-SHM end */ 16221 16222 16223 enum MappingType:int { 16224 MappingModifier =0, 16225 MappingKeyboard =1, 16226 MappingPointer =2 16227 } 16228 16229 /* ImageFormat -- PutImage, GetImage */ 16230 enum ImageFormat:int { 16231 XYBitmap =0, /* depth 1, XYFormat */ 16232 XYPixmap =1, /* depth == drawable depth */ 16233 ZPixmap =2 /* depth == drawable depth */ 16234 } 16235 16236 enum ModifierName:int { 16237 ShiftMapIndex =0, 16238 LockMapIndex =1, 16239 ControlMapIndex =2, 16240 Mod1MapIndex =3, 16241 Mod2MapIndex =4, 16242 Mod3MapIndex =5, 16243 Mod4MapIndex =6, 16244 Mod5MapIndex =7 16245 } 16246 16247 enum ButtonMask:int { 16248 Button1Mask =1<<8, 16249 Button2Mask =1<<9, 16250 Button3Mask =1<<10, 16251 Button4Mask =1<<11, 16252 Button5Mask =1<<12, 16253 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16254 } 16255 16256 enum KeyOrButtonMask:uint { 16257 ShiftMask =1<<0, 16258 LockMask =1<<1, 16259 ControlMask =1<<2, 16260 Mod1Mask =1<<3, 16261 Mod2Mask =1<<4, 16262 Mod3Mask =1<<5, 16263 Mod4Mask =1<<6, 16264 Mod5Mask =1<<7, 16265 Button1Mask =1<<8, 16266 Button2Mask =1<<9, 16267 Button3Mask =1<<10, 16268 Button4Mask =1<<11, 16269 Button5Mask =1<<12, 16270 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16271 } 16272 16273 enum ButtonName:int { 16274 Button1 =1, 16275 Button2 =2, 16276 Button3 =3, 16277 Button4 =4, 16278 Button5 =5 16279 } 16280 16281 /* Notify modes */ 16282 enum NotifyModes:int 16283 { 16284 NotifyNormal =0, 16285 NotifyGrab =1, 16286 NotifyUngrab =2, 16287 NotifyWhileGrabbed =3 16288 } 16289 enum NotifyHint = 1; /* for MotionNotify events */ 16290 16291 /* Notify detail */ 16292 enum NotifyDetail:int 16293 { 16294 NotifyAncestor =0, 16295 NotifyVirtual =1, 16296 NotifyInferior =2, 16297 NotifyNonlinear =3, 16298 NotifyNonlinearVirtual =4, 16299 NotifyPointer =5, 16300 NotifyPointerRoot =6, 16301 NotifyDetailNone =7 16302 } 16303 16304 /* Visibility notify */ 16305 16306 enum VisibilityNotify:int 16307 { 16308 VisibilityUnobscured =0, 16309 VisibilityPartiallyObscured =1, 16310 VisibilityFullyObscured =2 16311 } 16312 16313 16314 enum WindowStackingMethod:int 16315 { 16316 Above =0, 16317 Below =1, 16318 TopIf =2, 16319 BottomIf =3, 16320 Opposite =4 16321 } 16322 16323 /* Circulation request */ 16324 enum CirculationRequest:int 16325 { 16326 PlaceOnTop =0, 16327 PlaceOnBottom =1 16328 } 16329 16330 enum PropertyNotification:int 16331 { 16332 PropertyNewValue =0, 16333 PropertyDelete =1 16334 } 16335 16336 enum ColorMapNotification:int 16337 { 16338 ColormapUninstalled =0, 16339 ColormapInstalled =1 16340 } 16341 16342 16343 struct _XPrivate {} 16344 struct _XrmHashBucketRec {} 16345 16346 alias void* XPointer; 16347 alias void* XExtData; 16348 16349 version( X86_64 ) { 16350 alias ulong XID; 16351 alias ulong arch_ulong; 16352 alias long arch_long; 16353 } else version (AArch64) { 16354 alias ulong XID; 16355 alias ulong arch_ulong; 16356 alias long arch_long; 16357 } else { 16358 alias uint XID; 16359 alias uint arch_ulong; 16360 alias int arch_long; 16361 } 16362 16363 alias XID Window; 16364 alias XID Drawable; 16365 alias XID Pixmap; 16366 16367 alias arch_ulong Atom; 16368 alias int Bool; 16369 alias Display XDisplay; 16370 16371 alias int ByteOrder; 16372 alias arch_ulong Time; 16373 alias void ScreenFormat; 16374 16375 struct XImage { 16376 int width, height; /* size of image */ 16377 int xoffset; /* number of pixels offset in X direction */ 16378 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 16379 void *data; /* pointer to image data */ 16380 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 16381 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 16382 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 16383 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 16384 int depth; /* depth of image */ 16385 int bytes_per_line; /* accelarator to next line */ 16386 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 16387 arch_ulong red_mask; /* bits in z arrangment */ 16388 arch_ulong green_mask; 16389 arch_ulong blue_mask; 16390 XPointer obdata; /* hook for the object routines to hang on */ 16391 static struct F { /* image manipulation routines */ 16392 XImage* function( 16393 XDisplay* /* display */, 16394 Visual* /* visual */, 16395 uint /* depth */, 16396 int /* format */, 16397 int /* offset */, 16398 ubyte* /* data */, 16399 uint /* width */, 16400 uint /* height */, 16401 int /* bitmap_pad */, 16402 int /* bytes_per_line */) create_image; 16403 int function(XImage *) destroy_image; 16404 arch_ulong function(XImage *, int, int) get_pixel; 16405 int function(XImage *, int, int, arch_ulong) put_pixel; 16406 XImage* function(XImage *, int, int, uint, uint) sub_image; 16407 int function(XImage *, arch_long) add_pixel; 16408 } 16409 F f; 16410 } 16411 version(X86_64) static assert(XImage.sizeof == 136); 16412 else version(X86) static assert(XImage.sizeof == 88); 16413 16414 struct XCharStruct { 16415 short lbearing; /* origin to left edge of raster */ 16416 short rbearing; /* origin to right edge of raster */ 16417 short width; /* advance to next char's origin */ 16418 short ascent; /* baseline to top edge of raster */ 16419 short descent; /* baseline to bottom edge of raster */ 16420 ushort attributes; /* per char flags (not predefined) */ 16421 } 16422 16423 /* 16424 * To allow arbitrary information with fonts, there are additional properties 16425 * returned. 16426 */ 16427 struct XFontProp { 16428 Atom name; 16429 arch_ulong card32; 16430 } 16431 16432 alias Atom Font; 16433 16434 struct XFontStruct { 16435 XExtData *ext_data; /* Hook for extension to hang data */ 16436 Font fid; /* Font ID for this font */ 16437 uint direction; /* Direction the font is painted */ 16438 uint min_char_or_byte2; /* First character */ 16439 uint max_char_or_byte2; /* Last character */ 16440 uint min_byte1; /* First row that exists (for two-byte fonts) */ 16441 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 16442 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 16443 uint default_char; /* Char to print for undefined character */ 16444 int n_properties; /* How many properties there are */ 16445 XFontProp *properties; /* Pointer to array of additional properties*/ 16446 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 16447 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 16448 XCharStruct *per_char; /* first_char to last_char information */ 16449 int ascent; /* Max extent above baseline for spacing */ 16450 int descent; /* Max descent below baseline for spacing */ 16451 } 16452 16453 16454 /* 16455 * Definitions of specific events. 16456 */ 16457 struct XKeyEvent 16458 { 16459 int type; /* of event */ 16460 arch_ulong serial; /* # of last request processed by server */ 16461 Bool send_event; /* true if this came from a SendEvent request */ 16462 Display *display; /* Display the event was read from */ 16463 Window window; /* "event" window it is reported relative to */ 16464 Window root; /* root window that the event occurred on */ 16465 Window subwindow; /* child window */ 16466 Time time; /* milliseconds */ 16467 int x, y; /* pointer x, y coordinates in event window */ 16468 int x_root, y_root; /* coordinates relative to root */ 16469 KeyOrButtonMask state; /* key or button mask */ 16470 uint keycode; /* detail */ 16471 Bool same_screen; /* same screen flag */ 16472 } 16473 version(X86_64) static assert(XKeyEvent.sizeof == 96); 16474 alias XKeyEvent XKeyPressedEvent; 16475 alias XKeyEvent XKeyReleasedEvent; 16476 16477 struct XButtonEvent 16478 { 16479 int type; /* of event */ 16480 arch_ulong serial; /* # of last request processed by server */ 16481 Bool send_event; /* true if this came from a SendEvent request */ 16482 Display *display; /* Display the event was read from */ 16483 Window window; /* "event" window it is reported relative to */ 16484 Window root; /* root window that the event occurred on */ 16485 Window subwindow; /* child window */ 16486 Time time; /* milliseconds */ 16487 int x, y; /* pointer x, y coordinates in event window */ 16488 int x_root, y_root; /* coordinates relative to root */ 16489 KeyOrButtonMask state; /* key or button mask */ 16490 uint button; /* detail */ 16491 Bool same_screen; /* same screen flag */ 16492 } 16493 alias XButtonEvent XButtonPressedEvent; 16494 alias XButtonEvent XButtonReleasedEvent; 16495 16496 struct XMotionEvent{ 16497 int type; /* of event */ 16498 arch_ulong serial; /* # of last request processed by server */ 16499 Bool send_event; /* true if this came from a SendEvent request */ 16500 Display *display; /* Display the event was read from */ 16501 Window window; /* "event" window reported relative to */ 16502 Window root; /* root window that the event occurred on */ 16503 Window subwindow; /* child window */ 16504 Time time; /* milliseconds */ 16505 int x, y; /* pointer x, y coordinates in event window */ 16506 int x_root, y_root; /* coordinates relative to root */ 16507 KeyOrButtonMask state; /* key or button mask */ 16508 byte is_hint; /* detail */ 16509 Bool same_screen; /* same screen flag */ 16510 } 16511 alias XMotionEvent XPointerMovedEvent; 16512 16513 struct XCrossingEvent{ 16514 int type; /* of event */ 16515 arch_ulong serial; /* # of last request processed by server */ 16516 Bool send_event; /* true if this came from a SendEvent request */ 16517 Display *display; /* Display the event was read from */ 16518 Window window; /* "event" window reported relative to */ 16519 Window root; /* root window that the event occurred on */ 16520 Window subwindow; /* child window */ 16521 Time time; /* milliseconds */ 16522 int x, y; /* pointer x, y coordinates in event window */ 16523 int x_root, y_root; /* coordinates relative to root */ 16524 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 16525 NotifyDetail detail; 16526 /* 16527 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16528 * NotifyNonlinear,NotifyNonlinearVirtual 16529 */ 16530 Bool same_screen; /* same screen flag */ 16531 Bool focus; /* Boolean focus */ 16532 KeyOrButtonMask state; /* key or button mask */ 16533 } 16534 alias XCrossingEvent XEnterWindowEvent; 16535 alias XCrossingEvent XLeaveWindowEvent; 16536 16537 struct XFocusChangeEvent{ 16538 int type; /* FocusIn or FocusOut */ 16539 arch_ulong serial; /* # of last request processed by server */ 16540 Bool send_event; /* true if this came from a SendEvent request */ 16541 Display *display; /* Display the event was read from */ 16542 Window window; /* window of event */ 16543 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 16544 NotifyGrab, NotifyUngrab */ 16545 NotifyDetail detail; 16546 /* 16547 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16548 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 16549 * NotifyPointerRoot, NotifyDetailNone 16550 */ 16551 } 16552 alias XFocusChangeEvent XFocusInEvent; 16553 alias XFocusChangeEvent XFocusOutEvent; 16554 16555 enum CWBackPixmap = (1L<<0); 16556 enum CWBackPixel = (1L<<1); 16557 enum CWBorderPixmap = (1L<<2); 16558 enum CWBorderPixel = (1L<<3); 16559 enum CWBitGravity = (1L<<4); 16560 enum CWWinGravity = (1L<<5); 16561 enum CWBackingStore = (1L<<6); 16562 enum CWBackingPlanes = (1L<<7); 16563 enum CWBackingPixel = (1L<<8); 16564 enum CWOverrideRedirect = (1L<<9); 16565 enum CWSaveUnder = (1L<<10); 16566 enum CWEventMask = (1L<<11); 16567 enum CWDontPropagate = (1L<<12); 16568 enum CWColormap = (1L<<13); 16569 enum CWCursor = (1L<<14); 16570 16571 struct XWindowAttributes { 16572 int x, y; /* location of window */ 16573 int width, height; /* width and height of window */ 16574 int border_width; /* border width of window */ 16575 int depth; /* depth of window */ 16576 Visual *visual; /* the associated visual structure */ 16577 Window root; /* root of screen containing window */ 16578 int class_; /* InputOutput, InputOnly*/ 16579 int bit_gravity; /* one of the bit gravity values */ 16580 int win_gravity; /* one of the window gravity values */ 16581 int backing_store; /* NotUseful, WhenMapped, Always */ 16582 arch_ulong backing_planes; /* planes to be preserved if possible */ 16583 arch_ulong backing_pixel; /* value to be used when restoring planes */ 16584 Bool save_under; /* boolean, should bits under be saved? */ 16585 Colormap colormap; /* color map to be associated with window */ 16586 Bool map_installed; /* boolean, is color map currently installed*/ 16587 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 16588 arch_long all_event_masks; /* set of events all people have interest in*/ 16589 arch_long your_event_mask; /* my event mask */ 16590 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 16591 Bool override_redirect; /* boolean value for override-redirect */ 16592 Screen *screen; /* back pointer to correct screen */ 16593 } 16594 16595 enum IsUnmapped = 0; 16596 enum IsUnviewable = 1; 16597 enum IsViewable = 2; 16598 16599 struct XSetWindowAttributes { 16600 Pixmap background_pixmap;/* background, None, or ParentRelative */ 16601 arch_ulong background_pixel;/* background pixel */ 16602 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 16603 arch_ulong border_pixel;/* border pixel value */ 16604 int bit_gravity; /* one of bit gravity values */ 16605 int win_gravity; /* one of the window gravity values */ 16606 int backing_store; /* NotUseful, WhenMapped, Always */ 16607 arch_ulong backing_planes;/* planes to be preserved if possible */ 16608 arch_ulong backing_pixel;/* value to use in restoring planes */ 16609 Bool save_under; /* should bits under be saved? (popups) */ 16610 arch_long event_mask; /* set of events that should be saved */ 16611 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 16612 Bool override_redirect; /* boolean value for override_redirect */ 16613 Colormap colormap; /* color map to be associated with window */ 16614 Cursor cursor; /* cursor to be displayed (or None) */ 16615 } 16616 16617 16618 alias int Status; 16619 16620 16621 enum EventMask:int 16622 { 16623 NoEventMask =0, 16624 KeyPressMask =1<<0, 16625 KeyReleaseMask =1<<1, 16626 ButtonPressMask =1<<2, 16627 ButtonReleaseMask =1<<3, 16628 EnterWindowMask =1<<4, 16629 LeaveWindowMask =1<<5, 16630 PointerMotionMask =1<<6, 16631 PointerMotionHintMask =1<<7, 16632 Button1MotionMask =1<<8, 16633 Button2MotionMask =1<<9, 16634 Button3MotionMask =1<<10, 16635 Button4MotionMask =1<<11, 16636 Button5MotionMask =1<<12, 16637 ButtonMotionMask =1<<13, 16638 KeymapStateMask =1<<14, 16639 ExposureMask =1<<15, 16640 VisibilityChangeMask =1<<16, 16641 StructureNotifyMask =1<<17, 16642 ResizeRedirectMask =1<<18, 16643 SubstructureNotifyMask =1<<19, 16644 SubstructureRedirectMask=1<<20, 16645 FocusChangeMask =1<<21, 16646 PropertyChangeMask =1<<22, 16647 ColormapChangeMask =1<<23, 16648 OwnerGrabButtonMask =1<<24 16649 } 16650 16651 struct MwmHints { 16652 c_ulong flags; 16653 c_ulong functions; 16654 c_ulong decorations; 16655 c_long input_mode; 16656 c_ulong status; 16657 } 16658 16659 enum { 16660 MWM_HINTS_FUNCTIONS = (1L << 0), 16661 MWM_HINTS_DECORATIONS = (1L << 1), 16662 16663 MWM_FUNC_ALL = (1L << 0), 16664 MWM_FUNC_RESIZE = (1L << 1), 16665 MWM_FUNC_MOVE = (1L << 2), 16666 MWM_FUNC_MINIMIZE = (1L << 3), 16667 MWM_FUNC_MAXIMIZE = (1L << 4), 16668 MWM_FUNC_CLOSE = (1L << 5), 16669 16670 MWM_DECOR_ALL = (1L << 0), 16671 MWM_DECOR_BORDER = (1L << 1), 16672 MWM_DECOR_RESIZEH = (1L << 2), 16673 MWM_DECOR_TITLE = (1L << 3), 16674 MWM_DECOR_MENU = (1L << 4), 16675 MWM_DECOR_MINIMIZE = (1L << 5), 16676 MWM_DECOR_MAXIMIZE = (1L << 6), 16677 } 16678 16679 import core.stdc.config : c_long, c_ulong; 16680 16681 /* Size hints mask bits */ 16682 16683 enum USPosition = (1L << 0) /* user specified x, y */; 16684 enum USSize = (1L << 1) /* user specified width, height */; 16685 enum PPosition = (1L << 2) /* program specified position */; 16686 enum PSize = (1L << 3) /* program specified size */; 16687 enum PMinSize = (1L << 4) /* program specified minimum size */; 16688 enum PMaxSize = (1L << 5) /* program specified maximum size */; 16689 enum PResizeInc = (1L << 6) /* program specified resize increments */; 16690 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 16691 enum PBaseSize = (1L << 8); 16692 enum PWinGravity = (1L << 9); 16693 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 16694 struct XSizeHints { 16695 arch_long flags; /* marks which fields in this structure are defined */ 16696 int x, y; /* Obsolete */ 16697 int width, height; /* Obsolete */ 16698 int min_width, min_height; 16699 int max_width, max_height; 16700 int width_inc, height_inc; 16701 struct Aspect { 16702 int x; /* numerator */ 16703 int y; /* denominator */ 16704 } 16705 16706 Aspect min_aspect; 16707 Aspect max_aspect; 16708 int base_width, base_height; 16709 int win_gravity; 16710 /* this structure may be extended in the future */ 16711 } 16712 16713 16714 16715 enum EventType:int 16716 { 16717 KeyPress =2, 16718 KeyRelease =3, 16719 ButtonPress =4, 16720 ButtonRelease =5, 16721 MotionNotify =6, 16722 EnterNotify =7, 16723 LeaveNotify =8, 16724 FocusIn =9, 16725 FocusOut =10, 16726 KeymapNotify =11, 16727 Expose =12, 16728 GraphicsExpose =13, 16729 NoExpose =14, 16730 VisibilityNotify =15, 16731 CreateNotify =16, 16732 DestroyNotify =17, 16733 UnmapNotify =18, 16734 MapNotify =19, 16735 MapRequest =20, 16736 ReparentNotify =21, 16737 ConfigureNotify =22, 16738 ConfigureRequest =23, 16739 GravityNotify =24, 16740 ResizeRequest =25, 16741 CirculateNotify =26, 16742 CirculateRequest =27, 16743 PropertyNotify =28, 16744 SelectionClear =29, 16745 SelectionRequest =30, 16746 SelectionNotify =31, 16747 ColormapNotify =32, 16748 ClientMessage =33, 16749 MappingNotify =34, 16750 LASTEvent =35 /* must be bigger than any event # */ 16751 } 16752 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 16753 struct XKeymapEvent 16754 { 16755 int type; 16756 arch_ulong serial; /* # of last request processed by server */ 16757 Bool send_event; /* true if this came from a SendEvent request */ 16758 Display *display; /* Display the event was read from */ 16759 Window window; 16760 byte[32] key_vector; 16761 } 16762 16763 struct XExposeEvent 16764 { 16765 int type; 16766 arch_ulong serial; /* # of last request processed by server */ 16767 Bool send_event; /* true if this came from a SendEvent request */ 16768 Display *display; /* Display the event was read from */ 16769 Window window; 16770 int x, y; 16771 int width, height; 16772 int count; /* if non-zero, at least this many more */ 16773 } 16774 16775 struct XGraphicsExposeEvent{ 16776 int type; 16777 arch_ulong serial; /* # of last request processed by server */ 16778 Bool send_event; /* true if this came from a SendEvent request */ 16779 Display *display; /* Display the event was read from */ 16780 Drawable drawable; 16781 int x, y; 16782 int width, height; 16783 int count; /* if non-zero, at least this many more */ 16784 int major_code; /* core is CopyArea or CopyPlane */ 16785 int minor_code; /* not defined in the core */ 16786 } 16787 16788 struct XNoExposeEvent{ 16789 int type; 16790 arch_ulong serial; /* # of last request processed by server */ 16791 Bool send_event; /* true if this came from a SendEvent request */ 16792 Display *display; /* Display the event was read from */ 16793 Drawable drawable; 16794 int major_code; /* core is CopyArea or CopyPlane */ 16795 int minor_code; /* not defined in the core */ 16796 } 16797 16798 struct XVisibilityEvent{ 16799 int type; 16800 arch_ulong serial; /* # of last request processed by server */ 16801 Bool send_event; /* true if this came from a SendEvent request */ 16802 Display *display; /* Display the event was read from */ 16803 Window window; 16804 VisibilityNotify state; /* Visibility state */ 16805 } 16806 16807 struct XCreateWindowEvent{ 16808 int type; 16809 arch_ulong serial; /* # of last request processed by server */ 16810 Bool send_event; /* true if this came from a SendEvent request */ 16811 Display *display; /* Display the event was read from */ 16812 Window parent; /* parent of the window */ 16813 Window window; /* window id of window created */ 16814 int x, y; /* window location */ 16815 int width, height; /* size of window */ 16816 int border_width; /* border width */ 16817 Bool override_redirect; /* creation should be overridden */ 16818 } 16819 16820 struct XDestroyWindowEvent 16821 { 16822 int type; 16823 arch_ulong serial; /* # of last request processed by server */ 16824 Bool send_event; /* true if this came from a SendEvent request */ 16825 Display *display; /* Display the event was read from */ 16826 Window event; 16827 Window window; 16828 } 16829 16830 struct XUnmapEvent 16831 { 16832 int type; 16833 arch_ulong serial; /* # of last request processed by server */ 16834 Bool send_event; /* true if this came from a SendEvent request */ 16835 Display *display; /* Display the event was read from */ 16836 Window event; 16837 Window window; 16838 Bool from_configure; 16839 } 16840 16841 struct XMapEvent 16842 { 16843 int type; 16844 arch_ulong serial; /* # of last request processed by server */ 16845 Bool send_event; /* true if this came from a SendEvent request */ 16846 Display *display; /* Display the event was read from */ 16847 Window event; 16848 Window window; 16849 Bool override_redirect; /* Boolean, is override set... */ 16850 } 16851 16852 struct XMapRequestEvent 16853 { 16854 int type; 16855 arch_ulong serial; /* # of last request processed by server */ 16856 Bool send_event; /* true if this came from a SendEvent request */ 16857 Display *display; /* Display the event was read from */ 16858 Window parent; 16859 Window window; 16860 } 16861 16862 struct XReparentEvent 16863 { 16864 int type; 16865 arch_ulong serial; /* # of last request processed by server */ 16866 Bool send_event; /* true if this came from a SendEvent request */ 16867 Display *display; /* Display the event was read from */ 16868 Window event; 16869 Window window; 16870 Window parent; 16871 int x, y; 16872 Bool override_redirect; 16873 } 16874 16875 struct XConfigureEvent 16876 { 16877 int type; 16878 arch_ulong serial; /* # of last request processed by server */ 16879 Bool send_event; /* true if this came from a SendEvent request */ 16880 Display *display; /* Display the event was read from */ 16881 Window event; 16882 Window window; 16883 int x, y; 16884 int width, height; 16885 int border_width; 16886 Window above; 16887 Bool override_redirect; 16888 } 16889 16890 struct XGravityEvent 16891 { 16892 int type; 16893 arch_ulong serial; /* # of last request processed by server */ 16894 Bool send_event; /* true if this came from a SendEvent request */ 16895 Display *display; /* Display the event was read from */ 16896 Window event; 16897 Window window; 16898 int x, y; 16899 } 16900 16901 struct XResizeRequestEvent 16902 { 16903 int type; 16904 arch_ulong serial; /* # of last request processed by server */ 16905 Bool send_event; /* true if this came from a SendEvent request */ 16906 Display *display; /* Display the event was read from */ 16907 Window window; 16908 int width, height; 16909 } 16910 16911 struct XConfigureRequestEvent 16912 { 16913 int type; 16914 arch_ulong serial; /* # of last request processed by server */ 16915 Bool send_event; /* true if this came from a SendEvent request */ 16916 Display *display; /* Display the event was read from */ 16917 Window parent; 16918 Window window; 16919 int x, y; 16920 int width, height; 16921 int border_width; 16922 Window above; 16923 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 16924 arch_ulong value_mask; 16925 } 16926 16927 struct XCirculateEvent 16928 { 16929 int type; 16930 arch_ulong serial; /* # of last request processed by server */ 16931 Bool send_event; /* true if this came from a SendEvent request */ 16932 Display *display; /* Display the event was read from */ 16933 Window event; 16934 Window window; 16935 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 16936 } 16937 16938 struct XCirculateRequestEvent 16939 { 16940 int type; 16941 arch_ulong serial; /* # of last request processed by server */ 16942 Bool send_event; /* true if this came from a SendEvent request */ 16943 Display *display; /* Display the event was read from */ 16944 Window parent; 16945 Window window; 16946 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 16947 } 16948 16949 struct XPropertyEvent 16950 { 16951 int type; 16952 arch_ulong serial; /* # of last request processed by server */ 16953 Bool send_event; /* true if this came from a SendEvent request */ 16954 Display *display; /* Display the event was read from */ 16955 Window window; 16956 Atom atom; 16957 Time time; 16958 PropertyNotification state; /* NewValue, Deleted */ 16959 } 16960 16961 struct XSelectionClearEvent 16962 { 16963 int type; 16964 arch_ulong serial; /* # of last request processed by server */ 16965 Bool send_event; /* true if this came from a SendEvent request */ 16966 Display *display; /* Display the event was read from */ 16967 Window window; 16968 Atom selection; 16969 Time time; 16970 } 16971 16972 struct XSelectionRequestEvent 16973 { 16974 int type; 16975 arch_ulong serial; /* # of last request processed by server */ 16976 Bool send_event; /* true if this came from a SendEvent request */ 16977 Display *display; /* Display the event was read from */ 16978 Window owner; 16979 Window requestor; 16980 Atom selection; 16981 Atom target; 16982 Atom property; 16983 Time time; 16984 } 16985 16986 struct XSelectionEvent 16987 { 16988 int type; 16989 arch_ulong serial; /* # of last request processed by server */ 16990 Bool send_event; /* true if this came from a SendEvent request */ 16991 Display *display; /* Display the event was read from */ 16992 Window requestor; 16993 Atom selection; 16994 Atom target; 16995 Atom property; /* ATOM or None */ 16996 Time time; 16997 } 16998 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 16999 17000 struct XColormapEvent 17001 { 17002 int type; 17003 arch_ulong serial; /* # of last request processed by server */ 17004 Bool send_event; /* true if this came from a SendEvent request */ 17005 Display *display; /* Display the event was read from */ 17006 Window window; 17007 Colormap colormap; /* COLORMAP or None */ 17008 Bool new_; /* C++ */ 17009 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 17010 } 17011 version(X86_64) static assert(XColormapEvent.sizeof == 56); 17012 17013 struct XClientMessageEvent 17014 { 17015 int type; 17016 arch_ulong serial; /* # of last request processed by server */ 17017 Bool send_event; /* true if this came from a SendEvent request */ 17018 Display *display; /* Display the event was read from */ 17019 Window window; 17020 Atom message_type; 17021 int format; 17022 union Data{ 17023 byte[20] b; 17024 short[10] s; 17025 arch_ulong[5] l; 17026 } 17027 Data data; 17028 17029 } 17030 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 17031 17032 struct XMappingEvent 17033 { 17034 int type; 17035 arch_ulong serial; /* # of last request processed by server */ 17036 Bool send_event; /* true if this came from a SendEvent request */ 17037 Display *display; /* Display the event was read from */ 17038 Window window; /* unused */ 17039 MappingType request; /* one of MappingModifier, MappingKeyboard, 17040 MappingPointer */ 17041 int first_keycode; /* first keycode */ 17042 int count; /* defines range of change w. first_keycode*/ 17043 } 17044 17045 struct XErrorEvent 17046 { 17047 int type; 17048 Display *display; /* Display the event was read from */ 17049 XID resourceid; /* resource id */ 17050 arch_ulong serial; /* serial number of failed request */ 17051 ubyte error_code; /* error code of failed request */ 17052 ubyte request_code; /* Major op-code of failed request */ 17053 ubyte minor_code; /* Minor op-code of failed request */ 17054 } 17055 17056 struct XAnyEvent 17057 { 17058 int type; 17059 arch_ulong serial; /* # of last request processed by server */ 17060 Bool send_event; /* true if this came from a SendEvent request */ 17061 Display *display;/* Display the event was read from */ 17062 Window window; /* window on which event was requested in event mask */ 17063 } 17064 17065 union XEvent{ 17066 int type; /* must not be changed; first element */ 17067 XAnyEvent xany; 17068 XKeyEvent xkey; 17069 XButtonEvent xbutton; 17070 XMotionEvent xmotion; 17071 XCrossingEvent xcrossing; 17072 XFocusChangeEvent xfocus; 17073 XExposeEvent xexpose; 17074 XGraphicsExposeEvent xgraphicsexpose; 17075 XNoExposeEvent xnoexpose; 17076 XVisibilityEvent xvisibility; 17077 XCreateWindowEvent xcreatewindow; 17078 XDestroyWindowEvent xdestroywindow; 17079 XUnmapEvent xunmap; 17080 XMapEvent xmap; 17081 XMapRequestEvent xmaprequest; 17082 XReparentEvent xreparent; 17083 XConfigureEvent xconfigure; 17084 XGravityEvent xgravity; 17085 XResizeRequestEvent xresizerequest; 17086 XConfigureRequestEvent xconfigurerequest; 17087 XCirculateEvent xcirculate; 17088 XCirculateRequestEvent xcirculaterequest; 17089 XPropertyEvent xproperty; 17090 XSelectionClearEvent xselectionclear; 17091 XSelectionRequestEvent xselectionrequest; 17092 XSelectionEvent xselection; 17093 XColormapEvent xcolormap; 17094 XClientMessageEvent xclient; 17095 XMappingEvent xmapping; 17096 XErrorEvent xerror; 17097 XKeymapEvent xkeymap; 17098 arch_ulong[24] pad; 17099 } 17100 17101 17102 struct Display { 17103 XExtData *ext_data; /* hook for extension to hang data */ 17104 _XPrivate *private1; 17105 int fd; /* Network socket. */ 17106 int private2; 17107 int proto_major_version;/* major version of server's X protocol */ 17108 int proto_minor_version;/* minor version of servers X protocol */ 17109 char *vendor; /* vendor of the server hardware */ 17110 XID private3; 17111 XID private4; 17112 XID private5; 17113 int private6; 17114 XID function(Display*)resource_alloc;/* allocator function */ 17115 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17116 int bitmap_unit; /* padding and data requirements */ 17117 int bitmap_pad; /* padding requirements on bitmaps */ 17118 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17119 int nformats; /* number of pixmap formats in list */ 17120 ScreenFormat *pixmap_format; /* pixmap format list */ 17121 int private8; 17122 int release; /* release of the server */ 17123 _XPrivate *private9; 17124 _XPrivate *private10; 17125 int qlen; /* Length of input event queue */ 17126 arch_ulong last_request_read; /* seq number of last event read */ 17127 arch_ulong request; /* sequence number of last request. */ 17128 XPointer private11; 17129 XPointer private12; 17130 XPointer private13; 17131 XPointer private14; 17132 uint max_request_size; /* maximum number 32 bit words in request*/ 17133 _XrmHashBucketRec *db; 17134 int function (Display*)private15; 17135 char *display_name; /* "host:display" string used on this connect*/ 17136 int default_screen; /* default screen for operations */ 17137 int nscreens; /* number of screens on this server*/ 17138 Screen *screens; /* pointer to list of screens */ 17139 arch_ulong motion_buffer; /* size of motion buffer */ 17140 arch_ulong private16; 17141 int min_keycode; /* minimum defined keycode */ 17142 int max_keycode; /* maximum defined keycode */ 17143 XPointer private17; 17144 XPointer private18; 17145 int private19; 17146 byte *xdefaults; /* contents of defaults from server */ 17147 /* there is more to this structure, but it is private to Xlib */ 17148 } 17149 17150 // I got these numbers from a C program as a sanity test 17151 version(X86_64) { 17152 static assert(Display.sizeof == 296); 17153 static assert(XPointer.sizeof == 8); 17154 static assert(XErrorEvent.sizeof == 40); 17155 static assert(XAnyEvent.sizeof == 40); 17156 static assert(XMappingEvent.sizeof == 56); 17157 static assert(XEvent.sizeof == 192); 17158 } else version (AArch64) { 17159 // omit check for aarch64 17160 } else { 17161 static assert(Display.sizeof == 176); 17162 static assert(XPointer.sizeof == 4); 17163 static assert(XEvent.sizeof == 96); 17164 } 17165 17166 struct Depth 17167 { 17168 int depth; /* this depth (Z) of the depth */ 17169 int nvisuals; /* number of Visual types at this depth */ 17170 Visual *visuals; /* list of visuals possible at this depth */ 17171 } 17172 17173 alias void* GC; 17174 alias c_ulong VisualID; 17175 alias XID Colormap; 17176 alias XID Cursor; 17177 alias XID KeySym; 17178 alias uint KeyCode; 17179 enum None = 0; 17180 } 17181 17182 version(without_opengl) {} 17183 else { 17184 extern(C) nothrow @nogc { 17185 17186 17187 static if(!SdpyIsUsingIVGLBinds) { 17188 enum GLX_USE_GL= 1; /* support GLX rendering */ 17189 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17190 enum GLX_LEVEL= 3; /* level in plane stacking */ 17191 enum GLX_RGBA= 4; /* true if RGBA mode */ 17192 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17193 enum GLX_STEREO= 6; /* stereo buffering supported */ 17194 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 17195 enum GLX_RED_SIZE= 8; /* number of red component bits */ 17196 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 17197 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 17198 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 17199 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 17200 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 17201 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 17202 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 17203 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 17204 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 17205 17206 17207 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 17208 17209 17210 17211 enum GL_TRUE = 1; 17212 enum GL_FALSE = 0; 17213 alias int GLint; 17214 } 17215 17216 alias XID GLXContextID; 17217 alias XID GLXPixmap; 17218 alias XID GLXDrawable; 17219 alias XID GLXPbuffer; 17220 alias XID GLXWindow; 17221 alias XID GLXFBConfigID; 17222 alias void* GLXContext; 17223 17224 } 17225 } 17226 17227 enum AllocNone = 0; 17228 17229 extern(C) { 17230 /* WARNING, this type not in Xlib spec */ 17231 extern(C) alias XIOErrorHandler = int function (Display* display); 17232 } 17233 17234 extern(C) nothrow 17235 alias XErrorHandler = int function(Display*, XErrorEvent*); 17236 17237 extern(C) nothrow @nogc { 17238 struct Screen{ 17239 XExtData *ext_data; /* hook for extension to hang data */ 17240 Display *display; /* back pointer to display structure */ 17241 Window root; /* Root window id. */ 17242 int width, height; /* width and height of screen */ 17243 int mwidth, mheight; /* width and height of in millimeters */ 17244 int ndepths; /* number of depths possible */ 17245 Depth *depths; /* list of allowable depths on the screen */ 17246 int root_depth; /* bits per pixel */ 17247 Visual *root_visual; /* root visual */ 17248 GC default_gc; /* GC for the root root visual */ 17249 Colormap cmap; /* default color map */ 17250 uint white_pixel; 17251 uint black_pixel; /* White and Black pixel values */ 17252 int max_maps, min_maps; /* max and min color maps */ 17253 int backing_store; /* Never, WhenMapped, Always */ 17254 bool save_unders; 17255 int root_input_mask; /* initial root input mask */ 17256 } 17257 17258 struct Visual 17259 { 17260 XExtData *ext_data; /* hook for extension to hang data */ 17261 VisualID visualid; /* visual id of this visual */ 17262 int class_; /* class of screen (monochrome, etc.) */ 17263 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 17264 int bits_per_rgb; /* log base 2 of distinct color values */ 17265 int map_entries; /* color map entries */ 17266 } 17267 17268 alias Display* _XPrivDisplay; 17269 17270 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) { 17271 assert(dpy !is null); 17272 return &dpy.screens[scr]; 17273 } 17274 17275 extern(D) Window RootWindow(Display *dpy,int scr) { 17276 return ScreenOfDisplay(dpy,scr).root; 17277 } 17278 17279 struct XWMHints { 17280 arch_long flags; 17281 Bool input; 17282 int initial_state; 17283 Pixmap icon_pixmap; 17284 Window icon_window; 17285 int icon_x, icon_y; 17286 Pixmap icon_mask; 17287 XID window_group; 17288 } 17289 17290 struct XClassHint { 17291 char* res_name; 17292 char* res_class; 17293 } 17294 17295 extern(D) int DefaultScreen(Display *dpy) { 17296 return dpy.default_screen; 17297 } 17298 17299 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 17300 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 17301 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 17302 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 17303 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 17304 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 17305 17306 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 17307 17308 enum int AnyPropertyType = 0; 17309 enum int Success = 0; 17310 17311 enum int RevertToNone = None; 17312 enum int PointerRoot = 1; 17313 enum Time CurrentTime = 0; 17314 enum int RevertToPointerRoot = PointerRoot; 17315 enum int RevertToParent = 2; 17316 17317 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 17318 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 17319 } 17320 17321 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 17322 return ScreenOfDisplay(dpy,scr).root_visual; 17323 } 17324 17325 extern(D) GC DefaultGC(Display *dpy,int scr) { 17326 return ScreenOfDisplay(dpy,scr).default_gc; 17327 } 17328 17329 extern(D) uint BlackPixel(Display *dpy,int scr) { 17330 return ScreenOfDisplay(dpy,scr).black_pixel; 17331 } 17332 17333 extern(D) uint WhitePixel(Display *dpy,int scr) { 17334 return ScreenOfDisplay(dpy,scr).white_pixel; 17335 } 17336 17337 alias void* XFontSet; // i think 17338 struct XmbTextItem { 17339 char* chars; 17340 int nchars; 17341 int delta; 17342 XFontSet font_set; 17343 } 17344 17345 struct XTextItem { 17346 char* chars; 17347 int nchars; 17348 int delta; 17349 Font font; 17350 } 17351 17352 enum { 17353 GXclear = 0x0, /* 0 */ 17354 GXand = 0x1, /* src AND dst */ 17355 GXandReverse = 0x2, /* src AND NOT dst */ 17356 GXcopy = 0x3, /* src */ 17357 GXandInverted = 0x4, /* NOT src AND dst */ 17358 GXnoop = 0x5, /* dst */ 17359 GXxor = 0x6, /* src XOR dst */ 17360 GXor = 0x7, /* src OR dst */ 17361 GXnor = 0x8, /* NOT src AND NOT dst */ 17362 GXequiv = 0x9, /* NOT src XOR dst */ 17363 GXinvert = 0xa, /* NOT dst */ 17364 GXorReverse = 0xb, /* src OR NOT dst */ 17365 GXcopyInverted = 0xc, /* NOT src */ 17366 GXorInverted = 0xd, /* NOT src OR dst */ 17367 GXnand = 0xe, /* NOT src OR NOT dst */ 17368 GXset = 0xf, /* 1 */ 17369 } 17370 enum QueueMode : int { 17371 QueuedAlready, 17372 QueuedAfterReading, 17373 QueuedAfterFlush 17374 } 17375 17376 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 17377 17378 struct XPoint { 17379 short x; 17380 short y; 17381 } 17382 17383 enum CoordMode:int { 17384 CoordModeOrigin = 0, 17385 CoordModePrevious = 1 17386 } 17387 17388 enum PolygonShape:int { 17389 Complex = 0, 17390 Nonconvex = 1, 17391 Convex = 2 17392 } 17393 17394 struct XTextProperty { 17395 const(char)* value; /* same as Property routines */ 17396 Atom encoding; /* prop type */ 17397 int format; /* prop data format: 8, 16, or 32 */ 17398 arch_ulong nitems; /* number of data items in value */ 17399 } 17400 17401 version( X86_64 ) { 17402 static assert(XTextProperty.sizeof == 32); 17403 } 17404 17405 17406 struct XGCValues { 17407 int function_; /* logical operation */ 17408 arch_ulong plane_mask;/* plane mask */ 17409 arch_ulong foreground;/* foreground pixel */ 17410 arch_ulong background;/* background pixel */ 17411 int line_width; /* line width */ 17412 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 17413 int cap_style; /* CapNotLast, CapButt, 17414 CapRound, CapProjecting */ 17415 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 17416 int fill_style; /* FillSolid, FillTiled, 17417 FillStippled, FillOpaeueStippled */ 17418 int fill_rule; /* EvenOddRule, WindingRule */ 17419 int arc_mode; /* ArcChord, ArcPieSlice */ 17420 Pixmap tile; /* tile pixmap for tiling operations */ 17421 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 17422 int ts_x_origin; /* offset for tile or stipple operations */ 17423 int ts_y_origin; 17424 Font font; /* default text font for text operations */ 17425 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 17426 Bool graphics_exposures;/* boolean, should exposures be generated */ 17427 int clip_x_origin; /* origin for clipping */ 17428 int clip_y_origin; 17429 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 17430 int dash_offset; /* patterned/dashed line information */ 17431 char dashes; 17432 } 17433 17434 struct XColor { 17435 arch_ulong pixel; 17436 ushort red, green, blue; 17437 byte flags; 17438 byte pad; 17439 } 17440 17441 struct XRectangle { 17442 short x; 17443 short y; 17444 ushort width; 17445 ushort height; 17446 } 17447 17448 enum ClipByChildren = 0; 17449 enum IncludeInferiors = 1; 17450 17451 enum Atom XA_PRIMARY = 1; 17452 enum Atom XA_SECONDARY = 2; 17453 enum Atom XA_STRING = 31; 17454 enum Atom XA_CARDINAL = 6; 17455 enum Atom XA_WM_NAME = 39; 17456 enum Atom XA_ATOM = 4; 17457 enum Atom XA_WINDOW = 33; 17458 enum Atom XA_WM_HINTS = 35; 17459 enum int PropModeAppend = 2; 17460 enum int PropModeReplace = 0; 17461 enum int PropModePrepend = 1; 17462 17463 enum int CopyFromParent = 0; 17464 enum int InputOutput = 1; 17465 17466 // XWMHints 17467 enum InputHint = 1 << 0; 17468 enum StateHint = 1 << 1; 17469 enum IconPixmapHint = (1L << 2); 17470 enum IconWindowHint = (1L << 3); 17471 enum IconPositionHint = (1L << 4); 17472 enum IconMaskHint = (1L << 5); 17473 enum WindowGroupHint = (1L << 6); 17474 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 17475 enum XUrgencyHint = (1L << 8); 17476 17477 // GC Components 17478 enum GCFunction = (1L<<0); 17479 enum GCPlaneMask = (1L<<1); 17480 enum GCForeground = (1L<<2); 17481 enum GCBackground = (1L<<3); 17482 enum GCLineWidth = (1L<<4); 17483 enum GCLineStyle = (1L<<5); 17484 enum GCCapStyle = (1L<<6); 17485 enum GCJoinStyle = (1L<<7); 17486 enum GCFillStyle = (1L<<8); 17487 enum GCFillRule = (1L<<9); 17488 enum GCTile = (1L<<10); 17489 enum GCStipple = (1L<<11); 17490 enum GCTileStipXOrigin = (1L<<12); 17491 enum GCTileStipYOrigin = (1L<<13); 17492 enum GCFont = (1L<<14); 17493 enum GCSubwindowMode = (1L<<15); 17494 enum GCGraphicsExposures= (1L<<16); 17495 enum GCClipXOrigin = (1L<<17); 17496 enum GCClipYOrigin = (1L<<18); 17497 enum GCClipMask = (1L<<19); 17498 enum GCDashOffset = (1L<<20); 17499 enum GCDashList = (1L<<21); 17500 enum GCArcMode = (1L<<22); 17501 enum GCLastBit = 22; 17502 17503 17504 enum int WithdrawnState = 0; 17505 enum int NormalState = 1; 17506 enum int IconicState = 3; 17507 17508 } 17509 } else version (OSXCocoa) { 17510 private: 17511 alias void* id; 17512 alias void* Class; 17513 alias void* SEL; 17514 alias void* IMP; 17515 alias void* Ivar; 17516 alias byte BOOL; 17517 alias const(void)* CFStringRef; 17518 alias const(void)* CFAllocatorRef; 17519 alias const(void)* CFTypeRef; 17520 alias const(void)* CGContextRef; 17521 alias const(void)* CGColorSpaceRef; 17522 alias const(void)* CGImageRef; 17523 alias ulong CGBitmapInfo; 17524 17525 struct objc_super { 17526 id self; 17527 Class superclass; 17528 } 17529 17530 struct CFRange { 17531 long location, length; 17532 } 17533 17534 struct NSPoint { 17535 double x, y; 17536 17537 static fromTuple(T)(T tupl) { 17538 return NSPoint(tupl.tupleof); 17539 } 17540 } 17541 struct NSSize { 17542 double width, height; 17543 } 17544 struct NSRect { 17545 NSPoint origin; 17546 NSSize size; 17547 } 17548 alias NSPoint CGPoint; 17549 alias NSSize CGSize; 17550 alias NSRect CGRect; 17551 17552 struct CGAffineTransform { 17553 double a, b, c, d, tx, ty; 17554 } 17555 17556 enum NSApplicationActivationPolicyRegular = 0; 17557 enum NSBackingStoreBuffered = 2; 17558 enum kCFStringEncodingUTF8 = 0x08000100; 17559 17560 enum : size_t { 17561 NSBorderlessWindowMask = 0, 17562 NSTitledWindowMask = 1 << 0, 17563 NSClosableWindowMask = 1 << 1, 17564 NSMiniaturizableWindowMask = 1 << 2, 17565 NSResizableWindowMask = 1 << 3, 17566 NSTexturedBackgroundWindowMask = 1 << 8 17567 } 17568 17569 enum : ulong { 17570 kCGImageAlphaNone, 17571 kCGImageAlphaPremultipliedLast, 17572 kCGImageAlphaPremultipliedFirst, 17573 kCGImageAlphaLast, 17574 kCGImageAlphaFirst, 17575 kCGImageAlphaNoneSkipLast, 17576 kCGImageAlphaNoneSkipFirst 17577 } 17578 enum : ulong { 17579 kCGBitmapAlphaInfoMask = 0x1F, 17580 kCGBitmapFloatComponents = (1 << 8), 17581 kCGBitmapByteOrderMask = 0x7000, 17582 kCGBitmapByteOrderDefault = (0 << 12), 17583 kCGBitmapByteOrder16Little = (1 << 12), 17584 kCGBitmapByteOrder32Little = (2 << 12), 17585 kCGBitmapByteOrder16Big = (3 << 12), 17586 kCGBitmapByteOrder32Big = (4 << 12) 17587 } 17588 enum CGPathDrawingMode { 17589 kCGPathFill, 17590 kCGPathEOFill, 17591 kCGPathStroke, 17592 kCGPathFillStroke, 17593 kCGPathEOFillStroke 17594 } 17595 enum objc_AssociationPolicy : size_t { 17596 OBJC_ASSOCIATION_ASSIGN = 0, 17597 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 17598 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 17599 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 17600 OBJC_ASSOCIATION_COPY = 0x303 //01403 17601 } 17602 17603 extern(C) { 17604 id objc_msgSend(id receiver, SEL selector, ...); 17605 id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...); 17606 id objc_getClass(const(char)* name); 17607 SEL sel_registerName(const(char)* str); 17608 Class objc_allocateClassPair(Class superclass, const(char)* name, 17609 size_t extra_bytes); 17610 void objc_registerClassPair(Class cls); 17611 BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types); 17612 id objc_getAssociatedObject(id object, void* key); 17613 void objc_setAssociatedObject(id object, void* key, id value, 17614 objc_AssociationPolicy policy); 17615 Ivar class_getInstanceVariable(Class cls, const(char)* name); 17616 id object_getIvar(id object, Ivar ivar); 17617 void object_setIvar(id object, Ivar ivar, id value); 17618 BOOL class_addIvar(Class cls, const(char)* name, 17619 size_t size, ubyte alignment, const(char)* types); 17620 17621 extern __gshared id NSApp; 17622 17623 void CFRelease(CFTypeRef obj); 17624 17625 CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator, 17626 const(char)* bytes, long numBytes, 17627 long encoding, 17628 BOOL isExternalRepresentation); 17629 long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding, 17630 char lossByte, bool isExternalRepresentation, 17631 char* buffer, long maxBufLen, long* usedBufLen); 17632 long CFStringGetLength(CFStringRef theString); 17633 17634 CGContextRef CGBitmapContextCreate(void* data, 17635 size_t width, size_t height, 17636 size_t bitsPerComponent, 17637 size_t bytesPerRow, 17638 CGColorSpaceRef colorspace, 17639 CGBitmapInfo bitmapInfo); 17640 void CGContextRelease(CGContextRef c); 17641 ubyte* CGBitmapContextGetData(CGContextRef c); 17642 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 17643 size_t CGBitmapContextGetWidth(CGContextRef c); 17644 size_t CGBitmapContextGetHeight(CGContextRef c); 17645 17646 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 17647 void CGColorSpaceRelease(CGColorSpaceRef cs); 17648 17649 void CGContextSetRGBStrokeColor(CGContextRef c, 17650 double red, double green, double blue, 17651 double alpha); 17652 void CGContextSetRGBFillColor(CGContextRef c, 17653 double red, double green, double blue, 17654 double alpha); 17655 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 17656 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, 17657 const(char)* str, size_t length); 17658 void CGContextStrokeLineSegments(CGContextRef c, 17659 const(CGPoint)* points, size_t count); 17660 17661 void CGContextBeginPath(CGContextRef c); 17662 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 17663 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 17664 void CGContextAddArc(CGContextRef c, double x, double y, double radius, 17665 double startAngle, double endAngle, long clockwise); 17666 void CGContextAddRect(CGContextRef c, CGRect rect); 17667 void CGContextAddLines(CGContextRef c, 17668 const(CGPoint)* points, size_t count); 17669 void CGContextSaveGState(CGContextRef c); 17670 void CGContextRestoreGState(CGContextRef c); 17671 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, 17672 ulong textEncoding); 17673 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 17674 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 17675 17676 void CGImageRelease(CGImageRef image); 17677 } 17678 17679 private: 17680 // A convenient method to create a CFString (=NSString) from a D string. 17681 CFStringRef createCFString(string str) { 17682 return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length, 17683 kCFStringEncodingUTF8, false); 17684 } 17685 17686 // Objective-C calls. 17687 RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) { 17688 auto _cmd = sel_registerName(selector.ptr); 17689 alias extern(C) RetType function(id, SEL, T) ExpectedType; 17690 return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args); 17691 } 17692 RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) { 17693 auto _cmd = sel_registerName(selector.ptr); 17694 auto cls = objc_getClass(className); 17695 alias extern(C) RetType function(id, SEL, T) ExpectedType; 17696 return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args); 17697 } 17698 RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) { 17699 return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args); 17700 } 17701 17702 alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay; 17703 alias objc_msgSend_classMethod!("alloc", id) alloc; 17704 alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:", 17705 id, NSRect, size_t, size_t, BOOL) initWithContentRect; 17706 alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle; 17707 alias objc_msgSend_specialized!("center", void) center; 17708 alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame; 17709 alias objc_msgSend_specialized!("setContentView:", void, id) setContentView; 17710 alias objc_msgSend_specialized!("release", void) release; 17711 alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor; 17712 alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor; 17713 alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront; 17714 alias objc_msgSend_specialized!("invalidate", void) invalidate; 17715 alias objc_msgSend_specialized!("close", void) close; 17716 alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:", 17717 id, double, id, SEL, id, BOOL) scheduledTimer; 17718 alias objc_msgSend_specialized!("run", void) run; 17719 alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext", 17720 id) currentNSGraphicsContext; 17721 alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort; 17722 alias objc_msgSend_specialized!("characters", CFStringRef) characters; 17723 alias objc_msgSend_specialized!("superclass", Class) superclass; 17724 alias objc_msgSend_specialized!("init", id) init; 17725 alias objc_msgSend_specialized!("addItem:", void, id) addItem; 17726 alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu; 17727 alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:", 17728 id, CFStringRef, SEL, CFStringRef) initWithTitle; 17729 alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu; 17730 alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate; 17731 alias objc_msgSend_specialized!("activateIgnoringOtherApps:", 17732 void, BOOL) activateIgnoringOtherApps; 17733 alias objc_msgSend_classMethod!("NSApplication", "sharedApplication", 17734 id) sharedNSApplication; 17735 alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy; 17736 } else static assert(0, "Unsupported operating system"); 17737 17738 17739 version(OSXCocoa) { 17740 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 17741 // 17742 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 17743 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 17744 // 17745 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 17746 // Probably won't even fully compile right now 17747 17748 import std.math : PI; 17749 import std.algorithm : map; 17750 import std.array : array; 17751 17752 alias SimpleWindow NativeWindowHandle; 17753 alias void delegate(id) NativeEventHandler; 17754 17755 __gshared Ivar simpleWindowIvar; 17756 17757 enum KEY_ESCAPE = 27; 17758 17759 mixin template NativeImageImplementation() { 17760 CGContextRef context; 17761 ubyte* rawData; 17762 final: 17763 17764 void convertToRgbaBytes(ubyte[] where) { 17765 assert(where.length == this.width * this.height * 4); 17766 17767 // if rawData had a length.... 17768 //assert(rawData.length == where.length); 17769 for(long idx = 0; idx < where.length; idx += 4) { 17770 auto alpha = rawData[idx + 3]; 17771 if(alpha == 255) { 17772 where[idx + 0] = rawData[idx + 0]; // r 17773 where[idx + 1] = rawData[idx + 1]; // g 17774 where[idx + 2] = rawData[idx + 2]; // b 17775 where[idx + 3] = rawData[idx + 3]; // a 17776 } else { 17777 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 17778 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 17779 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 17780 where[idx + 3] = rawData[idx + 3]; // a 17781 17782 } 17783 } 17784 } 17785 17786 void setFromRgbaBytes(in ubyte[] where) { 17787 // FIXME: this is probably wrong 17788 assert(where.length == this.width * this.height * 4); 17789 17790 // if rawData had a length.... 17791 //assert(rawData.length == where.length); 17792 for(long idx = 0; idx < where.length; idx += 4) { 17793 auto alpha = rawData[idx + 3]; 17794 if(alpha == 255) { 17795 rawData[idx + 0] = where[idx + 0]; // r 17796 rawData[idx + 1] = where[idx + 1]; // g 17797 rawData[idx + 2] = where[idx + 2]; // b 17798 rawData[idx + 3] = where[idx + 3]; // a 17799 } else { 17800 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 17801 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 17802 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 17803 rawData[idx + 3] = where[idx + 3]; // a 17804 17805 } 17806 } 17807 } 17808 17809 17810 void createImage(int width, int height, bool forcexshm=false) { 17811 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 17812 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 17813 colorSpace, 17814 kCGImageAlphaPremultipliedLast 17815 |kCGBitmapByteOrder32Big); 17816 CGColorSpaceRelease(colorSpace); 17817 rawData = CGBitmapContextGetData(context); 17818 } 17819 void dispose() { 17820 CGContextRelease(context); 17821 } 17822 17823 void setPixel(int x, int y, Color c) { 17824 auto offset = (y * width + x) * 4; 17825 if (c.a == 255) { 17826 rawData[offset + 0] = c.r; 17827 rawData[offset + 1] = c.g; 17828 rawData[offset + 2] = c.b; 17829 rawData[offset + 3] = c.a; 17830 } else { 17831 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 17832 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 17833 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 17834 rawData[offset + 3] = c.a; 17835 } 17836 } 17837 } 17838 17839 mixin template NativeScreenPainterImplementation() { 17840 CGContextRef context; 17841 ubyte[4] _outlineComponents; 17842 id view; 17843 17844 void create(NativeWindowHandle window) { 17845 context = window.drawingContext; 17846 view = window.view; 17847 } 17848 17849 void dispose() { 17850 setNeedsDisplay(view, true); 17851 } 17852 17853 bool manualInvalidations; 17854 void invalidateRect(Rectangle invalidRect) { } 17855 17856 // NotYetImplementedException 17857 Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); } 17858 void rasterOp(RasterOp op) {} 17859 Pen _activePen; 17860 Color _fillColor; 17861 Rectangle _clipRectangle; 17862 void setClipRectangle(int, int, int, int) {} 17863 void setFont(OperatingSystemFont) {} 17864 int fontHeight() { return 14; } 17865 17866 // end 17867 17868 void pen(Pen pen) { 17869 _activePen = pen; 17870 auto color = pen.color; // FIXME 17871 double alphaComponent = color.a/255.0f; 17872 CGContextSetRGBStrokeColor(context, 17873 color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 17874 17875 if (color.a != 255) { 17876 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 17877 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 17878 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 17879 _outlineComponents[3] = color.a; 17880 } else { 17881 _outlineComponents[0] = color.r; 17882 _outlineComponents[1] = color.g; 17883 _outlineComponents[2] = color.b; 17884 _outlineComponents[3] = color.a; 17885 } 17886 } 17887 17888 @property void fillColor(Color color) { 17889 CGContextSetRGBFillColor(context, 17890 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 17891 } 17892 17893 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 17894 // NotYetImplementedException for upper left/width/height 17895 auto cgImage = CGBitmapContextCreateImage(image.context); 17896 auto size = CGSize(CGBitmapContextGetWidth(image.context), 17897 CGBitmapContextGetHeight(image.context)); 17898 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 17899 CGImageRelease(cgImage); 17900 } 17901 17902 version(OSXCocoa) {} else // NotYetImplementedException 17903 void drawPixmap(Sprite image, int x, int y) { 17904 // FIXME: is this efficient? 17905 auto cgImage = CGBitmapContextCreateImage(image.context); 17906 auto size = CGSize(CGBitmapContextGetWidth(image.context), 17907 CGBitmapContextGetHeight(image.context)); 17908 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 17909 CGImageRelease(cgImage); 17910 } 17911 17912 17913 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 17914 // FIXME: alignment 17915 if (_outlineComponents[3] != 0) { 17916 CGContextSaveGState(context); 17917 auto invAlpha = 1.0f/_outlineComponents[3]; 17918 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 17919 _outlineComponents[1]*invAlpha, 17920 _outlineComponents[2]*invAlpha, 17921 _outlineComponents[3]/255.0f); 17922 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 17923 // auto cfstr = cast(id)createCFString(text); 17924 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 17925 // NSPoint(x, y), null); 17926 // CFRelease(cfstr); 17927 CGContextRestoreGState(context); 17928 } 17929 } 17930 17931 void drawPixel(int x, int y) { 17932 auto rawData = CGBitmapContextGetData(context); 17933 auto width = CGBitmapContextGetWidth(context); 17934 auto height = CGBitmapContextGetHeight(context); 17935 auto offset = ((height - y - 1) * width + x) * 4; 17936 rawData[offset .. offset+4] = _outlineComponents; 17937 } 17938 17939 void drawLine(int x1, int y1, int x2, int y2) { 17940 CGPoint[2] linePoints; 17941 linePoints[0] = CGPoint(x1, y1); 17942 linePoints[1] = CGPoint(x2, y2); 17943 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 17944 } 17945 17946 void drawRectangle(int x, int y, int width, int height) { 17947 CGContextBeginPath(context); 17948 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 17949 CGContextAddRect(context, rect); 17950 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17951 } 17952 17953 void drawEllipse(int x1, int y1, int x2, int y2) { 17954 CGContextBeginPath(context); 17955 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 17956 CGContextAddEllipseInRect(context, rect); 17957 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17958 } 17959 17960 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 17961 // @@@BUG@@@ Does not support elliptic arc (width != height). 17962 CGContextBeginPath(context); 17963 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 17964 start*PI/(180*64), finish*PI/(180*64), 0); 17965 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17966 } 17967 17968 void drawPolygon(Point[] intPoints) { 17969 CGContextBeginPath(context); 17970 auto points = array(map!(CGPoint.fromTuple)(intPoints)); 17971 CGContextAddLines(context, points.ptr, points.length); 17972 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17973 } 17974 } 17975 17976 mixin template NativeSimpleWindowImplementation() { 17977 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 17978 synchronized { 17979 if (NSApp == null) initializeApp(); 17980 } 17981 17982 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 17983 17984 // create the window. 17985 window = initWithContentRect(alloc("NSWindow"), 17986 contentRect, 17987 NSTitledWindowMask 17988 |NSClosableWindowMask 17989 |NSMiniaturizableWindowMask 17990 |NSResizableWindowMask, 17991 NSBackingStoreBuffered, 17992 true); 17993 17994 // set the title & move the window to center. 17995 auto windowTitle = createCFString(title); 17996 setTitle(window, windowTitle); 17997 CFRelease(windowTitle); 17998 center(window); 17999 18000 // create area to draw on. 18001 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 18002 drawingContext = CGBitmapContextCreate(null, width, height, 18003 8, 4*width, colorSpace, 18004 kCGImageAlphaPremultipliedLast 18005 |kCGBitmapByteOrder32Big); 18006 CGColorSpaceRelease(colorSpace); 18007 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 18008 auto matrix = CGContextGetTextMatrix(drawingContext); 18009 matrix.c = -matrix.c; 18010 matrix.d = -matrix.d; 18011 CGContextSetTextMatrix(drawingContext, matrix); 18012 18013 // create the subview that things will be drawn on. 18014 view = initWithFrame(alloc("SDGraphicsView"), contentRect); 18015 setContentView(window, view); 18016 object_setIvar(view, simpleWindowIvar, cast(id)this); 18017 release(view); 18018 18019 setBackgroundColor(window, whiteNSColor); 18020 makeKeyAndOrderFront(window, null); 18021 } 18022 void dispose() { 18023 closeWindow(); 18024 release(window); 18025 } 18026 void closeWindow() { 18027 invalidate(timer); 18028 .close(window); 18029 } 18030 18031 ScreenPainter getPainter(bool manualInvalidations) { 18032 return ScreenPainter(this, this, manualInvalidations); 18033 } 18034 18035 id window; 18036 id timer; 18037 id view; 18038 CGContextRef drawingContext; 18039 } 18040 18041 extern(C) { 18042 private: 18043 BOOL returnTrue3(id self, SEL _cmd, id app) { 18044 return true; 18045 } 18046 BOOL returnTrue2(id self, SEL _cmd) { 18047 return true; 18048 } 18049 18050 void pulse(id self, SEL _cmd) { 18051 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 18052 simpleWindow.handlePulse(); 18053 setNeedsDisplay(self, true); 18054 } 18055 void drawRect(id self, SEL _cmd, NSRect rect) { 18056 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 18057 auto curCtx = graphicsPort(currentNSGraphicsContext); 18058 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 18059 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), 18060 CGBitmapContextGetHeight(simpleWindow.drawingContext)); 18061 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 18062 CGImageRelease(cgImage); 18063 } 18064 void keyDown(id self, SEL _cmd, id event) { 18065 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 18066 18067 // the event may have multiple characters, and we send them all at 18068 // once. 18069 if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) { 18070 auto chars = characters(event); 18071 auto range = CFRange(0, CFStringGetLength(chars)); 18072 auto buffer = new char[range.length*3]; 18073 long actualLength; 18074 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false, 18075 buffer.ptr, cast(int) buffer.length, &actualLength); 18076 foreach (dchar dc; buffer[0..actualLength]) { 18077 if (simpleWindow.handleCharEvent) 18078 simpleWindow.handleCharEvent(dc); 18079 // NotYetImplementedException 18080 //if (simpleWindow.handleKeyEvent) 18081 //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp? 18082 } 18083 } 18084 18085 // the event's 'keyCode' is hardware-dependent. I don't think people 18086 // will like it. Let's leave it to the native handler. 18087 18088 // perform the default action. 18089 18090 // so the default action is to make a bomp sound and i dont want that 18091 // sooooooooo yeah not gonna do that. 18092 18093 //auto superData = objc_super(self, superclass(self)); 18094 //alias extern(C) void function(objc_super*, SEL, id) T; 18095 //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event); 18096 } 18097 } 18098 18099 // initialize the app so that it can be interacted with the user. 18100 // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html 18101 private void initializeApp() { 18102 // push an autorelease pool to avoid leaking. 18103 init(alloc("NSAutoreleasePool")); 18104 18105 // create a new NSApp instance 18106 sharedNSApplication; 18107 setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular); 18108 18109 // create the "Quit" menu. 18110 auto menuBar = init(alloc("NSMenu")); 18111 auto appMenuItem = init(alloc("NSMenuItem")); 18112 addItem(menuBar, appMenuItem); 18113 setMainMenu(NSApp, menuBar); 18114 release(appMenuItem); 18115 release(menuBar); 18116 18117 auto appMenu = init(alloc("NSMenu")); 18118 auto quitTitle = createCFString("Quit"); 18119 auto q = createCFString("q"); 18120 auto quitItem = initWithTitle(alloc("NSMenuItem"), 18121 quitTitle, sel_registerName("terminate:"), q); 18122 addItem(appMenu, quitItem); 18123 setSubmenu(appMenuItem, appMenu); 18124 release(quitItem); 18125 release(appMenu); 18126 CFRelease(q); 18127 CFRelease(quitTitle); 18128 18129 // assign a delegate for the application, allow it to quit when the last 18130 // window is closed. 18131 auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), 18132 "SDWindowCloseDelegate", 0); 18133 class_addMethod(delegateClass, 18134 sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"), 18135 &returnTrue3, "c@:@"); 18136 objc_registerClassPair(delegateClass); 18137 18138 auto appDelegate = init(alloc("SDWindowCloseDelegate")); 18139 setDelegate(NSApp, appDelegate); 18140 activateIgnoringOtherApps(NSApp, true); 18141 18142 // create a new view that draws the graphics and respond to keyDown 18143 // events. 18144 auto viewClass = objc_allocateClassPair(objc_getClass("NSView"), 18145 "SDGraphicsView", (void*).sizeof); 18146 class_addIvar(viewClass, "simpledisplay_simpleWindow", 18147 (void*).sizeof, (void*).alignof, "^v"); 18148 class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"), 18149 &pulse, "v@:"); 18150 class_addMethod(viewClass, sel_registerName("drawRect:"), 18151 &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); 18152 class_addMethod(viewClass, sel_registerName("isFlipped"), 18153 &returnTrue2, "c@:"); 18154 class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"), 18155 &returnTrue2, "c@:"); 18156 class_addMethod(viewClass, sel_registerName("keyDown:"), 18157 &keyDown, "v@:@"); 18158 objc_registerClassPair(viewClass); 18159 simpleWindowIvar = class_getInstanceVariable(viewClass, 18160 "simpledisplay_simpleWindow"); 18161 } 18162 } 18163 18164 version(without_opengl) {} else 18165 extern(System) nothrow @nogc { 18166 //enum uint GL_VERSION = 0x1F02; 18167 //const(char)* glGetString (/*GLenum*/uint); 18168 version(X11) { 18169 static if (!SdpyIsUsingIVGLBinds) { 18170 18171 enum GLX_X_RENDERABLE = 0x8012; 18172 enum GLX_DRAWABLE_TYPE = 0x8010; 18173 enum GLX_RENDER_TYPE = 0x8011; 18174 enum GLX_X_VISUAL_TYPE = 0x22; 18175 enum GLX_TRUE_COLOR = 0x8002; 18176 enum GLX_WINDOW_BIT = 0x00000001; 18177 enum GLX_RGBA_BIT = 0x00000001; 18178 enum GLX_COLOR_INDEX_BIT = 0x00000002; 18179 enum GLX_SAMPLE_BUFFERS = 0x186a0; 18180 enum GLX_SAMPLES = 0x186a1; 18181 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18182 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18183 } 18184 18185 // GLX_EXT_swap_control 18186 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 18187 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 18188 18189 //k8: ugly code to prevent warnings when sdpy is compiled into .a 18190 extern(System) { 18191 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 18192 } 18193 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 18194 18195 // this made public so we don't have to get it again and again 18196 public bool glXCreateContextAttribsARB_present () { 18197 if (glXCreateContextAttribsARBFn is cast(void*)1) { 18198 // get it 18199 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 18200 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 18201 } 18202 return (glXCreateContextAttribsARBFn !is null); 18203 } 18204 18205 // this made public so we don't have to get it again and again 18206 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 18207 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 18208 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 18209 } 18210 18211 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 18212 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 18213 18214 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 18215 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 18216 if (_glx_swapInterval_fn is null) { 18217 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 18218 if (_glx_swapInterval_fn is null) { 18219 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 18220 return; 18221 } 18222 version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); } 18223 } 18224 18225 if(glXSwapIntervalMESA is null) { 18226 // it seems to require both to actually take effect on many computers 18227 // idk why 18228 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 18229 if(glXSwapIntervalMESA is null) 18230 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 18231 } 18232 18233 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 18234 glXSwapIntervalMESA(wait ? 1 : 0); 18235 18236 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 18237 } 18238 } else version(Windows) { 18239 static if (!SdpyIsUsingIVGLBinds) { 18240 enum GL_TRUE = 1; 18241 enum GL_FALSE = 0; 18242 alias int GLint; 18243 18244 public void* glbindGetProcAddress (const(char)* name) { 18245 void* res = wglGetProcAddress(name); 18246 if (res is null) { 18247 /+ 18248 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 18249 import core.sys.windows.windef, core.sys.windows.winbase; 18250 __gshared HINSTANCE dll = null; 18251 if (dll is null) { 18252 dll = LoadLibraryA("opengl32.dll"); 18253 if (dll is null) return null; // <32, but idc 18254 } 18255 res = GetProcAddress(dll, name); 18256 +/ 18257 res = GetProcAddress(gl.libHandle, name); 18258 } 18259 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 18260 return res; 18261 } 18262 } 18263 18264 18265 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 18266 void wglSetVSync(bool wait) { 18267 if(wglSwapIntervalEXT is null) { 18268 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 18269 if(wglSwapIntervalEXT is null) 18270 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 18271 } 18272 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 18273 return; 18274 18275 wglSwapIntervalEXT(wait ? 1 : 0); 18276 } 18277 18278 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18279 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18280 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 18281 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 18282 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 18283 18284 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 18285 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 18286 18287 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 18288 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 18289 18290 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 18291 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 18292 18293 void wglInitOtherFunctions () { 18294 if (wglCreateContextAttribsARB is null) { 18295 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 18296 } 18297 } 18298 } 18299 18300 static if (!SdpyIsUsingIVGLBinds) { 18301 18302 interface GL { 18303 extern(System) @nogc nothrow: 18304 18305 void glGetIntegerv(int, void*); 18306 void glMatrixMode(int); 18307 void glPushMatrix(); 18308 void glLoadIdentity(); 18309 void glOrtho(double, double, double, double, double, double); 18310 void glFrustum(double, double, double, double, double, double); 18311 18312 void glPopMatrix(); 18313 void glEnable(int); 18314 void glDisable(int); 18315 void glClear(int); 18316 void glBegin(int); 18317 void glVertex2f(float, float); 18318 void glVertex3f(float, float, float); 18319 void glEnd(); 18320 void glColor3b(byte, byte, byte); 18321 void glColor3ub(ubyte, ubyte, ubyte); 18322 void glColor4b(byte, byte, byte, byte); 18323 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 18324 void glColor3i(int, int, int); 18325 void glColor3ui(uint, uint, uint); 18326 void glColor4i(int, int, int, int); 18327 void glColor4ui(uint, uint, uint, uint); 18328 void glColor3f(float, float, float); 18329 void glColor4f(float, float, float, float); 18330 void glTranslatef(float, float, float); 18331 void glScalef(float, float, float); 18332 version(X11) { 18333 void glSecondaryColor3b(byte, byte, byte); 18334 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 18335 void glSecondaryColor3i(int, int, int); 18336 void glSecondaryColor3ui(uint, uint, uint); 18337 void glSecondaryColor3f(float, float, float); 18338 } 18339 18340 void glDrawElements(int, int, int, void*); 18341 18342 void glRotatef(float, float, float, float); 18343 18344 uint glGetError(); 18345 18346 void glDeleteTextures(int, uint*); 18347 18348 18349 void glRasterPos2i(int, int); 18350 void glDrawPixels(int, int, uint, uint, void*); 18351 void glClearColor(float, float, float, float); 18352 18353 18354 void glPixelStorei(uint, int); 18355 18356 void glGenTextures(uint, uint*); 18357 void glBindTexture(int, int); 18358 void glTexParameteri(uint, uint, int); 18359 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18360 void glTexImage2D(int, int, int, int, int, int, int, int, in void*); 18361 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 18362 /*GLsizei*/int width, /*GLsizei*/int height, 18363 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 18364 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18365 18366 void glLineWidth(int); 18367 18368 18369 void glTexCoord2f(float, float); 18370 void glVertex2i(int, int); 18371 void glBlendFunc (int, int); 18372 void glDepthFunc (int); 18373 void glViewport(int, int, int, int); 18374 18375 void glClearDepth(double); 18376 18377 void glReadBuffer(uint); 18378 void glReadPixels(int, int, int, int, int, int, void*); 18379 18380 void glFlush(); 18381 void glFinish(); 18382 18383 version(Windows) { 18384 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 18385 HGLRC wglCreateContext(HDC); 18386 HGLRC wglCreateLayerContext(HDC, int); 18387 BOOL wglDeleteContext(HGLRC); 18388 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 18389 HGLRC wglGetCurrentContext(); 18390 HDC wglGetCurrentDC(); 18391 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 18392 PROC wglGetProcAddress(LPCSTR); 18393 BOOL wglMakeCurrent(HDC, HGLRC); 18394 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 18395 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 18396 BOOL wglShareLists(HGLRC, HGLRC); 18397 BOOL wglSwapLayerBuffers(HDC, UINT); 18398 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 18399 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 18400 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18401 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18402 } 18403 18404 } 18405 18406 interface GL3 { 18407 extern(System) @nogc nothrow: 18408 18409 void glGenVertexArrays(GLsizei, GLuint*); 18410 void glBindVertexArray(GLuint); 18411 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 18412 void glGenerateMipmap(GLenum); 18413 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 18414 void glStencilMask(GLuint); 18415 void glStencilFunc(GLenum, GLint, GLuint); 18416 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18417 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18418 GLuint glCreateProgram(); 18419 GLuint glCreateShader(GLenum); 18420 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 18421 void glCompileShader(GLuint); 18422 void glGetShaderiv(GLuint, GLenum, GLint*); 18423 void glAttachShader(GLuint, GLuint); 18424 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 18425 void glLinkProgram(GLuint); 18426 void glGetProgramiv(GLuint, GLenum, GLint*); 18427 void glDeleteProgram(GLuint); 18428 void glDeleteShader(GLuint); 18429 GLint glGetUniformLocation(GLuint, const(GLchar)*); 18430 void glGenBuffers(GLsizei, GLuint*); 18431 18432 void glUniform1f(GLint location, GLfloat v0); 18433 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 18434 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 18435 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 18436 void glUniform1i(GLint location, GLint v0); 18437 void glUniform2i(GLint location, GLint v0, GLint v1); 18438 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 18439 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 18440 void glUniform1ui(GLint location, GLuint v0); 18441 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 18442 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 18443 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 18444 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 18445 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 18446 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 18447 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 18448 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 18449 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 18450 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 18451 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 18452 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 18453 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 18454 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 18455 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 18456 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18457 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18458 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18459 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18460 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18461 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18462 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18463 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18464 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18465 18466 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 18467 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 18468 void glDrawArrays(GLenum, GLint, GLsizei); 18469 void glStencilOp(GLenum, GLenum, GLenum); 18470 void glUseProgram(GLuint); 18471 void glCullFace(GLenum); 18472 void glFrontFace(GLenum); 18473 void glActiveTexture(GLenum); 18474 void glBindBuffer(GLenum, GLuint); 18475 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 18476 void glEnableVertexAttribArray(GLuint); 18477 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 18478 void glUniform1i(GLint, GLint); 18479 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 18480 void glDisableVertexAttribArray(GLuint); 18481 void glDeleteBuffers(GLsizei, const(GLuint)*); 18482 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 18483 void glLogicOp (GLenum opcode); 18484 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 18485 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 18486 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 18487 GLenum glCheckFramebufferStatus (GLenum target); 18488 void glBindFramebuffer (GLenum target, GLuint framebuffer); 18489 } 18490 18491 interface GL4 { 18492 extern(System) @nogc nothrow: 18493 18494 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 18495 /*GLsizei*/int width, /*GLsizei*/int height, 18496 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 18497 } 18498 18499 interface GLU { 18500 extern(System) @nogc nothrow: 18501 18502 void gluLookAt(double, double, double, double, double, double, double, double, double); 18503 void gluPerspective(double, double, double, double); 18504 18505 char* gluErrorString(uint); 18506 } 18507 18508 18509 enum GL_RED = 0x1903; 18510 enum GL_ALPHA = 0x1906; 18511 18512 enum uint GL_FRONT = 0x0404; 18513 18514 enum uint GL_BLEND = 0x0be2; 18515 enum uint GL_LEQUAL = 0x0203; 18516 18517 18518 enum uint GL_RGB = 0x1907; 18519 enum uint GL_BGRA = 0x80e1; 18520 enum uint GL_RGBA = 0x1908; 18521 enum uint GL_TEXTURE_2D = 0x0DE1; 18522 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 18523 enum uint GL_NEAREST = 0x2600; 18524 enum uint GL_LINEAR = 0x2601; 18525 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 18526 enum uint GL_TEXTURE_WRAP_S = 0x2802; 18527 enum uint GL_TEXTURE_WRAP_T = 0x2803; 18528 enum uint GL_REPEAT = 0x2901; 18529 enum uint GL_CLAMP = 0x2900; 18530 enum uint GL_CLAMP_TO_EDGE = 0x812F; 18531 enum uint GL_CLAMP_TO_BORDER = 0x812D; 18532 enum uint GL_DECAL = 0x2101; 18533 enum uint GL_MODULATE = 0x2100; 18534 enum uint GL_TEXTURE_ENV = 0x2300; 18535 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 18536 enum uint GL_REPLACE = 0x1E01; 18537 enum uint GL_LIGHTING = 0x0B50; 18538 enum uint GL_DITHER = 0x0BD0; 18539 18540 enum uint GL_NO_ERROR = 0; 18541 18542 18543 18544 enum int GL_VIEWPORT = 0x0BA2; 18545 enum int GL_MODELVIEW = 0x1700; 18546 enum int GL_TEXTURE = 0x1702; 18547 enum int GL_PROJECTION = 0x1701; 18548 enum int GL_DEPTH_TEST = 0x0B71; 18549 18550 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 18551 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 18552 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 18553 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 18554 18555 enum int GL_POINTS = 0x0000; 18556 enum int GL_LINES = 0x0001; 18557 enum int GL_LINE_LOOP = 0x0002; 18558 enum int GL_LINE_STRIP = 0x0003; 18559 enum int GL_TRIANGLES = 0x0004; 18560 enum int GL_TRIANGLE_STRIP = 5; 18561 enum int GL_TRIANGLE_FAN = 6; 18562 enum int GL_QUADS = 7; 18563 enum int GL_QUAD_STRIP = 8; 18564 enum int GL_POLYGON = 9; 18565 18566 alias GLvoid = void; 18567 alias GLboolean = ubyte; 18568 alias GLuint = uint; 18569 alias GLenum = uint; 18570 alias GLchar = char; 18571 alias GLsizei = int; 18572 alias GLfloat = float; 18573 alias GLintptr = size_t; 18574 alias GLsizeiptr = ptrdiff_t; 18575 18576 18577 enum uint GL_INVALID_ENUM = 0x0500; 18578 18579 enum uint GL_ZERO = 0; 18580 enum uint GL_ONE = 1; 18581 18582 enum uint GL_BYTE = 0x1400; 18583 enum uint GL_UNSIGNED_BYTE = 0x1401; 18584 enum uint GL_SHORT = 0x1402; 18585 enum uint GL_UNSIGNED_SHORT = 0x1403; 18586 enum uint GL_INT = 0x1404; 18587 enum uint GL_UNSIGNED_INT = 0x1405; 18588 enum uint GL_FLOAT = 0x1406; 18589 enum uint GL_2_BYTES = 0x1407; 18590 enum uint GL_3_BYTES = 0x1408; 18591 enum uint GL_4_BYTES = 0x1409; 18592 enum uint GL_DOUBLE = 0x140A; 18593 18594 enum uint GL_STREAM_DRAW = 0x88E0; 18595 18596 enum uint GL_CCW = 0x0901; 18597 18598 enum uint GL_STENCIL_TEST = 0x0B90; 18599 enum uint GL_SCISSOR_TEST = 0x0C11; 18600 18601 enum uint GL_EQUAL = 0x0202; 18602 enum uint GL_NOTEQUAL = 0x0205; 18603 18604 enum uint GL_ALWAYS = 0x0207; 18605 enum uint GL_KEEP = 0x1E00; 18606 18607 enum uint GL_INCR = 0x1E02; 18608 18609 enum uint GL_INCR_WRAP = 0x8507; 18610 enum uint GL_DECR_WRAP = 0x8508; 18611 18612 enum uint GL_CULL_FACE = 0x0B44; 18613 enum uint GL_BACK = 0x0405; 18614 18615 enum uint GL_FRAGMENT_SHADER = 0x8B30; 18616 enum uint GL_VERTEX_SHADER = 0x8B31; 18617 18618 enum uint GL_COMPILE_STATUS = 0x8B81; 18619 enum uint GL_LINK_STATUS = 0x8B82; 18620 18621 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 18622 18623 enum uint GL_STATIC_DRAW = 0x88E4; 18624 18625 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 18626 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 18627 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 18628 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 18629 18630 enum uint GL_GENERATE_MIPMAP = 0x8191; 18631 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 18632 18633 enum uint GL_TEXTURE0 = 0x84C0U; 18634 enum uint GL_TEXTURE1 = 0x84C1U; 18635 18636 enum uint GL_ARRAY_BUFFER = 0x8892; 18637 18638 enum uint GL_SRC_COLOR = 0x0300; 18639 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 18640 enum uint GL_SRC_ALPHA = 0x0302; 18641 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 18642 enum uint GL_DST_ALPHA = 0x0304; 18643 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 18644 enum uint GL_DST_COLOR = 0x0306; 18645 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 18646 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 18647 18648 enum uint GL_INVERT = 0x150AU; 18649 18650 enum uint GL_DEPTH_STENCIL = 0x84F9U; 18651 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 18652 18653 enum uint GL_FRAMEBUFFER = 0x8D40U; 18654 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 18655 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 18656 18657 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 18658 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 18659 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 18660 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 18661 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 18662 18663 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 18664 enum uint GL_CLEAR = 0x1500U; 18665 enum uint GL_COPY = 0x1503U; 18666 enum uint GL_XOR = 0x1506U; 18667 18668 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 18669 18670 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 18671 18672 } 18673 } 18674 18675 /++ 18676 History: 18677 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. 18678 +/ 18679 __gshared bool gluSuccessfullyLoaded = true; 18680 18681 version(without_opengl) {} else { 18682 static if(!SdpyIsUsingIVGLBinds) { 18683 version(Windows) { 18684 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 18685 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 18686 } else { 18687 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 18688 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 18689 } 18690 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 18691 18692 18693 shared static this() { 18694 gl.loadDynamicLibrary(); 18695 18696 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 18697 // unless those functions are actually used 18698 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 18699 glu.loadDynamicLibrary(); 18700 } 18701 } 18702 } 18703 18704 /++ 18705 Convenience method for converting D arrays to opengl buffer data 18706 18707 I would LOVE to overload it with the original glBufferData, but D won't 18708 let me since glBufferData is a function pointer :( 18709 18710 Added: August 25, 2020 (version 8.5) 18711 +/ 18712 version(without_opengl) {} else 18713 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 18714 glBufferData(target, data.length, data.ptr, usage); 18715 } 18716 18717 /+ 18718 /++ 18719 A matrix for simple uses that easily integrates with [OpenGlShader]. 18720 18721 Might not be useful to you since it only as some simple functions and 18722 probably isn't that fast. 18723 18724 Note it uses an inline static array for its storage, so copying it 18725 may be expensive. 18726 +/ 18727 struct BasicMatrix(int columns, int rows, T = float) { 18728 import core.stdc.math; 18729 18730 T[columns * rows] data = 0.0; 18731 18732 /++ 18733 Basic operations that operate *in place*. 18734 +/ 18735 void translate() { 18736 18737 } 18738 18739 /// ditto 18740 void scale() { 18741 18742 } 18743 18744 /// ditto 18745 void rotate() { 18746 18747 } 18748 18749 /++ 18750 18751 +/ 18752 static if(columns == rows) 18753 static BasicMatrix identity() { 18754 BasicMatrix m; 18755 foreach(i; 0 .. columns) 18756 data[0 + i + i * columns] = 1.0; 18757 return m; 18758 } 18759 18760 static BasicMatrix ortho() { 18761 return BasicMatrix.init; 18762 } 18763 } 18764 +/ 18765 18766 /++ 18767 Convenience class for using opengl shaders. 18768 18769 Ensure that you've loaded opengl 3+ and set your active 18770 context before trying to use this. 18771 18772 Added: August 25, 2020 (version 8.5) 18773 +/ 18774 version(without_opengl) {} else 18775 final class OpenGlShader { 18776 private int shaderProgram_; 18777 private @property void shaderProgram(int a) { 18778 shaderProgram_ = a; 18779 } 18780 /// Get the program ID for use in OpenGL functions. 18781 public @property int shaderProgram() { 18782 return shaderProgram_; 18783 } 18784 18785 /++ 18786 18787 +/ 18788 static struct Source { 18789 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 18790 string code; /// 18791 } 18792 18793 /++ 18794 Helper method to just compile some shader code and check for errors 18795 while you do glCreateShader, etc. on the outside yourself. 18796 18797 This just does `glShaderSource` and `glCompileShader` for the given code. 18798 18799 If you the OpenGlShader class constructor, you never need to call this yourself. 18800 +/ 18801 static void compile(int sid, Source code) { 18802 const(char)*[1] buffer; 18803 int[1] lengthBuffer; 18804 18805 buffer[0] = code.code.ptr; 18806 lengthBuffer[0] = cast(int) code.code.length; 18807 18808 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 18809 glCompileShader(sid); 18810 18811 int success; 18812 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 18813 if(!success) { 18814 char[512] info; 18815 int len; 18816 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 18817 18818 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 18819 } 18820 } 18821 18822 /++ 18823 Calls `glLinkProgram` and throws if error a occurs. 18824 18825 If you the OpenGlShader class constructor, you never need to call this yourself. 18826 +/ 18827 static void link(int shaderProgram) { 18828 glLinkProgram(shaderProgram); 18829 int success; 18830 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 18831 if(!success) { 18832 char[512] info; 18833 int len; 18834 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 18835 18836 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 18837 } 18838 } 18839 18840 /++ 18841 Constructs the shader object by calling `glCreateProgram`, then 18842 compiling each given [Source], and finally, linking them together. 18843 18844 Throws: on compile or link failure. 18845 +/ 18846 this(Source[] codes...) { 18847 shaderProgram = glCreateProgram(); 18848 18849 int[16] shadersBufferStack; 18850 18851 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 18852 shadersBufferStack[0 .. codes.length] : 18853 new int[](codes.length); 18854 18855 foreach(idx, code; codes) { 18856 shadersBuffer[idx] = glCreateShader(code.type); 18857 18858 compile(shadersBuffer[idx], code); 18859 18860 glAttachShader(shaderProgram, shadersBuffer[idx]); 18861 } 18862 18863 link(shaderProgram); 18864 18865 foreach(s; shadersBuffer) 18866 glDeleteShader(s); 18867 } 18868 18869 /// Calls `glUseProgram(this.shaderProgram)` 18870 void use() { 18871 glUseProgram(this.shaderProgram); 18872 } 18873 18874 /// Deletes the program. 18875 void delete_() { 18876 glDeleteProgram(shaderProgram); 18877 shaderProgram = 0; 18878 } 18879 18880 /++ 18881 [OpenGlShader.uniforms].name gives you one of these. 18882 18883 You can get the id out of it or just assign 18884 +/ 18885 static struct Uniform { 18886 /// the id passed to glUniform* 18887 int id; 18888 18889 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 18890 void opAssign(float x, float y, float z, float w) { 18891 if(id != -1) 18892 glUniform4f(id, x, y, z, w); 18893 } 18894 18895 void opAssign(float x) { 18896 if(id != -1) 18897 glUniform1f(id, x); 18898 } 18899 18900 void opAssign(float x, float y) { 18901 if(id != -1) 18902 glUniform2f(id, x, y); 18903 } 18904 18905 void opAssign(T)(T t) { 18906 t.glUniform(id); 18907 } 18908 } 18909 18910 static struct UniformsHelper { 18911 OpenGlShader _shader; 18912 18913 @property Uniform opDispatch(string name)() { 18914 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 18915 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 18916 //if(i == -1) 18917 //throw new Exception("Could not find uniform " ~ name); 18918 return Uniform(i); 18919 } 18920 18921 @property void opDispatch(string name, T)(T t) { 18922 Uniform f = this.opDispatch!name; 18923 t.glUniform(f); 18924 } 18925 } 18926 18927 /++ 18928 Gives access to the uniforms through dot access. 18929 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 18930 +/ 18931 @property UniformsHelper uniforms() { return UniformsHelper(this); } 18932 } 18933 18934 version(without_opengl) {} else { 18935 /++ 18936 A static container of experimental types and value constructors for opengl 3+ shaders. 18937 18938 18939 You can declare variables like: 18940 18941 ``` 18942 OGL.vec3f something; 18943 ``` 18944 18945 But generally it would be used with [OpenGlShader]'s uniform helpers like 18946 18947 ``` 18948 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 18949 ``` 18950 18951 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 18952 18953 18954 History: 18955 Added December 7, 2021. Not yet stable. 18956 +/ 18957 final class OGL { 18958 static: 18959 18960 private template typeFromSpecifier(string specifier) { 18961 static if(specifier == "f") 18962 alias typeFromSpecifier = GLfloat; 18963 else static if(specifier == "i") 18964 alias typeFromSpecifier = GLint; 18965 else static if(specifier == "ui") 18966 alias typeFromSpecifier = GLuint; 18967 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 18968 } 18969 18970 private template CommonType(T...) { 18971 static if(T.length == 1) 18972 alias CommonType = T[0]; 18973 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 18974 alias CommonType = CommonType!(C, T[2 .. $]); 18975 } 18976 18977 private template typesToSpecifier(T...) { 18978 static if(is(CommonType!T == float)) 18979 enum typesToSpecifier = "f"; 18980 else static if(is(CommonType!T == int)) 18981 enum typesToSpecifier = "i"; 18982 else static if(is(CommonType!T == uint)) 18983 enum typesToSpecifier = "ui"; 18984 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 18985 } 18986 18987 private template genNames(size_t dim, size_t dim2 = 0) { 18988 string helper() { 18989 string s; 18990 if(dim2) { 18991 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;"; 18992 } else { 18993 if(dim > 0) s ~= "type x = 0;"; 18994 if(dim > 1) s ~= "type y = 0;"; 18995 if(dim > 2) s ~= "type z = 0;"; 18996 if(dim > 3) s ~= "type w = 0;"; 18997 } 18998 return s; 18999 } 19000 19001 enum genNames = helper(); 19002 } 19003 19004 // there's vec, arrays of vec, mat, and arrays of mat 19005 template opDispatch(string name) 19006 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 19007 { 19008 static if(name[4] == 'x') { 19009 enum dimX = cast(int) (name[3] - '0'); 19010 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 19011 19012 enum dimY = cast(int) (name[5] - '0'); 19013 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 19014 19015 enum isArray = name[$ - 1] == 'v'; 19016 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 19017 alias type = typeFromSpecifier!typeSpecifier; 19018 } else { 19019 enum dim = cast(int) (name[3] - '0'); 19020 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 19021 enum isArray = name[$ - 1] == 'v'; 19022 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 19023 alias type = typeFromSpecifier!typeSpecifier; 19024 } 19025 19026 align(1) 19027 struct opDispatch { 19028 align(1): 19029 static if(name[4] == 'x') 19030 mixin(genNames!(dimX, dimY)); 19031 else 19032 mixin(genNames!dim); 19033 19034 private void glUniform(OpenGlShader.Uniform assignTo) { 19035 glUniform(assignTo.id); 19036 } 19037 private void glUniform(int assignTo) { 19038 static if(name[4] == 'x') { 19039 // FIXME 19040 pragma(msg, "This matrix uniform helper has never been tested!!!!"); 19041 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr); 19042 } else 19043 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 19044 } 19045 } 19046 } 19047 19048 auto vec(T...)(T members) { 19049 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 19050 } 19051 } 19052 } 19053 19054 version(linux) { 19055 version(with_eventloop) {} else { 19056 private int epollFd = -1; 19057 void prepareEventLoop() { 19058 if(epollFd != -1) 19059 return; // already initialized, no need to do it again 19060 import ep = core.sys.linux.epoll; 19061 19062 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 19063 if(epollFd == -1) 19064 throw new Exception("epoll create failure"); 19065 } 19066 } 19067 } else version(Posix) { 19068 void prepareEventLoop() {} 19069 } 19070 19071 version(X11) { 19072 import core.stdc.locale : LC_ALL; // rdmd fix 19073 __gshared bool sdx_isUTF8Locale; 19074 19075 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 19076 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 19077 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 19078 // anal magic is here. I (Ketmar) hope you like it. 19079 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 19080 // always return correct unicode symbols. The detection is here 'cause user can change locale 19081 // later. 19082 19083 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 19084 shared static this () { 19085 if(!librariesSuccessfullyLoaded) 19086 return; 19087 19088 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 19089 19090 // this doesn't hurt; it may add some locking, but the speed is still 19091 // allows doing 60 FPS videogames; also, ignore the result, as most 19092 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 19093 // never seen this failing). 19094 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 19095 19096 setlocale(LC_ALL, ""); 19097 // check if out locale is UTF-8 19098 auto lct = setlocale(LC_CTYPE, null); 19099 if (lct is null) { 19100 sdx_isUTF8Locale = false; 19101 } else { 19102 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 19103 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 19104 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 19105 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 19106 { 19107 sdx_isUTF8Locale = true; 19108 break; 19109 } 19110 } 19111 } 19112 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 19113 } 19114 } 19115 19116 class ExperimentalTextComponent2 { 19117 /+ 19118 Stage 1: get it working monospace 19119 Stage 2: use proportional font 19120 Stage 3: allow changes in inline style 19121 Stage 4: allow new fonts and sizes in the middle 19122 Stage 5: optimize gap buffer 19123 Stage 6: optimize layout 19124 Stage 7: word wrap 19125 Stage 8: justification 19126 Stage 9: editing, selection, etc. 19127 19128 Operations: 19129 insert text 19130 overstrike text 19131 select 19132 cut 19133 modify 19134 +/ 19135 19136 /++ 19137 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. 19138 +/ 19139 this(SimpleWindow window) { 19140 this.window = window; 19141 } 19142 19143 private SimpleWindow window; 19144 19145 19146 /++ 19147 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 19148 representing the internal parts. The first pass is focused on the x parameter, then the 19149 renderer is responsible for going back to the parts in the current line and calling 19150 adjustDownForAscent to change the y params. 19151 +/ 19152 static interface ComponentRenderHelper { 19153 19154 /+ 19155 When you do an edit, possibly stuff on the same line previously need to move (to adjust 19156 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 19157 to move (adjust y to make room for new line) until you get back to the same position, 19158 then you can stop - if one thing is unchanged, nothing after it is changed too. 19159 19160 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 19161 once you reach something that is unchanged, you can stop. 19162 +/ 19163 19164 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 19165 19166 int ascent() const; 19167 int descent() const; 19168 19169 int advance() const; 19170 19171 bool endsWithExplititLineBreak() const; 19172 } 19173 19174 static interface RenderResult { 19175 /++ 19176 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. 19177 +/ 19178 void popFront(); 19179 @property bool empty() const; 19180 @property ComponentRenderHelper front() const; 19181 19182 void repositionForNextLine(Point baseline, int availableWidth); 19183 } 19184 19185 static interface ComponentInFlow { 19186 void draw(ScreenPainter painter); 19187 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 19188 19189 bool startsWithExplicitLineBreak() const; 19190 } 19191 19192 static class TextFlowComponent : ComponentInFlow { 19193 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 19194 19195 Color foreground; 19196 Color background; 19197 19198 OperatingSystemFont font; // should NEVER be null 19199 19200 ubyte attributes; // underline, strike through, display on new block 19201 19202 version(Windows) 19203 const(wchar)[] content; 19204 else 19205 const(char)[] content; // this should NEVER have a newline, except at the end 19206 19207 RenderedComponent[] rendered; // entirely controlled by [rerender] 19208 19209 // could prolly put some spacing around it too like margin / padding 19210 19211 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 19212 in { assert(font !is null); 19213 assert(!font.isNull); } 19214 do 19215 { 19216 this.foreground = f; 19217 this.background = b; 19218 this.font = font; 19219 19220 this.attributes = attr; 19221 version(Windows) { 19222 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 19223 auto sz = sizeOfConvertedWstring(c, conversionFlags); 19224 auto buffer = new wchar[](sz); 19225 this.content = makeWindowsString(c, buffer, conversionFlags); 19226 } else { 19227 this.content = c.dup; 19228 } 19229 } 19230 19231 void draw(ScreenPainter painter) { 19232 painter.setFont(this.font); 19233 painter.outlineColor = this.foreground; 19234 painter.fillColor = Color.transparent; 19235 foreach(rendered; this.rendered) { 19236 // the component works in term of baseline, 19237 // but the painter works in term of upper left bounding box 19238 // so need to translate that 19239 19240 if(this.background.a) { 19241 painter.fillColor = this.background; 19242 painter.outlineColor = this.background; 19243 19244 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 19245 19246 painter.outlineColor = this.foreground; 19247 painter.fillColor = Color.transparent; 19248 } 19249 19250 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 19251 19252 // FIXME: strike through, underline, highlight selection, etc. 19253 } 19254 } 19255 } 19256 19257 // I could split the parts into words on render 19258 // for easier word-wrap, each one being an unbreakable "inline-block" 19259 private TextFlowComponent[] parts; 19260 private int needsRerenderFrom; 19261 19262 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 19263 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 19264 parts ~= new TextFlowComponent(f, b, font, attr, c); 19265 } 19266 19267 static struct RenderedComponent { 19268 int startX; 19269 int startY; 19270 short width; 19271 // 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! 19272 // for individual chars in here you've gotta process on demand 19273 version(Windows) 19274 const(wchar)[] slice; 19275 else 19276 const(char)[] slice; 19277 } 19278 19279 19280 void rerender(Rectangle boundingBox) { 19281 Point baseline = boundingBox.upperLeft; 19282 19283 this.boundingBox.left = boundingBox.left; 19284 this.boundingBox.top = boundingBox.top; 19285 19286 auto remainingParts = parts; 19287 19288 int largestX; 19289 19290 19291 foreach(part; parts) 19292 part.font.prepareContext(window); 19293 scope(exit) 19294 foreach(part; parts) 19295 part.font.releaseContext(); 19296 19297 calculateNextLine: 19298 19299 int nextLineHeight = 0; 19300 int nextBiggestDescent = 0; 19301 19302 foreach(part; remainingParts) { 19303 auto height = part.font.ascent; 19304 if(height > nextLineHeight) 19305 nextLineHeight = height; 19306 if(part.font.descent > nextBiggestDescent) 19307 nextBiggestDescent = part.font.descent; 19308 if(part.content.length && part.content[$-1] == '\n') 19309 break; 19310 } 19311 19312 baseline.y += nextLineHeight; 19313 auto lineStart = baseline; 19314 19315 while(remainingParts.length) { 19316 remainingParts[0].rendered = null; 19317 19318 bool eol; 19319 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 19320 eol = true; 19321 19322 // FIXME: word wrap 19323 auto font = remainingParts[0].font; 19324 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 19325 auto width = font.stringWidth(slice, window); 19326 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 19327 19328 remainingParts = remainingParts[1 .. $]; 19329 baseline.x += width; 19330 19331 if(eol) { 19332 baseline.y += nextBiggestDescent; 19333 if(baseline.x > largestX) 19334 largestX = baseline.x; 19335 baseline.x = lineStart.x; 19336 goto calculateNextLine; 19337 } 19338 } 19339 19340 if(baseline.x > largestX) 19341 largestX = baseline.x; 19342 19343 this.boundingBox.right = largestX; 19344 this.boundingBox.bottom = baseline.y; 19345 } 19346 19347 // you must call rerender first! 19348 void draw(ScreenPainter painter) { 19349 foreach(part; parts) { 19350 part.draw(painter); 19351 } 19352 } 19353 19354 struct IdentifyResult { 19355 TextFlowComponent part; 19356 int charIndexInPart; 19357 int totalCharIndex = -1; // if this is -1, it just means the end 19358 19359 Rectangle boundingBox; 19360 } 19361 19362 IdentifyResult identify(Point pt, bool exact = false) { 19363 if(parts.length == 0) 19364 return IdentifyResult(null, 0); 19365 19366 if(pt.y < boundingBox.top) { 19367 if(exact) 19368 return IdentifyResult(null, 1); 19369 return IdentifyResult(parts[0], 0); 19370 } 19371 if(pt.y > boundingBox.bottom) { 19372 if(exact) 19373 return IdentifyResult(null, 2); 19374 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 19375 } 19376 19377 int tci = 0; 19378 19379 // I should probably like binary search this or something... 19380 foreach(ref part; parts) { 19381 foreach(rendered; part.rendered) { 19382 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 19383 if(rect.contains(pt)) { 19384 auto x = pt.x - rendered.startX; 19385 auto estimatedIdx = x / part.font.averageWidth; 19386 19387 if(estimatedIdx < 0) 19388 estimatedIdx = 0; 19389 19390 if(estimatedIdx > rendered.slice.length) 19391 estimatedIdx = cast(int) rendered.slice.length; 19392 19393 int idx; 19394 int x1, x2; 19395 if(part.font.isMonospace) { 19396 auto w = part.font.averageWidth; 19397 if(!exact && x > (estimatedIdx + 1) * w) 19398 return IdentifyResult(null, 4); 19399 idx = estimatedIdx; 19400 x1 = idx * w; 19401 x2 = (idx + 1) * w; 19402 } else { 19403 idx = estimatedIdx; 19404 19405 part.font.prepareContext(window); 19406 scope(exit) part.font.releaseContext(); 19407 19408 // int iterations; 19409 19410 while(true) { 19411 // iterations++; 19412 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 19413 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 19414 19415 x1 += rendered.startX; 19416 x2 += rendered.startX; 19417 19418 if(pt.x < x1) { 19419 if(idx == 0) { 19420 if(exact) 19421 return IdentifyResult(null, 6); 19422 else 19423 break; 19424 } 19425 idx--; 19426 } else if(pt.x > x2) { 19427 idx++; 19428 if(idx > rendered.slice.length) { 19429 if(exact) 19430 return IdentifyResult(null, 5); 19431 else 19432 break; 19433 } 19434 } else if(pt.x >= x1 && pt.x <= x2) { 19435 if(idx) 19436 idx--; // point it at the original index 19437 break; // we fit 19438 } 19439 } 19440 19441 // import std.stdio; writeln(iterations) 19442 } 19443 19444 19445 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 19446 } 19447 } 19448 tci += cast(int) part.content.length; // FIXME: utf-8? 19449 } 19450 return IdentifyResult(null, 3); 19451 } 19452 19453 Rectangle boundingBox; // only set after [rerender] 19454 19455 // text will be positioned around the exclusion zone 19456 static struct ExclusionZone { 19457 19458 } 19459 19460 ExclusionZone[] exclusionZones; 19461 } 19462 19463 19464 // Don't use this yet. When I'm happy with it, I will move it to the 19465 // regular module namespace. 19466 mixin template ExperimentalTextComponent() { 19467 19468 static: 19469 19470 alias Rectangle = arsd.color.Rectangle; 19471 19472 struct ForegroundColor { 19473 Color color; 19474 alias color this; 19475 19476 this(Color c) { 19477 color = c; 19478 } 19479 19480 this(int r, int g, int b, int a = 255) { 19481 color = Color(r, g, b, a); 19482 } 19483 19484 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 19485 return ForegroundColor(mixin("Color." ~ s)); 19486 } 19487 } 19488 19489 struct BackgroundColor { 19490 Color color; 19491 alias color this; 19492 19493 this(Color c) { 19494 color = c; 19495 } 19496 19497 this(int r, int g, int b, int a = 255) { 19498 color = Color(r, g, b, a); 19499 } 19500 19501 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 19502 return BackgroundColor(mixin("Color." ~ s)); 19503 } 19504 } 19505 19506 static class InlineElement { 19507 string text; 19508 19509 BlockElement containingBlock; 19510 19511 Color color = Color.black; 19512 Color backgroundColor = Color.transparent; 19513 ushort styles; 19514 19515 string font; 19516 int fontSize; 19517 19518 int lineHeight; 19519 19520 void* identifier; 19521 19522 Rectangle boundingBox; 19523 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 19524 19525 bool isMergeCompatible(InlineElement other) { 19526 return 19527 containingBlock is other.containingBlock && 19528 color == other.color && 19529 backgroundColor == other.backgroundColor && 19530 styles == other.styles && 19531 font == other.font && 19532 fontSize == other.fontSize && 19533 lineHeight == other.lineHeight && 19534 true; 19535 } 19536 19537 int xOfIndex(size_t index) { 19538 if(index < letterXs.length) 19539 return letterXs[index]; 19540 else 19541 return boundingBox.right; 19542 } 19543 19544 InlineElement clone() { 19545 auto ie = new InlineElement(); 19546 ie.tupleof = this.tupleof; 19547 return ie; 19548 } 19549 19550 InlineElement getPreviousInlineElement() { 19551 InlineElement prev = null; 19552 foreach(ie; this.containingBlock.parts) { 19553 if(ie is this) 19554 break; 19555 prev = ie; 19556 } 19557 if(prev is null) { 19558 BlockElement pb; 19559 BlockElement cb = this.containingBlock; 19560 moar: 19561 foreach(ie; this.containingBlock.containingLayout.blocks) { 19562 if(ie is cb) 19563 break; 19564 pb = ie; 19565 } 19566 if(pb is null) 19567 return null; 19568 if(pb.parts.length == 0) { 19569 cb = pb; 19570 goto moar; 19571 } 19572 19573 prev = pb.parts[$-1]; 19574 19575 } 19576 return prev; 19577 } 19578 19579 InlineElement getNextInlineElement() { 19580 InlineElement next = null; 19581 foreach(idx, ie; this.containingBlock.parts) { 19582 if(ie is this) { 19583 if(idx + 1 < this.containingBlock.parts.length) 19584 next = this.containingBlock.parts[idx + 1]; 19585 break; 19586 } 19587 } 19588 if(next is null) { 19589 BlockElement n; 19590 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 19591 if(ie is this.containingBlock) { 19592 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 19593 n = this.containingBlock.containingLayout.blocks[idx + 1]; 19594 break; 19595 } 19596 } 19597 if(n is null) 19598 return null; 19599 19600 if(n.parts.length) 19601 next = n.parts[0]; 19602 else {} // FIXME 19603 19604 } 19605 return next; 19606 } 19607 19608 } 19609 19610 // Block elements are used entirely for positioning inline elements, 19611 // which are the things that are actually drawn. 19612 class BlockElement { 19613 InlineElement[] parts; 19614 uint alignment; 19615 19616 int whiteSpace; // pre, pre-wrap, wrap 19617 19618 TextLayout containingLayout; 19619 19620 // inputs 19621 Point where; 19622 Size minimumSize; 19623 Size maximumSize; 19624 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 19625 void* identifier; 19626 19627 Rectangle margin; 19628 Rectangle padding; 19629 19630 // outputs 19631 Rectangle[] boundingBoxes; 19632 } 19633 19634 struct TextIdentifyResult { 19635 InlineElement element; 19636 int offset; 19637 19638 private TextIdentifyResult fixupNewline() { 19639 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 19640 offset--; 19641 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 19642 offset--; 19643 } 19644 return this; 19645 } 19646 } 19647 19648 class TextLayout { 19649 BlockElement[] blocks; 19650 Rectangle boundingBox_; 19651 Rectangle boundingBox() { return boundingBox_; } 19652 void boundingBox(Rectangle r) { 19653 if(r != boundingBox_) { 19654 boundingBox_ = r; 19655 layoutInvalidated = true; 19656 } 19657 } 19658 19659 Rectangle contentBoundingBox() { 19660 Rectangle r; 19661 foreach(block; blocks) 19662 foreach(ie; block.parts) { 19663 if(ie.boundingBox.right > r.right) 19664 r.right = ie.boundingBox.right; 19665 if(ie.boundingBox.bottom > r.bottom) 19666 r.bottom = ie.boundingBox.bottom; 19667 } 19668 return r; 19669 } 19670 19671 BlockElement[] getBlocks() { 19672 return blocks; 19673 } 19674 19675 InlineElement[] getTexts() { 19676 InlineElement[] elements; 19677 foreach(block; blocks) 19678 elements ~= block.parts; 19679 return elements; 19680 } 19681 19682 string getPlainText() { 19683 string text; 19684 foreach(block; blocks) 19685 foreach(part; block.parts) 19686 text ~= part.text; 19687 return text; 19688 } 19689 19690 string getHtml() { 19691 return null; // FIXME 19692 } 19693 19694 this(Rectangle boundingBox) { 19695 this.boundingBox = boundingBox; 19696 } 19697 19698 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 19699 auto be = new BlockElement(); 19700 be.containingLayout = this; 19701 if(after is null) 19702 blocks ~= be; 19703 else { 19704 foreach(idx, b; blocks) { 19705 if(b is after.containingBlock) { 19706 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 19707 break; 19708 } 19709 } 19710 } 19711 return be; 19712 } 19713 19714 void clear() { 19715 blocks = null; 19716 selectionStart = selectionEnd = caret = Caret.init; 19717 } 19718 19719 void addText(Args...)(Args args) { 19720 if(blocks.length == 0) 19721 addBlock(); 19722 19723 InlineElement ie = new InlineElement(); 19724 foreach(idx, arg; args) { 19725 static if(is(typeof(arg) == ForegroundColor)) 19726 ie.color = arg; 19727 else static if(is(typeof(arg) == TextFormat)) { 19728 if(arg & 0x8000) // ~TextFormat.something turns it off 19729 ie.styles &= arg; 19730 else 19731 ie.styles |= arg; 19732 } else static if(is(typeof(arg) == string)) { 19733 static if(idx == 0 && args.length > 1) 19734 static assert(0, "Put styles before the string."); 19735 size_t lastLineIndex; 19736 foreach(cidx, char a; arg) { 19737 if(a == '\n') { 19738 ie.text = arg[lastLineIndex .. cidx + 1]; 19739 lastLineIndex = cidx + 1; 19740 ie.containingBlock = blocks[$-1]; 19741 blocks[$-1].parts ~= ie.clone; 19742 ie.text = null; 19743 } else { 19744 19745 } 19746 } 19747 19748 ie.text = arg[lastLineIndex .. $]; 19749 ie.containingBlock = blocks[$-1]; 19750 blocks[$-1].parts ~= ie.clone; 19751 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 19752 } 19753 } 19754 19755 invalidateLayout(); 19756 } 19757 19758 void tryMerge(InlineElement into, InlineElement what) { 19759 if(!into.isMergeCompatible(what)) { 19760 return; // cannot merge, different configs 19761 } 19762 19763 // cool, can merge, bring text together... 19764 into.text ~= what.text; 19765 19766 // and remove what 19767 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 19768 if(what.containingBlock.parts[a] is what) { 19769 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 19770 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 19771 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 19772 19773 } 19774 } 19775 19776 // FIXME: ensure no other carets have a reference to it 19777 } 19778 19779 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 19780 TextIdentifyResult identify(int x, int y, bool exact = false) { 19781 TextIdentifyResult inexactMatch; 19782 foreach(block; blocks) { 19783 foreach(part; block.parts) { 19784 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 19785 19786 // FIXME binary search 19787 int tidx; 19788 int lastX; 19789 foreach_reverse(idxo, lx; part.letterXs) { 19790 int idx = cast(int) idxo; 19791 if(lx <= x) { 19792 if(lastX && lastX - x < x - lx) 19793 tidx = idx + 1; 19794 else 19795 tidx = idx; 19796 break; 19797 } 19798 lastX = lx; 19799 } 19800 19801 return TextIdentifyResult(part, tidx).fixupNewline; 19802 } else if(!exact) { 19803 // we're not in the box, but are we on the same line? 19804 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 19805 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 19806 } 19807 } 19808 } 19809 19810 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 19811 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 19812 19813 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 19814 } 19815 19816 void moveCaretToPixelCoordinates(int x, int y) { 19817 auto result = identify(x, y); 19818 caret.inlineElement = result.element; 19819 caret.offset = result.offset; 19820 } 19821 19822 void selectToPixelCoordinates(int x, int y) { 19823 auto result = identify(x, y); 19824 19825 if(y < caretLastDrawnY1) { 19826 // on a previous line, carat is selectionEnd 19827 selectionEnd = caret; 19828 19829 selectionStart = Caret(this, result.element, result.offset); 19830 } else if(y > caretLastDrawnY2) { 19831 // on a later line 19832 selectionStart = caret; 19833 19834 selectionEnd = Caret(this, result.element, result.offset); 19835 } else { 19836 // on the same line... 19837 if(x <= caretLastDrawnX) { 19838 selectionEnd = caret; 19839 selectionStart = Caret(this, result.element, result.offset); 19840 } else { 19841 selectionStart = caret; 19842 selectionEnd = Caret(this, result.element, result.offset); 19843 } 19844 19845 } 19846 } 19847 19848 19849 /// Call this if the inputs change. It will reflow everything 19850 void redoLayout(ScreenPainter painter) { 19851 //painter.setClipRectangle(boundingBox); 19852 auto pos = Point(boundingBox.left, boundingBox.top); 19853 19854 int lastHeight; 19855 void nl() { 19856 pos.x = boundingBox.left; 19857 pos.y += lastHeight; 19858 } 19859 foreach(block; blocks) { 19860 nl(); 19861 foreach(part; block.parts) { 19862 part.letterXs = null; 19863 19864 auto size = painter.textSize(part.text); 19865 version(Windows) 19866 if(part.text.length && part.text[$-1] == '\n') 19867 size.height /= 2; // windows counts the new line at the end, but we don't want that 19868 19869 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 19870 19871 foreach(idx, char c; part.text) { 19872 // FIXME: unicode 19873 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 19874 } 19875 19876 pos.x += size.width; 19877 if(pos.x >= boundingBox.right) { 19878 pos.y += size.height; 19879 pos.x = boundingBox.left; 19880 lastHeight = 0; 19881 } else { 19882 lastHeight = size.height; 19883 } 19884 19885 if(part.text.length && part.text[$-1] == '\n') 19886 nl(); 19887 } 19888 } 19889 19890 layoutInvalidated = false; 19891 } 19892 19893 bool layoutInvalidated = true; 19894 void invalidateLayout() { 19895 layoutInvalidated = true; 19896 } 19897 19898 // FIXME: caret can remain sometimes when inserting 19899 // FIXME: inserting at the beginning once you already have something can eff it up. 19900 void drawInto(ScreenPainter painter, bool focused = false) { 19901 if(layoutInvalidated) 19902 redoLayout(painter); 19903 foreach(block; blocks) { 19904 foreach(part; block.parts) { 19905 painter.outlineColor = part.color; 19906 painter.fillColor = part.backgroundColor; 19907 19908 auto pos = part.boundingBox.upperLeft; 19909 auto size = part.boundingBox.size; 19910 19911 painter.drawText(pos, part.text); 19912 if(part.styles & TextFormat.underline) 19913 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 19914 if(part.styles & TextFormat.strikethrough) 19915 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 19916 } 19917 } 19918 19919 // on every redraw, I will force the caret to be 19920 // redrawn too, in order to eliminate perceived lag 19921 // when moving around with the mouse. 19922 eraseCaret(painter); 19923 19924 if(focused) { 19925 highlightSelection(painter); 19926 drawCaret(painter); 19927 } 19928 } 19929 19930 Color selectionXorColor = Color(255, 255, 127); 19931 19932 void highlightSelection(ScreenPainter painter) { 19933 if(selectionStart is selectionEnd) 19934 return; // no selection 19935 19936 if(selectionStart.inlineElement is null) return; 19937 if(selectionEnd.inlineElement is null) return; 19938 19939 assert(selectionStart.inlineElement !is null); 19940 assert(selectionEnd.inlineElement !is null); 19941 19942 painter.rasterOp = RasterOp.xor; 19943 painter.outlineColor = Color.transparent; 19944 painter.fillColor = selectionXorColor; 19945 19946 auto at = selectionStart.inlineElement; 19947 auto atOffset = selectionStart.offset; 19948 bool done; 19949 while(at) { 19950 auto box = at.boundingBox; 19951 if(atOffset < at.letterXs.length) 19952 box.left = at.letterXs[atOffset]; 19953 19954 if(at is selectionEnd.inlineElement) { 19955 if(selectionEnd.offset < at.letterXs.length) 19956 box.right = at.letterXs[selectionEnd.offset]; 19957 done = true; 19958 } 19959 19960 painter.drawRectangle(box.upperLeft, box.width, box.height); 19961 19962 if(done) 19963 break; 19964 19965 at = at.getNextInlineElement(); 19966 atOffset = 0; 19967 } 19968 } 19969 19970 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 19971 bool caretShowingOnScreen = false; 19972 void drawCaret(ScreenPainter painter) { 19973 //painter.setClipRectangle(boundingBox); 19974 int x, y1, y2; 19975 if(caret.inlineElement is null) { 19976 x = boundingBox.left; 19977 y1 = boundingBox.top + 2; 19978 y2 = boundingBox.top + painter.fontHeight; 19979 } else { 19980 x = caret.inlineElement.xOfIndex(caret.offset); 19981 y1 = caret.inlineElement.boundingBox.top + 2; 19982 y2 = caret.inlineElement.boundingBox.bottom - 2; 19983 } 19984 19985 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 19986 eraseCaret(painter); 19987 19988 painter.pen = Pen(Color.white, 1); 19989 painter.rasterOp = RasterOp.xor; 19990 painter.drawLine( 19991 Point(x, y1), 19992 Point(x, y2) 19993 ); 19994 painter.rasterOp = RasterOp.normal; 19995 caretShowingOnScreen = !caretShowingOnScreen; 19996 19997 if(caretShowingOnScreen) { 19998 caretLastDrawnX = x; 19999 caretLastDrawnY1 = y1; 20000 caretLastDrawnY2 = y2; 20001 } 20002 } 20003 20004 Rectangle caretBoundingBox() { 20005 int x, y1, y2; 20006 if(caret.inlineElement is null) { 20007 x = boundingBox.left; 20008 y1 = boundingBox.top + 2; 20009 y2 = boundingBox.top + 16; 20010 } else { 20011 x = caret.inlineElement.xOfIndex(caret.offset); 20012 y1 = caret.inlineElement.boundingBox.top + 2; 20013 y2 = caret.inlineElement.boundingBox.bottom - 2; 20014 } 20015 20016 return Rectangle(x, y1, x + 1, y2); 20017 } 20018 20019 void eraseCaret(ScreenPainter painter) { 20020 //painter.setClipRectangle(boundingBox); 20021 if(!caretShowingOnScreen) return; 20022 painter.pen = Pen(Color.white, 1); 20023 painter.rasterOp = RasterOp.xor; 20024 painter.drawLine( 20025 Point(caretLastDrawnX, caretLastDrawnY1), 20026 Point(caretLastDrawnX, caretLastDrawnY2) 20027 ); 20028 20029 caretShowingOnScreen = false; 20030 painter.rasterOp = RasterOp.normal; 20031 } 20032 20033 /// Caret movement api 20034 /// These should give the user a logical result based on what they see on screen... 20035 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 20036 void moveUp() { 20037 if(caret.inlineElement is null) return; 20038 auto x = caret.inlineElement.xOfIndex(caret.offset); 20039 auto y = caret.inlineElement.boundingBox.top + 2; 20040 20041 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20042 if(y < 0) 20043 return; 20044 20045 auto i = identify(x, y); 20046 20047 if(i.element) { 20048 caret.inlineElement = i.element; 20049 caret.offset = i.offset; 20050 } 20051 } 20052 void moveDown() { 20053 if(caret.inlineElement is null) return; 20054 auto x = caret.inlineElement.xOfIndex(caret.offset); 20055 auto y = caret.inlineElement.boundingBox.bottom - 2; 20056 20057 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 20058 20059 auto i = identify(x, y); 20060 if(i.element) { 20061 caret.inlineElement = i.element; 20062 caret.offset = i.offset; 20063 } 20064 } 20065 void moveLeft() { 20066 if(caret.inlineElement is null) return; 20067 if(caret.offset) 20068 caret.offset--; 20069 else { 20070 auto p = caret.inlineElement.getPreviousInlineElement(); 20071 if(p) { 20072 caret.inlineElement = p; 20073 if(p.text.length && p.text[$-1] == '\n') 20074 caret.offset = cast(int) p.text.length - 1; 20075 else 20076 caret.offset = cast(int) p.text.length; 20077 } 20078 } 20079 } 20080 void moveRight() { 20081 if(caret.inlineElement is null) return; 20082 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 20083 caret.offset++; 20084 } else { 20085 auto p = caret.inlineElement.getNextInlineElement(); 20086 if(p) { 20087 caret.inlineElement = p; 20088 caret.offset = 0; 20089 } 20090 } 20091 } 20092 void moveHome() { 20093 if(caret.inlineElement is null) return; 20094 auto x = 0; 20095 auto y = caret.inlineElement.boundingBox.top + 2; 20096 20097 auto i = identify(x, y); 20098 20099 if(i.element) { 20100 caret.inlineElement = i.element; 20101 caret.offset = i.offset; 20102 } 20103 } 20104 void moveEnd() { 20105 if(caret.inlineElement is null) return; 20106 auto x = int.max; 20107 auto y = caret.inlineElement.boundingBox.top + 2; 20108 20109 auto i = identify(x, y); 20110 20111 if(i.element) { 20112 caret.inlineElement = i.element; 20113 caret.offset = i.offset; 20114 } 20115 20116 } 20117 void movePageUp(ref Caret caret) {} 20118 void movePageDown(ref Caret caret) {} 20119 20120 void moveDocumentStart(ref Caret caret) { 20121 if(blocks.length && blocks[0].parts.length) 20122 caret = Caret(this, blocks[0].parts[0], 0); 20123 else 20124 caret = Caret.init; 20125 } 20126 20127 void moveDocumentEnd(ref Caret caret) { 20128 if(blocks.length) { 20129 auto parts = blocks[$-1].parts; 20130 if(parts.length) { 20131 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 20132 } else { 20133 caret = Caret.init; 20134 } 20135 } else 20136 caret = Caret.init; 20137 } 20138 20139 void deleteSelection() { 20140 if(selectionStart is selectionEnd) 20141 return; 20142 20143 if(selectionStart.inlineElement is null) return; 20144 if(selectionEnd.inlineElement is null) return; 20145 20146 assert(selectionStart.inlineElement !is null); 20147 assert(selectionEnd.inlineElement !is null); 20148 20149 auto at = selectionStart.inlineElement; 20150 20151 if(selectionEnd.inlineElement is at) { 20152 // same element, need to chop out 20153 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 20154 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 20155 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 20156 } else { 20157 // different elements, we can do it with slicing 20158 at.text = at.text[0 .. selectionStart.offset]; 20159 if(selectionStart.offset < at.letterXs.length) 20160 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 20161 20162 at = at.getNextInlineElement(); 20163 20164 while(at) { 20165 if(at is selectionEnd.inlineElement) { 20166 at.text = at.text[selectionEnd.offset .. $]; 20167 if(selectionEnd.offset < at.letterXs.length) 20168 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 20169 selectionEnd.offset = 0; 20170 break; 20171 } else { 20172 auto cfd = at; 20173 cfd.text = null; // delete the whole thing 20174 20175 at = at.getNextInlineElement(); 20176 20177 if(cfd.text.length == 0) { 20178 // and remove cfd 20179 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 20180 if(cfd.containingBlock.parts[a] is cfd) { 20181 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 20182 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 20183 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 20184 20185 } 20186 } 20187 } 20188 } 20189 } 20190 } 20191 20192 caret = selectionEnd; 20193 selectNone(); 20194 20195 invalidateLayout(); 20196 20197 } 20198 20199 /// Plain text editing api. These work at the current caret inside the selected inline element. 20200 void insert(in char[] text) { 20201 foreach(dchar ch; text) 20202 insert(ch); 20203 } 20204 /// ditto 20205 void insert(dchar ch) { 20206 20207 bool selectionDeleted = false; 20208 if(selectionStart !is selectionEnd) { 20209 deleteSelection(); 20210 selectionDeleted = true; 20211 } 20212 20213 if(ch == 127) { 20214 delete_(); 20215 return; 20216 } 20217 if(ch == 8) { 20218 if(!selectionDeleted) 20219 backspace(); 20220 return; 20221 } 20222 20223 invalidateLayout(); 20224 20225 if(ch == 13) ch = 10; 20226 auto e = caret.inlineElement; 20227 if(e is null) { 20228 addText("" ~ cast(char) ch) ; // FIXME 20229 return; 20230 } 20231 20232 if(caret.offset == e.text.length) { 20233 e.text ~= cast(char) ch; // FIXME 20234 caret.offset++; 20235 if(ch == 10) { 20236 auto c = caret.inlineElement.clone; 20237 c.text = null; 20238 c.letterXs = null; 20239 insertPartAfter(c,e); 20240 caret = Caret(this, c, 0); 20241 } 20242 } else { 20243 // FIXME cast char sucks 20244 if(ch == 10) { 20245 auto c = caret.inlineElement.clone; 20246 c.text = e.text[caret.offset .. $]; 20247 if(caret.offset < c.letterXs.length) 20248 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 20249 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 20250 if(caret.offset <= e.letterXs.length) { 20251 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 20252 } 20253 insertPartAfter(c,e); 20254 caret = Caret(this, c, 0); 20255 } else { 20256 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 20257 caret.offset++; 20258 } 20259 } 20260 } 20261 20262 void insertPartAfter(InlineElement what, InlineElement where) { 20263 foreach(idx, p; where.containingBlock.parts) { 20264 if(p is where) { 20265 if(idx + 1 == where.containingBlock.parts.length) 20266 where.containingBlock.parts ~= what; 20267 else 20268 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 20269 return; 20270 } 20271 } 20272 } 20273 20274 void cleanupStructures() { 20275 for(size_t i = 0; i < blocks.length; i++) { 20276 auto block = blocks[i]; 20277 for(size_t a = 0; a < block.parts.length; a++) { 20278 auto part = block.parts[a]; 20279 if(part.text.length == 0) { 20280 for(size_t b = a; b < block.parts.length - 1; b++) 20281 block.parts[b] = block.parts[b+1]; 20282 block.parts = block.parts[0 .. $-1]; 20283 } 20284 } 20285 if(block.parts.length == 0) { 20286 for(size_t a = i; a < blocks.length - 1; a++) 20287 blocks[a] = blocks[a+1]; 20288 blocks = blocks[0 .. $-1]; 20289 } 20290 } 20291 } 20292 20293 void backspace() { 20294 try_again: 20295 auto e = caret.inlineElement; 20296 if(e is null) 20297 return; 20298 if(caret.offset == 0) { 20299 auto prev = e.getPreviousInlineElement(); 20300 if(prev is null) 20301 return; 20302 auto newOffset = cast(int) prev.text.length; 20303 tryMerge(prev, e); 20304 caret.inlineElement = prev; 20305 caret.offset = prev is null ? 0 : newOffset; 20306 20307 goto try_again; 20308 } else if(caret.offset == e.text.length) { 20309 e.text = e.text[0 .. $-1]; 20310 caret.offset--; 20311 } else { 20312 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 20313 caret.offset--; 20314 } 20315 //cleanupStructures(); 20316 20317 invalidateLayout(); 20318 } 20319 void delete_() { 20320 if(selectionStart !is selectionEnd) 20321 deleteSelection(); 20322 else { 20323 auto before = caret; 20324 moveRight(); 20325 if(caret != before) { 20326 backspace(); 20327 } 20328 } 20329 20330 invalidateLayout(); 20331 } 20332 void overstrike() {} 20333 20334 /// Selection API. See also: caret movement. 20335 void selectAll() { 20336 moveDocumentStart(selectionStart); 20337 moveDocumentEnd(selectionEnd); 20338 } 20339 bool selectNone() { 20340 if(selectionStart != selectionEnd) { 20341 selectionStart = selectionEnd = Caret.init; 20342 return true; 20343 } 20344 return false; 20345 } 20346 20347 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 20348 /// They will modify the current selection if there is one and will splice one in if needed. 20349 void changeAttributes() {} 20350 20351 20352 /// Text search api. They manipulate the selection and/or caret. 20353 void findText(string text) {} 20354 void findIndex(size_t textIndex) {} 20355 20356 // sample event handlers 20357 20358 void handleEvent(KeyEvent event) { 20359 //if(event.type == KeyEvent.Type.KeyPressed) { 20360 20361 //} 20362 } 20363 20364 void handleEvent(dchar ch) { 20365 20366 } 20367 20368 void handleEvent(MouseEvent event) { 20369 20370 } 20371 20372 bool contentEditable; // can it be edited? 20373 bool contentCaretable; // is there a caret/cursor that moves around in there? 20374 bool contentSelectable; // selectable? 20375 20376 Caret caret; 20377 Caret selectionStart; 20378 Caret selectionEnd; 20379 20380 bool insertMode; 20381 } 20382 20383 struct Caret { 20384 TextLayout layout; 20385 InlineElement inlineElement; 20386 int offset; 20387 } 20388 20389 enum TextFormat : ushort { 20390 // decorations 20391 underline = 1, 20392 strikethrough = 2, 20393 20394 // font selectors 20395 20396 bold = 0x4000 | 1, // weight 700 20397 light = 0x4000 | 2, // weight 300 20398 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 20399 // bold | light is really invalid but should give weight 500 20400 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 20401 20402 italic = 0x4000 | 8, 20403 smallcaps = 0x4000 | 16, 20404 } 20405 20406 void* findFont(string family, int weight, TextFormat formats) { 20407 return null; 20408 } 20409 20410 } 20411 20412 /++ 20413 $(PITFALL This is not yet stable and may break in future versions without notice.) 20414 20415 History: 20416 Added February 19, 2021 20417 +/ 20418 /// Group: drag_and_drop 20419 interface DropHandler { 20420 /++ 20421 Called when the drag enters the handler's area. 20422 +/ 20423 DragAndDropAction dragEnter(DropPackage*); 20424 /++ 20425 Called when the drag leaves the handler's area or is 20426 cancelled. You should free your resources when this is called. 20427 +/ 20428 void dragLeave(); 20429 /++ 20430 Called continually as the drag moves over the handler's area. 20431 20432 Returns: feedback to the dragger 20433 +/ 20434 DropParameters dragOver(Point pt); 20435 /++ 20436 The user dropped the data and you should process it now. You can 20437 access the data through the given [DropPackage]. 20438 +/ 20439 void drop(scope DropPackage*); 20440 /++ 20441 Called when the drop is complete. You should free whatever temporary 20442 resources you were using. It is often reasonable to simply forward 20443 this call to [dragLeave]. 20444 +/ 20445 void finish(); 20446 20447 /++ 20448 Parameters returned by [DropHandler.drop]. 20449 +/ 20450 static struct DropParameters { 20451 /++ 20452 Acceptable action over this area. 20453 +/ 20454 DragAndDropAction action; 20455 /++ 20456 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 20457 20458 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 20459 +/ 20460 Rectangle consistentWithin; 20461 } 20462 } 20463 20464 /++ 20465 History: 20466 Added February 19, 2021 20467 +/ 20468 /// Group: drag_and_drop 20469 enum DragAndDropAction { 20470 none = 0, 20471 copy, 20472 move, 20473 link, 20474 ask, 20475 custom 20476 } 20477 20478 /++ 20479 An opaque structure representing dropped data. It contains 20480 private, platform-specific data that your `drop` function 20481 should simply forward. 20482 20483 $(PITFALL This is not yet stable and may break in future versions without notice.) 20484 20485 History: 20486 Added February 19, 2021 20487 +/ 20488 /// Group: drag_and_drop 20489 struct DropPackage { 20490 /++ 20491 Lists the available formats as magic numbers. You should compare these 20492 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 20493 understand the passed data. 20494 +/ 20495 DraggableData.FormatId[] availableFormats() { 20496 version(X11) { 20497 return xFormats; 20498 } else version(Windows) { 20499 if(pDataObj is null) 20500 return null; 20501 20502 typeof(return) ret; 20503 20504 IEnumFORMATETC ef; 20505 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 20506 FORMATETC fmt; 20507 ULONG fetched; 20508 while(ef.Next(1, &fmt, &fetched) == S_OK) { 20509 if(fetched == 0) 20510 break; 20511 20512 if(fmt.lindex != -1) 20513 continue; 20514 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 20515 continue; 20516 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 20517 continue; 20518 20519 ret ~= fmt.cfFormat; 20520 } 20521 } 20522 20523 return ret; 20524 } 20525 } 20526 20527 /++ 20528 Gets data from the drop and optionally accepts it. 20529 20530 Returns: 20531 void because the data is fed asynchronously through the `dg` parameter. 20532 20533 Params: 20534 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. 20535 20536 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. 20537 20538 Calling `getData` again after accepting a drop is not permitted. 20539 20540 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 20541 20542 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. 20543 20544 Throws: 20545 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 20546 20547 History: 20548 Included in first release of [DropPackage]. 20549 +/ 20550 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 20551 version(X11) { 20552 20553 auto display = XDisplayConnection.get(); 20554 auto selectionAtom = GetAtom!"XdndSelection"(display); 20555 auto best = format; 20556 20557 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 20558 20559 XDisplay* display; 20560 Atom selectionAtom; 20561 DraggableData.FormatId best; 20562 DraggableData.FormatId format; 20563 void delegate(scope ubyte[] data) dg; 20564 DragAndDropAction acceptedAction; 20565 Window sourceWindow; 20566 SimpleWindow win; 20567 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 20568 this.display = display; 20569 this.win = win; 20570 this.sourceWindow = sourceWindow; 20571 this.format = format; 20572 this.selectionAtom = selectionAtom; 20573 this.best = best; 20574 this.dg = dg; 20575 this.acceptedAction = acceptedAction; 20576 } 20577 20578 20579 mixin X11GetSelectionHandler_Basics; 20580 20581 void handleData(Atom target, in ubyte[] data) { 20582 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 20583 20584 dg(cast(ubyte[]) data); 20585 20586 if(acceptedAction != DragAndDropAction.none) { 20587 auto display = XDisplayConnection.get; 20588 20589 XClientMessageEvent xclient; 20590 20591 xclient.type = EventType.ClientMessage; 20592 xclient.window = sourceWindow; 20593 xclient.message_type = GetAtom!"XdndFinished"(display); 20594 xclient.format = 32; 20595 xclient.data.l[0] = win.impl.window; 20596 xclient.data.l[1] = 1; // drop successful 20597 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 20598 20599 XSendEvent( 20600 display, 20601 sourceWindow, 20602 false, 20603 EventMask.NoEventMask, 20604 cast(XEvent*) &xclient 20605 ); 20606 20607 XFlush(display); 20608 } 20609 } 20610 20611 Atom findBestFormat(Atom[] answer) { 20612 Atom best = None; 20613 foreach(option; answer) { 20614 if(option == format) { 20615 best = option; 20616 break; 20617 } 20618 /* 20619 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 20620 best = option; 20621 break; 20622 } else if(option == XA_STRING) { 20623 best = option; 20624 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 20625 best = option; 20626 } 20627 */ 20628 } 20629 return best; 20630 } 20631 } 20632 20633 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 20634 20635 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 20636 20637 } else version(Windows) { 20638 20639 // clean up like DragLeave 20640 // pass effect back up 20641 20642 FORMATETC t; 20643 assert(format >= 0 && format <= ushort.max); 20644 t.cfFormat = cast(ushort) format; 20645 t.lindex = -1; 20646 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 20647 t.tymed = TYMED.TYMED_HGLOBAL; 20648 20649 STGMEDIUM m; 20650 20651 if(pDataObj.GetData(&t, &m) != S_OK) { 20652 // fail 20653 } else { 20654 // succeed, take the data and clean up 20655 20656 // FIXME: ensure it is legit HGLOBAL 20657 auto handle = m.hGlobal; 20658 20659 if(handle) { 20660 auto sz = GlobalSize(handle); 20661 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 20662 scope(exit) GlobalUnlock(handle); 20663 scope(exit) GlobalFree(handle); 20664 20665 auto data = ptr[0 .. sz]; 20666 20667 dg(data); 20668 } 20669 } 20670 } 20671 } 20672 } 20673 20674 private: 20675 20676 version(X11) { 20677 SimpleWindow win; 20678 Window sourceWindow; 20679 Time dataTimestamp; 20680 20681 Atom[] xFormats; 20682 } 20683 version(Windows) { 20684 IDataObject pDataObj; 20685 } 20686 } 20687 20688 /++ 20689 A generic helper base class for making a drop handler with a preference list of custom types. 20690 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 20691 droppers too. 20692 20693 It assumes the whole window it used, but you can subclass to change that. 20694 20695 $(PITFALL This is not yet stable and may break in future versions without notice.) 20696 20697 History: 20698 Added February 19, 2021 20699 +/ 20700 /// Group: drag_and_drop 20701 class GenericDropHandlerBase : DropHandler { 20702 // no fancy state here so no need to do anything here 20703 void finish() { } 20704 void dragLeave() { } 20705 20706 private DragAndDropAction acceptedAction; 20707 private DraggableData.FormatId acceptedFormat; 20708 private void delegate(scope ubyte[]) acceptedHandler; 20709 20710 struct FormatHandler { 20711 DraggableData.FormatId format; 20712 void delegate(scope ubyte[]) handler; 20713 } 20714 20715 protected abstract FormatHandler[] formatHandlers(); 20716 20717 DragAndDropAction dragEnter(DropPackage* pkg) { 20718 debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 20719 foreach(fmt; formatHandlers()) 20720 foreach(f; pkg.availableFormats()) 20721 if(f == fmt.format) { 20722 acceptedFormat = f; 20723 acceptedHandler = fmt.handler; 20724 return acceptedAction = DragAndDropAction.copy; 20725 } 20726 return acceptedAction = DragAndDropAction.none; 20727 } 20728 DropParameters dragOver(Point pt) { 20729 return DropParameters(acceptedAction); 20730 } 20731 20732 void drop(scope DropPackage* dropPackage) { 20733 if(!acceptedFormat || acceptedHandler is null) { 20734 debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 20735 return; // prolly shouldn't happen anyway... 20736 } 20737 20738 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 20739 } 20740 } 20741 20742 /++ 20743 A simple handler for making your window accept drops of plain text. 20744 20745 $(PITFALL This is not yet stable and may break in future versions without notice.) 20746 20747 History: 20748 Added February 22, 2021 20749 +/ 20750 /// Group: drag_and_drop 20751 class TextDropHandler : GenericDropHandlerBase { 20752 private void delegate(in char[] text) dg; 20753 20754 /++ 20755 20756 +/ 20757 this(void delegate(in char[] text) dg) { 20758 this.dg = dg; 20759 } 20760 20761 protected override FormatHandler[] formatHandlers() { 20762 version(X11) 20763 return [ 20764 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 20765 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 20766 ]; 20767 else version(Windows) 20768 return [ 20769 FormatHandler(CF_UNICODETEXT, &translator), 20770 ]; 20771 } 20772 20773 private void translator(scope ubyte[] data) { 20774 version(X11) 20775 dg(cast(char[]) data); 20776 else version(Windows) 20777 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 20778 } 20779 } 20780 20781 /++ 20782 A simple handler for making your window accept drops of files, issued to you as file names. 20783 20784 $(PITFALL This is not yet stable and may break in future versions without notice.) 20785 20786 History: 20787 Added February 22, 2021 20788 +/ 20789 /// Group: drag_and_drop 20790 20791 class FilesDropHandler : GenericDropHandlerBase { 20792 private void delegate(in char[][]) dg; 20793 20794 /++ 20795 20796 +/ 20797 this(void delegate(in char[][] fileNames) dg) { 20798 this.dg = dg; 20799 } 20800 20801 protected override FormatHandler[] formatHandlers() { 20802 version(X11) 20803 return [ 20804 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 20805 ]; 20806 else version(Windows) 20807 return [ 20808 FormatHandler(CF_HDROP, &translator), 20809 ]; 20810 } 20811 20812 private void translator(scope ubyte[] data) { 20813 version(X11) { 20814 char[] listString = cast(char[]) data; 20815 char[][16] buffer; 20816 int count; 20817 char[][] result = buffer[]; 20818 20819 void commit(char[] s) { 20820 if(count == result.length) 20821 result.length += 16; 20822 if(s.length > 7 && s[0 ..7] == "file://") 20823 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 20824 result[count++] = s; 20825 } 20826 20827 size_t last; 20828 foreach(idx, char c; listString) { 20829 if(c == '\n') { 20830 commit(listString[last .. idx - 1]); // a \r 20831 last = idx + 1; // a \n 20832 } 20833 } 20834 20835 if(last < listString.length) { 20836 commit(listString[last .. $]); 20837 } 20838 20839 // FIXME: they are uris now, should I translate it to local file names? 20840 // of course the host name is supposed to be there cuz of X rokking... 20841 20842 dg(result[0 .. count]); 20843 } else version(Windows) { 20844 20845 static struct DROPFILES { 20846 DWORD pFiles; 20847 POINT pt; 20848 BOOL fNC; 20849 BOOL fWide; 20850 } 20851 20852 20853 const(char)[][16] buffer; 20854 int count; 20855 const(char)[][] result = buffer[]; 20856 size_t last; 20857 20858 void commitA(in char[] stuff) { 20859 if(count == result.length) 20860 result.length += 16; 20861 result[count++] = stuff; 20862 } 20863 20864 void commitW(in wchar[] stuff) { 20865 commitA(makeUtf8StringFromWindowsString(stuff)); 20866 } 20867 20868 void magic(T)(T chars) { 20869 size_t idx; 20870 while(chars[idx]) { 20871 last = idx; 20872 while(chars[idx]) { 20873 idx++; 20874 } 20875 static if(is(T == char*)) 20876 commitA(chars[last .. idx]); 20877 else 20878 commitW(chars[last .. idx]); 20879 idx++; 20880 } 20881 } 20882 20883 auto df = cast(DROPFILES*) data.ptr; 20884 if(df.fWide) { 20885 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 20886 magic(chars); 20887 } else { 20888 char* chars = cast(char*) (data.ptr + df.pFiles); 20889 magic(chars); 20890 } 20891 dg(result[0 .. count]); 20892 } 20893 } 20894 } 20895 20896 /++ 20897 Interface to describe data being dragged. See also [draggable] helper function. 20898 20899 $(PITFALL This is not yet stable and may break in future versions without notice.) 20900 20901 History: 20902 Added February 19, 2021 20903 +/ 20904 interface DraggableData { 20905 version(X11) 20906 alias FormatId = Atom; 20907 else 20908 alias FormatId = uint; 20909 /++ 20910 Gets the platform-specific FormatId associated with the given named format. 20911 20912 This may be a MIME type, but may also be other various strings defined by the 20913 programs you want to interoperate with. 20914 20915 FIXME: sdpy needs to offer data adapter things that look for compatible formats 20916 and convert it to some particular type for you. 20917 +/ 20918 static FormatId getFormatId(string name)() { 20919 version(X11) 20920 return GetAtom!name(XDisplayConnection.get); 20921 else version(Windows) { 20922 static UINT cache; 20923 if(!cache) 20924 cache = RegisterClipboardFormatA(name); 20925 return cache; 20926 } else 20927 throw new NotYetImplementedException(); 20928 } 20929 20930 /++ 20931 Looks up a string to represent the name for the given format, if there is one. 20932 20933 You should avoid using this function because it is slow. It is provided more for 20934 debugging than for primary use. 20935 +/ 20936 static string getFormatName(FormatId format) { 20937 version(X11) { 20938 if(format == 0) 20939 return "None"; 20940 else 20941 return getAtomName(format, XDisplayConnection.get); 20942 } else version(Windows) { 20943 switch(format) { 20944 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 20945 case CF_DIBV5: return "CF_DIBV5"; 20946 case CF_RIFF: return "CF_RIFF"; 20947 case CF_WAVE: return "CF_WAVE"; 20948 case CF_HDROP: return "CF_HDROP"; 20949 default: 20950 char[1024] name; 20951 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 20952 return name[0 .. count].idup; 20953 } 20954 } 20955 } 20956 20957 FormatId[] availableFormats(); 20958 // Return the slice of data you filled, empty slice if done. 20959 // this is to support the incremental thing 20960 ubyte[] getData(FormatId format, return scope ubyte[] data); 20961 20962 size_t dataLength(FormatId format); 20963 } 20964 20965 /++ 20966 $(PITFALL This is not yet stable and may break in future versions without notice.) 20967 20968 History: 20969 Added February 19, 2021 20970 +/ 20971 DraggableData draggable(string s) { 20972 version(X11) 20973 return new class X11SetSelectionHandler_Text, DraggableData { 20974 this() { 20975 super(s); 20976 } 20977 20978 override FormatId[] availableFormats() { 20979 return X11SetSelectionHandler_Text.availableFormats(); 20980 } 20981 20982 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 20983 return X11SetSelectionHandler_Text.getData(format, data); 20984 } 20985 20986 size_t dataLength(FormatId format) { 20987 return s.length; 20988 } 20989 }; 20990 version(Windows) 20991 return new class DraggableData { 20992 FormatId[] availableFormats() { 20993 return [CF_UNICODETEXT]; 20994 } 20995 20996 ubyte[] getData(FormatId format, return scope ubyte[] data) { 20997 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 20998 } 20999 21000 size_t dataLength(FormatId format) { 21001 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 21002 } 21003 }; 21004 } 21005 21006 /++ 21007 $(PITFALL This is not yet stable and may break in future versions without notice.) 21008 21009 History: 21010 Added February 19, 2021 21011 +/ 21012 /// Group: drag_and_drop 21013 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 21014 in { 21015 assert(window !is null); 21016 assert(handler !is null); 21017 } 21018 do 21019 { 21020 version(X11) { 21021 auto sh = cast(X11SetSelectionHandler) handler; 21022 if(sh is null) { 21023 // gotta make my own adapter. 21024 sh = new class X11SetSelectionHandler { 21025 mixin X11SetSelectionHandler_Basics; 21026 21027 Atom[] availableFormats() { return handler.availableFormats(); } 21028 ubyte[] getData(Atom format, return scope ubyte[] data) { 21029 return handler.getData(format, data); 21030 } 21031 21032 // since the drop selection is only ever used once it isn't important 21033 // to reset it. 21034 void done() {} 21035 }; 21036 } 21037 return doDragDropX11(window, sh, action); 21038 } else version(Windows) { 21039 return doDragDropWindows(window, handler, action); 21040 } else throw new NotYetImplementedException(); 21041 } 21042 21043 version(Windows) 21044 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 21045 IDataObject obj = new class IDataObject { 21046 ULONG refCount; 21047 ULONG AddRef() { 21048 return ++refCount; 21049 } 21050 ULONG Release() { 21051 return --refCount; 21052 } 21053 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21054 if (IID_IUnknown == *riid) { 21055 *ppv = cast(void*) cast(IUnknown) this; 21056 } 21057 else if (IID_IDataObject == *riid) { 21058 *ppv = cast(void*) cast(IDataObject) this; 21059 } 21060 else { 21061 *ppv = null; 21062 return E_NOINTERFACE; 21063 } 21064 21065 AddRef(); 21066 return NOERROR; 21067 } 21068 21069 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 21070 // import std.stdio; writeln("Advise"); 21071 return E_NOTIMPL; 21072 } 21073 HRESULT DUnadvise(DWORD dwConnection) { 21074 return E_NOTIMPL; 21075 } 21076 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 21077 // import std.stdio; writeln("EnumDAdvise"); 21078 return OLE_E_ADVISENOTSUPPORTED; 21079 } 21080 // tell what formats it supports 21081 21082 FORMATETC[] types; 21083 this() { 21084 FORMATETC t; 21085 foreach(ty; handler.availableFormats()) { 21086 assert(ty <= ushort.max && ty >= 0); 21087 t.cfFormat = cast(ushort) ty; 21088 t.lindex = -1; 21089 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21090 t.tymed = TYMED.TYMED_HGLOBAL; 21091 } 21092 types ~= t; 21093 } 21094 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 21095 if(dwDirection == DATADIR.DATADIR_GET) { 21096 *ppenumFormatEtc = new class IEnumFORMATETC { 21097 ULONG refCount; 21098 ULONG AddRef() { 21099 return ++refCount; 21100 } 21101 ULONG Release() { 21102 return --refCount; 21103 } 21104 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21105 if (IID_IUnknown == *riid) { 21106 *ppv = cast(void*) cast(IUnknown) this; 21107 } 21108 else if (IID_IEnumFORMATETC == *riid) { 21109 *ppv = cast(void*) cast(IEnumFORMATETC) this; 21110 } 21111 else { 21112 *ppv = null; 21113 return E_NOINTERFACE; 21114 } 21115 21116 AddRef(); 21117 return NOERROR; 21118 } 21119 21120 21121 int pos; 21122 this() { 21123 pos = 0; 21124 } 21125 21126 HRESULT Clone(IEnumFORMATETC* ppenum) { 21127 // import std.stdio; writeln("clone"); 21128 return E_NOTIMPL; // FIXME 21129 } 21130 21131 // Caller is responsible for freeing memory 21132 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 21133 // fetched may be null if celt is one 21134 if(celt != 1) 21135 return E_NOTIMPL; // FIXME 21136 21137 if(celt + pos > types.length) 21138 return S_FALSE; 21139 21140 *rgelt = types[pos++]; 21141 21142 if(pceltFetched !is null) 21143 *pceltFetched = 1; 21144 21145 // import std.stdio; writeln("ok celt ", celt); 21146 return S_OK; 21147 } 21148 21149 HRESULT Reset() { 21150 pos = 0; 21151 return S_OK; 21152 } 21153 21154 HRESULT Skip(ULONG celt) { 21155 if(celt + pos <= types.length) { 21156 pos += celt; 21157 return S_OK; 21158 } 21159 return S_FALSE; 21160 } 21161 }; 21162 21163 return S_OK; 21164 } else 21165 return E_NOTIMPL; 21166 } 21167 // given a format, return the format you'd prefer to use cuz it is identical 21168 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 21169 // FIXME: prolly could be better but meh 21170 // import std.stdio; writeln("gcf: ", *pformatectIn); 21171 *pformatetcOut = *pformatectIn; 21172 return S_OK; 21173 } 21174 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21175 foreach(ty; types) { 21176 if(ty == *pformatetcIn) { 21177 auto format = ty.cfFormat; 21178 // import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty); 21179 STGMEDIUM medium; 21180 medium.tymed = TYMED.TYMED_HGLOBAL; 21181 21182 auto sz = handler.dataLength(format); 21183 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 21184 if(handle is null) throw new WindowsApiException("GlobalAlloc", GetLastError()); 21185 if(auto data = cast(wchar*) GlobalLock(handle)) { 21186 auto slice = data[0 .. sz]; 21187 scope(exit) 21188 GlobalUnlock(handle); 21189 21190 handler.getData(format, cast(ubyte[]) slice[]); 21191 } 21192 21193 21194 medium.hGlobal = handle; // FIXME 21195 *pmedium = medium; 21196 return S_OK; 21197 } 21198 } 21199 return DV_E_FORMATETC; 21200 } 21201 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21202 // import std.stdio; writeln("GDH: ", *pformatetcIn); 21203 return E_NOTIMPL; // FIXME 21204 } 21205 HRESULT QueryGetData(FORMATETC* pformatetc) { 21206 auto search = *pformatetc; 21207 search.tymed &= TYMED.TYMED_HGLOBAL; 21208 foreach(ty; types) 21209 if(ty == search) { 21210 // import std.stdio; writeln("QueryGetData ", search, " ", types[0]); 21211 return S_OK; 21212 } 21213 if(pformatetc.cfFormat==CF_UNICODETEXT) { 21214 //import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]); 21215 } 21216 return S_FALSE; 21217 } 21218 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 21219 // import std.stdio; writeln("SetData: "); 21220 return E_NOTIMPL; 21221 } 21222 }; 21223 21224 21225 IDropSource src = new class IDropSource { 21226 ULONG refCount; 21227 ULONG AddRef() { 21228 return ++refCount; 21229 } 21230 ULONG Release() { 21231 return --refCount; 21232 } 21233 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21234 if (IID_IUnknown == *riid) { 21235 *ppv = cast(void*) cast(IUnknown) this; 21236 } 21237 else if (IID_IDropSource == *riid) { 21238 *ppv = cast(void*) cast(IDropSource) this; 21239 } 21240 else { 21241 *ppv = null; 21242 return E_NOINTERFACE; 21243 } 21244 21245 AddRef(); 21246 return NOERROR; 21247 } 21248 21249 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 21250 if(fEscapePressed) 21251 return DRAGDROP_S_CANCEL; 21252 if(!(grfKeyState & MK_LBUTTON)) 21253 return DRAGDROP_S_DROP; 21254 return S_OK; 21255 } 21256 21257 int GiveFeedback(uint dwEffect) { 21258 return DRAGDROP_S_USEDEFAULTCURSORS; 21259 } 21260 }; 21261 21262 DWORD effect; 21263 21264 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 21265 21266 DROPEFFECT de = win32DragAndDropAction(action); 21267 21268 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 21269 // but still prolly a FIXME 21270 21271 auto ret = DoDragDrop(obj, src, de, &effect); 21272 /+ 21273 import std.stdio; 21274 if(ret == DRAGDROP_S_DROP) 21275 writeln("drop ", effect); 21276 else if(ret == DRAGDROP_S_CANCEL) 21277 writeln("cancel"); 21278 else if(ret == S_OK) 21279 writeln("ok"); 21280 else writeln(ret); 21281 +/ 21282 21283 return ret; 21284 } 21285 21286 version(Windows) 21287 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 21288 DROPEFFECT de; 21289 21290 with(DragAndDropAction) 21291 with(DROPEFFECT) 21292 final switch(action) { 21293 case none: de = DROPEFFECT_NONE; break; 21294 case copy: de = DROPEFFECT_COPY; break; 21295 case move: de = DROPEFFECT_MOVE; break; 21296 case link: de = DROPEFFECT_LINK; break; 21297 case ask: throw new Exception("ask not implemented yet"); 21298 case custom: throw new Exception("custom not implemented yet"); 21299 } 21300 21301 return de; 21302 } 21303 21304 21305 /++ 21306 History: 21307 Added February 19, 2021 21308 +/ 21309 /// Group: drag_and_drop 21310 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 21311 version(X11) { 21312 auto display = XDisplayConnection.get; 21313 21314 Atom atom = 5; // right??? 21315 21316 XChangeProperty( 21317 display, 21318 window.impl.window, 21319 GetAtom!"XdndAware"(display), 21320 XA_ATOM, 21321 32 /* bits */, 21322 PropModeReplace, 21323 &atom, 21324 1); 21325 21326 window.dropHandler = handler; 21327 } else version(Windows) { 21328 21329 initDnd(); 21330 21331 auto dropTarget = new class (handler) IDropTarget { 21332 DropHandler handler; 21333 this(DropHandler handler) { 21334 this.handler = handler; 21335 } 21336 ULONG refCount; 21337 ULONG AddRef() { 21338 return ++refCount; 21339 } 21340 ULONG Release() { 21341 return --refCount; 21342 } 21343 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21344 if (IID_IUnknown == *riid) { 21345 *ppv = cast(void*) cast(IUnknown) this; 21346 } 21347 else if (IID_IDropTarget == *riid) { 21348 *ppv = cast(void*) cast(IDropTarget) this; 21349 } 21350 else { 21351 *ppv = null; 21352 return E_NOINTERFACE; 21353 } 21354 21355 AddRef(); 21356 return NOERROR; 21357 } 21358 21359 21360 // /////////////////// 21361 21362 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21363 DropPackage dropPackage = DropPackage(pDataObj); 21364 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 21365 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 21366 } 21367 21368 HRESULT DragLeave() { 21369 handler.dragLeave(); 21370 // release the IDataObject if needed 21371 return S_OK; 21372 } 21373 21374 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21375 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 21376 21377 *pdwEffect = win32DragAndDropAction(res.action); 21378 // same as DragEnter basically 21379 return S_OK; 21380 } 21381 21382 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21383 DropPackage pkg = DropPackage(pDataObj); 21384 handler.drop(&pkg); 21385 21386 return S_OK; 21387 } 21388 }; 21389 // Windows can hold on to the handler and try to call it 21390 // during which time the GC can't see it. so important to 21391 // manually manage this. At some point i'll FIXME and make 21392 // all my com instances manually managed since they supposed 21393 // to respect the refcount. 21394 import core.memory; 21395 GC.addRoot(cast(void*) dropTarget); 21396 21397 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 21398 throw new WindowsApiException("RegisterDragDrop", GetLastError()); 21399 21400 window.dropHandler = handler; 21401 } else throw new NotYetImplementedException(); 21402 } 21403 21404 21405 21406 static if(UsingSimpledisplayX11) { 21407 21408 enum _NET_WM_STATE_ADD = 1; 21409 enum _NET_WM_STATE_REMOVE = 0; 21410 enum _NET_WM_STATE_TOGGLE = 2; 21411 21412 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 21413 void demandAttention(SimpleWindow window, bool needs = true) { 21414 demandAttention(window.impl.window, needs); 21415 } 21416 21417 /// ditto 21418 void demandAttention(Window window, bool needs = true) { 21419 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 21420 } 21421 21422 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 21423 auto display = XDisplayConnection.get(); 21424 if(atom == None) 21425 return; // non-failure error 21426 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 21427 21428 XClientMessageEvent xclient; 21429 21430 xclient.type = EventType.ClientMessage; 21431 xclient.window = window; 21432 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 21433 xclient.format = 32; 21434 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 21435 xclient.data.l[1] = atom; 21436 xclient.data.l[2] = atom2; 21437 xclient.data.l[3] = 1; 21438 // [3] == source. 0 == unknown, 1 == app, 2 == else 21439 21440 XSendEvent( 21441 display, 21442 RootWindow(display, DefaultScreen(display)), 21443 false, 21444 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 21445 cast(XEvent*) &xclient 21446 ); 21447 21448 /+ 21449 XChangeProperty( 21450 display, 21451 window.impl.window, 21452 GetAtom!"_NET_WM_STATE"(display), 21453 XA_ATOM, 21454 32 /* bits */, 21455 PropModeAppend, 21456 &atom, 21457 1); 21458 +/ 21459 } 21460 21461 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 21462 Atom actionAtom; 21463 with(DragAndDropAction) 21464 final switch(action) { 21465 case none: actionAtom = None; break; 21466 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 21467 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 21468 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 21469 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 21470 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 21471 } 21472 21473 return actionAtom; 21474 } 21475 21476 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 21477 // FIXME: I need to show user feedback somehow. 21478 auto display = XDisplayConnection.get; 21479 21480 auto actionAtom = dndActionAtom(display, action); 21481 assert(actionAtom, "Don't use action none to accept a drop"); 21482 21483 setX11Selection!"XdndSelection"(window, handler, null); 21484 21485 auto oldKeyHandler = window.handleKeyEvent; 21486 scope(exit) window.handleKeyEvent = oldKeyHandler; 21487 21488 auto oldCharHandler = window.handleCharEvent; 21489 scope(exit) window.handleCharEvent = oldCharHandler; 21490 21491 auto oldMouseHandler = window.handleMouseEvent; 21492 scope(exit) window.handleMouseEvent = oldMouseHandler; 21493 21494 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 21495 21496 import core.sys.posix.sys.time; 21497 timeval tv; 21498 gettimeofday(&tv, null); 21499 21500 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 21501 21502 Time lastMouseTimestamp; 21503 21504 bool dnding = true; 21505 Window lastIn = None; 21506 21507 void leave() { 21508 if(lastIn == None) 21509 return; 21510 21511 XEvent ev; 21512 ev.xclient.type = EventType.ClientMessage; 21513 ev.xclient.window = lastIn; 21514 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 21515 ev.xclient.format = 32; 21516 ev.xclient.data.l[0] = window.impl.window; 21517 21518 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21519 XFlush(display); 21520 21521 lastIn = None; 21522 } 21523 21524 void enter(Window w) { 21525 assert(lastIn == None); 21526 21527 lastIn = w; 21528 21529 XEvent ev; 21530 ev.xclient.type = EventType.ClientMessage; 21531 ev.xclient.window = lastIn; 21532 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 21533 ev.xclient.format = 32; 21534 ev.xclient.data.l[0] = window.impl.window; 21535 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 21536 21537 auto types = handler.availableFormats(); 21538 assert(types.length > 0); 21539 21540 ev.xclient.data.l[2] = types[0]; 21541 if(types.length > 1) 21542 ev.xclient.data.l[3] = types[1]; 21543 if(types.length > 2) 21544 ev.xclient.data.l[4] = types[2]; 21545 21546 // FIXME: other types?!?!? and make sure we skip TARGETS 21547 21548 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21549 XFlush(display); 21550 } 21551 21552 void position(int rootX, int rootY) { 21553 assert(lastIn != None); 21554 21555 XEvent ev; 21556 ev.xclient.type = EventType.ClientMessage; 21557 ev.xclient.window = lastIn; 21558 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 21559 ev.xclient.format = 32; 21560 ev.xclient.data.l[0] = window.impl.window; 21561 ev.xclient.data.l[1] = 0; // reserved 21562 ev.xclient.data.l[2] = (rootX << 16) | rootY; 21563 ev.xclient.data.l[3] = dataTimestamp; 21564 ev.xclient.data.l[4] = actionAtom; 21565 21566 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21567 XFlush(display); 21568 21569 } 21570 21571 void drop() { 21572 XEvent ev; 21573 ev.xclient.type = EventType.ClientMessage; 21574 ev.xclient.window = lastIn; 21575 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 21576 ev.xclient.format = 32; 21577 ev.xclient.data.l[0] = window.impl.window; 21578 ev.xclient.data.l[1] = 0; // reserved 21579 ev.xclient.data.l[2] = dataTimestamp; 21580 21581 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21582 XFlush(display); 21583 21584 lastIn = None; 21585 dnding = false; 21586 } 21587 21588 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 21589 // but idk if i should... 21590 21591 window.setEventHandlers( 21592 delegate(KeyEvent ev) { 21593 if(ev.pressed == true && ev.key == Key.Escape) { 21594 // cancel 21595 dnding = false; 21596 } 21597 }, 21598 delegate(MouseEvent ev) { 21599 if(ev.timestamp < lastMouseTimestamp) 21600 return; 21601 21602 lastMouseTimestamp = ev.timestamp; 21603 21604 if(ev.type == MouseEventType.motion) { 21605 auto display = XDisplayConnection.get; 21606 auto root = RootWindow(display, DefaultScreen(display)); 21607 21608 Window topWindow; 21609 int rootX, rootY; 21610 21611 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 21612 21613 if(topWindow == None) 21614 return; 21615 21616 top: 21617 if(auto result = topWindow in eligibility) { 21618 auto dropWindow = *result; 21619 if(dropWindow == None) { 21620 leave(); 21621 return; 21622 } 21623 21624 if(dropWindow != lastIn) { 21625 leave(); 21626 enter(dropWindow); 21627 position(rootX, rootY); 21628 } else { 21629 position(rootX, rootY); 21630 } 21631 } else { 21632 // determine eligibility 21633 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 21634 if(data.length == 1) { 21635 // in case there is no WM or it isn't reparenting 21636 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 21637 } else { 21638 21639 Window tryScanChildren(Window search, int maxRecurse) { 21640 // could be reparenting window manager, so gotta check the next few children too 21641 Window child; 21642 int x; 21643 int y; 21644 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 21645 21646 if(child == None) 21647 return None; 21648 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 21649 if(data.length == 1) { 21650 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 21651 } else { 21652 if(maxRecurse) 21653 return tryScanChildren(child, maxRecurse - 1); 21654 else 21655 return None; 21656 } 21657 21658 } 21659 21660 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 21661 auto topResult = tryScanChildren(topWindow, 3); 21662 // it is easy to have a false negative due to the mouse going over a WM 21663 // child window like the close button if separate from the frame... so I 21664 // can't really cache negatives, :( 21665 if(topResult != None) { 21666 eligibility[topWindow] = topResult; 21667 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 21668 } 21669 } 21670 21671 } 21672 21673 } else if(ev.type == MouseEventType.buttonReleased) { 21674 drop(); 21675 dnding = false; 21676 } 21677 } 21678 ); 21679 21680 window.grabInput(); 21681 scope(exit) 21682 window.releaseInputGrab(); 21683 21684 21685 EventLoop.get.run(() => dnding); 21686 21687 return 0; 21688 } 21689 21690 /// X-specific 21691 TrueColorImage getWindowNetWmIcon(Window window) { 21692 try { 21693 auto display = XDisplayConnection.get; 21694 21695 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 21696 21697 if (data.length > arch_ulong.sizeof * 2) { 21698 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 21699 // these are an array of rgba images that we have to convert into pixmaps ourself 21700 21701 int width = cast(int) meta[0]; 21702 int height = cast(int) meta[1]; 21703 21704 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 21705 21706 static if(arch_ulong.sizeof == 4) { 21707 bytes = bytes[0 .. width * height * 4]; 21708 alias imageData = bytes; 21709 } else static if(arch_ulong.sizeof == 8) { 21710 bytes = bytes[0 .. width * height * 8]; 21711 auto imageData = new ubyte[](4 * width * height); 21712 } else static assert(0); 21713 21714 21715 21716 // this returns ARGB. Remember it is little-endian so 21717 // we have BGRA 21718 // our thing uses RGBA, which in little endian, is ABGR 21719 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 21720 auto r = bytes[idx + 2]; 21721 auto g = bytes[idx + 1]; 21722 auto b = bytes[idx + 0]; 21723 auto a = bytes[idx + 3]; 21724 21725 imageData[idx2 + 0] = r; 21726 imageData[idx2 + 1] = g; 21727 imageData[idx2 + 2] = b; 21728 imageData[idx2 + 3] = a; 21729 } 21730 21731 return new TrueColorImage(width, height, imageData); 21732 } 21733 21734 return null; 21735 } catch(Exception e) { 21736 return null; 21737 } 21738 } 21739 21740 } /* UsingSimpledisplayX11 */ 21741 21742 21743 void loadBinNameToWindowClassName () { 21744 import core.stdc.stdlib : realloc; 21745 version(linux) { 21746 // args[0] MAY be empty, so we'll just use this 21747 import core.sys.posix.unistd : readlink; 21748 char[1024] ebuf = void; // 1KB should be enough for everyone! 21749 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 21750 if (len < 1) return; 21751 } else /*version(Windows)*/ { 21752 import core.runtime : Runtime; 21753 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 21754 auto ebuf = Runtime.args[0]; 21755 auto len = ebuf.length; 21756 } 21757 auto pos = len; 21758 while (pos > 0 && ebuf[pos-1] != '/') --pos; 21759 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 21760 if (sdpyWindowClassStr is null) return; // oops 21761 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 21762 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 21763 } 21764 21765 /++ 21766 An interface representing a font that is drawn with custom facilities. 21767 21768 You might want [OperatingSystemFont] instead, which represents 21769 a font loaded and drawn by functions native to the operating system. 21770 21771 WARNING: I might still change this. 21772 +/ 21773 interface DrawableFont : MeasurableFont { 21774 /++ 21775 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 21776 21777 Implementations must use the painter's fillColor to draw a rectangle behind the string, 21778 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 21779 fill color, but that's up to the implementation. 21780 +/ 21781 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 21782 21783 /++ 21784 Requests that the given string is added to the image cache. You should only do this rarely, but 21785 if you have a string that you know will be used over and over again, adding it to a cache can 21786 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 21787 to implement this as a do-nothing method). 21788 +/ 21789 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 21790 } 21791 21792 /++ 21793 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 21794 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 21795 21796 You should also consider [OperatingSystemFont], which loads and draws a font with 21797 facilities native to the user's operating system. You might also consider 21798 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 21799 of game, as they have their own ways to draw text too. 21800 21801 Be warned: this can be slow, especially on remote connections to the X server, since 21802 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 21803 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 21804 experiment in your specific case. 21805 21806 Please note that the return type of [DrawableFont] also includes an implementation of 21807 [MeasurableFont]. 21808 +/ 21809 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 21810 import arsd.ttf; 21811 static class ArsdTtfFont : DrawableFont { 21812 TtfFont font; 21813 int size; 21814 this(in ubyte[] data, int size) { 21815 font = TtfFont(data); 21816 this.size = size; 21817 21818 21819 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 21820 int ascent_, descent_, line_gap; 21821 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 21822 21823 int advance, lsb; 21824 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 21825 xWidth = cast(int) (advance * scale); 21826 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 21827 MWidth = cast(int) (advance * scale); 21828 } 21829 21830 private int ascent_; 21831 private int descent_; 21832 private int xWidth; 21833 private int MWidth; 21834 21835 bool isMonospace() { 21836 return xWidth == MWidth; 21837 } 21838 int averageWidth() { 21839 return xWidth; 21840 } 21841 int height() { 21842 return size; 21843 } 21844 int ascent() { 21845 return ascent_; 21846 } 21847 int descent() { 21848 return descent_; 21849 } 21850 21851 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 21852 int width, height; 21853 font.getStringSize(s, size, width, height); 21854 return width; 21855 } 21856 21857 21858 21859 Sprite[string] cache; 21860 21861 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 21862 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 21863 cache[text] = sprite; 21864 } 21865 21866 Image stringToImage(Color fg, Color bg, in char[] text) { 21867 int width, height; 21868 auto data = font.renderString(text, size, width, height); 21869 auto image = new TrueColorImage(width, height); 21870 int pos = 0; 21871 foreach(y; 0 .. height) 21872 foreach(x; 0 .. width) { 21873 fg.a = data[0]; 21874 bg.a = 255; 21875 auto color = alphaBlend(fg, bg); 21876 image.imageData.bytes[pos++] = color.r; 21877 image.imageData.bytes[pos++] = color.g; 21878 image.imageData.bytes[pos++] = color.b; 21879 image.imageData.bytes[pos++] = data[0]; 21880 data = data[1 .. $]; 21881 } 21882 assert(data.length == 0); 21883 21884 return Image.fromMemoryImage(image); 21885 } 21886 21887 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 21888 Sprite sprite = (text in cache) ? *(text in cache) : null; 21889 21890 auto fg = painter.impl._outlineColor; 21891 auto bg = painter.impl._fillColor; 21892 21893 if(sprite !is null) { 21894 auto w = cast(SimpleWindow) painter.window; 21895 assert(w !is null); 21896 21897 sprite.drawAt(painter, upperLeft); 21898 } else { 21899 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 21900 } 21901 } 21902 } 21903 21904 return new ArsdTtfFont(data, size); 21905 } 21906 21907 class NotYetImplementedException : Exception { 21908 this(string file = __FILE__, size_t line = __LINE__) { 21909 super("Not yet implemented", file, line); 21910 } 21911 } 21912 21913 /// 21914 __gshared bool librariesSuccessfullyLoaded = true; 21915 /// 21916 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 21917 21918 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 21919 mixin(staticForeachReplacement!Iface); 21920 21921 void loadDynamicLibrary() @nogc { 21922 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 21923 } 21924 21925 void loadDynamicLibraryForReal() { 21926 foreach(name; __traits(derivedMembers, Iface)) { 21927 mixin("alias tmp = " ~ name ~ ";"); 21928 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 21929 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 21930 } 21931 } 21932 } 21933 21934 private const(char)[] staticForeachReplacement(Iface)() pure { 21935 /* 21936 // just this for gdc 9.... 21937 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 21938 21939 static foreach(name; __traits(derivedMembers, Iface)) 21940 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 21941 */ 21942 21943 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 21944 size_t pos; 21945 21946 void append(in char[] what) { 21947 if(pos + what.length > code.length) 21948 code.length = (code.length * 3) / 2; 21949 code[pos .. pos + what.length] = what[]; 21950 pos += what.length; 21951 } 21952 21953 foreach(name; __traits(derivedMembers, Iface)) { 21954 append(`__gshared typeof(&__traits(getMember, Iface, "`); 21955 append(name); 21956 append(`")) `); 21957 append(name); 21958 append(";"); 21959 } 21960 21961 return code[0 .. pos]; 21962 } 21963 21964 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 21965 mixin(staticForeachReplacement!Iface); 21966 21967 private __gshared void* libHandle; 21968 private __gshared bool attempted; 21969 21970 void loadDynamicLibrary() @nogc { 21971 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 21972 } 21973 21974 bool loadAttempted() { 21975 return attempted; 21976 } 21977 bool loadSuccessful() { 21978 return libHandle !is null; 21979 } 21980 21981 void loadDynamicLibraryForReal() { 21982 attempted = true; 21983 version(Posix) { 21984 import core.sys.posix.dlfcn; 21985 version(OSX) { 21986 version(X11) 21987 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 21988 else 21989 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 21990 } else { 21991 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 21992 if(libHandle is null) 21993 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 21994 } 21995 21996 static void* loadsym(void* l, const char* name) { 21997 import core.stdc.stdlib; 21998 if(l is null) 21999 return &abort; 22000 return dlsym(l, name); 22001 } 22002 } else version(Windows) { 22003 import core.sys.windows.winbase; 22004 libHandle = LoadLibrary(library ~ ".dll"); 22005 static void* loadsym(void* l, const char* name) { 22006 import core.stdc.stdlib; 22007 if(l is null) 22008 return &abort; 22009 return GetProcAddress(l, name); 22010 } 22011 } 22012 if(libHandle is null) { 22013 success = false; 22014 //throw new Exception("load failure of library " ~ library); 22015 } 22016 foreach(name; __traits(derivedMembers, Iface)) { 22017 mixin("alias tmp = " ~ name ~ ";"); 22018 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 22019 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 22020 } 22021 } 22022 22023 void unloadDynamicLibrary() { 22024 version(Posix) { 22025 import core.sys.posix.dlfcn; 22026 dlclose(libHandle); 22027 } else version(Windows) { 22028 import core.sys.windows.winbase; 22029 FreeLibrary(libHandle); 22030 } 22031 foreach(name; __traits(derivedMembers, Iface)) 22032 mixin(name ~ " = null;"); 22033 } 22034 } 22035 22036 /+ 22037 The GC can be called from any thread, and a lot of cleanup must be done 22038 on the gui thread. Since the GC can interrupt any locks - including being 22039 triggered inside a critical section - it is vital to avoid deadlocks to get 22040 these functions called from the right place. 22041 22042 If the buffer overflows, things are going to get leaked. I'm kinda ok with that 22043 right now. 22044 22045 The cleanup function is run when the event loop gets around to it, which is just 22046 whenever there's something there after it has been woken up for other work. It does 22047 NOT wake up the loop itself - can't risk doing that from inside the GC in another thread. 22048 (Well actually it might be ok but i don't wanna mess with it right now.) 22049 +/ 22050 private struct CleanupQueue { 22051 import core.stdc.stdlib; 22052 22053 void queue(alias func, T...)(T args) { 22054 static struct Args { 22055 T args; 22056 } 22057 static struct RealJob { 22058 Job j; 22059 Args a; 22060 } 22061 static void call(Job* data) { 22062 auto rj = cast(RealJob*) data; 22063 func(rj.a.args); 22064 } 22065 22066 RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof); 22067 thing.j.call = &call; 22068 thing.a.args = args; 22069 22070 buffer[tail++] = cast(Job*) thing; 22071 22072 // FIXME: set overflowed 22073 } 22074 22075 void process() { 22076 const tail = this.tail; 22077 22078 while(tail != head) { 22079 Job* job = cast(Job*) buffer[head++]; 22080 job.call(job); 22081 free(job); 22082 } 22083 22084 if(overflowed) 22085 throw new Exception("cleanup overflowed"); 22086 } 22087 22088 private: 22089 22090 ubyte tail; // must ONLY be written by queue 22091 ubyte head; // must ONLY be written by process 22092 bool overflowed; 22093 22094 static struct Job { 22095 void function(Job*) call; 22096 } 22097 22098 void*[256] buffer; 22099 } 22100 private __gshared CleanupQueue cleanupQueue; 22101 22102 version(X11) 22103 /++ 22104 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 22105 22106 $(WARNING 22107 This function is exempted from stability guarantees. 22108 ) 22109 +/ 22110 float customScalingFactorForMonitor(int monitorNumber) { 22111 import core.stdc.stdlib; 22112 auto val = getenv("ARSD_SCALING_FACTOR"); 22113 22114 if(val is null) 22115 return 1.0; 22116 22117 char[16] buffer = 0; 22118 int pos; 22119 22120 const(char)* at = val; 22121 22122 foreach(item; 0 .. monitorNumber + 1) { 22123 if(*at == 0) 22124 break; // reuse the last number when we at the end of the string 22125 pos = 0; 22126 while(pos + 1 < buffer.length && *at && *at != ';') { 22127 buffer[pos++] = *at; 22128 at++; 22129 } 22130 if(*at) 22131 at++; // skip the semicolon 22132 buffer[pos] = 0; 22133 } 22134 22135 //sdpyPrintDebugString(buffer[0 .. pos]); 22136 22137 import core.stdc.math; 22138 auto f = atof(buffer.ptr); 22139 22140 if(f <= 0.0 || isnan(f) || isinf(f)) 22141 return 1.0; 22142 22143 return f; 22144 } 22145 22146 void guiAbortProcess(string msg) { 22147 import core.stdc.stdlib; 22148 version(Windows) { 22149 WCharzBuffer t = WCharzBuffer(msg); 22150 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 22151 } else { 22152 import core.stdc.stdio; 22153 fwrite(msg.ptr, 1, msg.length, stderr); 22154 msg = "\n"; 22155 fwrite(msg.ptr, 1, msg.length, stderr); 22156 fflush(stderr); 22157 } 22158 22159 abort(); 22160 } 22161 22162 private int minInternal(int a, int b) { 22163 return (a < b) ? a : b; 22164 } 22165 22166 private alias scriptable = arsd_jsvar_compatible;