1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 // Search for: FIXME: leaks if multithreaded gc 4 5 // https://freedesktop.org/wiki/Specifications/XDND/ 6 7 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format 8 9 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html 10 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html 11 12 13 // on Mac with X11: -L-L/usr/X11/lib 14 15 /+ 16 17 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works 18 19 Progress bar in taskbar 20 - i can probably just set a property on the window... 21 it sets that prop to an integer 0 .. 100. Taskbar 22 deletes it or window deletes it when it is handled. 23 - prolly display it as a nice little line at the bottom. 24 25 26 from gtk: 27 28 #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" 29 #define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" 30 31 >+ if (cardinal > 0) 32 >+ { 33 >+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display), 34 >+ xid, 35 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name), 36 >+ XA_CARDINAL, 32, 37 >+ PropModeReplace, 38 >+ (guchar *) &cardinal, 1); 39 >+ } 40 >+ else 41 >+ { 42 >+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), 43 >+ xid, 44 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name)); 45 >+ } 46 47 from Windows: 48 49 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3 50 51 interface 52 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 53 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 54 listen for msg, return TRUE 55 interface->SetProgressState(hwnd, TBPF_NORMAL); 56 interface->SetProgressValue(hwnd, 40, 100); 57 58 59 My new notification system. 60 - use a unix socket? or a x property? or a udp port? 61 - could of course also get on the dbus train but ugh. 62 - it could also reply with the info as a string for easy remote examination. 63 64 +/ 65 66 /* 67 Event Loop would be nices: 68 69 * add on idle - runs when nothing else happens 70 * which can specify how long to yield for 71 * send messages without a recipient window 72 * setTimeout 73 * setInterval 74 */ 75 76 /* 77 Classic games I want to add: 78 * my tetris clone 79 * pac man 80 */ 81 82 /* 83 Text layout needs a lot of work. Plain drawText is useful but too 84 limited. It will need some kind of text context thing which it will 85 update and you can pass it on and get more details out of it. 86 87 It will need a bounding box, a current cursor location that is updated 88 as drawing continues, and various changable facts (which can also be 89 changed on the painter i guess) like font, color, size, background, 90 etc. 91 92 We can also fetch the caret location from it somehow. 93 94 Should prolly be an overload of drawText 95 96 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 97 98 WS_EX_NOACTIVATE 99 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 100 full screen windows. Can just set the atom on X. Windows will be harder. 101 102 moving windows. resizing windows. 103 104 hide cursor, capture cursor, change cursor. 105 106 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 107 sure the pieces are there to do its job easily and make other jobs possible. 108 */ 109 110 /++ 111 simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality, 112 including creating windows, drawing on them, working with the clipboard, 113 timers, OpenGL, and more. However, it does NOT provide high level GUI 114 widgets. See my minigui.d, an extension to this module, for that 115 functionality. 116 117 simpledisplay provides cross-platform wrapping for Windows and Linux 118 (and perhaps other OSes that use X11), but also does not prevent you 119 from using the underlying facilities if you need them. It has a goal 120 of working efficiently over a remote X link (at least as far as Xlib 121 reasonably allows.) 122 123 simpledisplay depends on [arsd.color|color.d], which should be available from the 124 same place where you got this file. Other than that, however, it has 125 very few dependencies and ones that don't come with the OS and/or the 126 compiler are all opt-in. 127 128 simpledisplay.d's home base is on my arsd repo on Github. The file is: 129 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 130 131 simpledisplay is basically stable. I plan to refactor the internals, 132 and may add new features and fix bugs, but It do not expect to 133 significantly change the API. It has been stable a few years already now. 134 135 Installation_instructions: 136 137 `simpledisplay.d` does not have any dependencies outside the 138 operating system and `color.d`, so it should just work most the 139 time, but there are a few caveats on some systems: 140 141 On Win32, you can pass `-L/subsystem:windows` if you don't want a 142 console to be automatically allocated. 143 144 Please note when compiling on Win64, you need to explicitly list 145 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 146 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 147 148 If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`; 149 note the "w". 150 151 I provided a `mixin EnableWindowsSubsystem;` helper to do those linker flags for you, 152 but you still need to use dmd -m32mscoff or -m64 (which dub does by default too fyi). 153 See [EnableWindowsSubsystem] for more information. 154 155 On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command. 156 157 On Ubuntu, you might need to install X11 development libraries to 158 successfully link. 159 160 $(CONSOLE 161 $ sudo apt-get install libglc-dev 162 $ sudo apt-get install libx11-dev 163 ) 164 165 166 Jump_list: 167 168 Don't worry, you don't have to read this whole documentation file! 169 170 Check out the [#event-example] and [#Pong-example] to get started quickly. 171 172 The main classes you may want to create are [SimpleWindow], [Timer], 173 [Image], and [Sprite]. 174 175 The main functions you'll want are [setClipboardText] and [getClipboardText]. 176 177 There are also platform-specific functions available such as [XDisplayConnection] 178 and [GetAtom] for X11, among others. 179 180 See the examples and topics list below to learn more. 181 182 $(WARNING 183 There should only be one GUI thread per application, 184 and all windows should be created in it and your 185 event loop should run there. 186 187 To do otherwise is undefined behavior and has no 188 cross platform guarantees. 189 ) 190 191 $(H2 About this documentation) 192 193 The goal here is to give some complete programs as overview examples first, then a look at each major feature with working examples first, then, finally, the inline class and method list will follow. 194 195 Scan for headers for a topic - $(B they will visually stand out) - you're interested in to get started quickly and feel free to copy and paste any example as a starting point for your program. I encourage you to learn the library by experimenting with the examples! 196 197 All examples are provided with no copyright restrictions whatsoever. You do not need to credit me or carry any kind of notice with the source if you copy and paste from them. 198 199 To get started, download `simpledisplay.d` and `color.d` to a working directory. Copy an example info a file called `example.d` and compile using the command given at the top of each example. 200 201 If you need help, email me: destructionator@gmail.com or IRC us, #d on Freenode (I am destructionator or adam_d_ruppe there). If you learn something that isn't documented, I appreciate pull requests on github to this file. 202 203 At points, I will talk about implementation details in the documentation. These are sometimes 204 subject to change, but nevertheless useful to understand what is really going on. You can learn 205 more about some of the referenced things by searching the web for info about using them from C. 206 You can always look at the source of simpledisplay.d too for the most authoritative source on 207 its specific implementation. If you disagree with how I did something, please contact me so we 208 can discuss it! 209 210 $(H2 Using with fibers) 211 212 simpledisplay can be used with [core.thread.Fiber], but be warned many of the functions can use a significant amount of stack space. I recommend at least 64 KB stack for each fiber (just set through the second argument to Fiber's constructor). 213 214 $(H2 Topics) 215 216 $(H3 $(ID topic-windows) Windows) 217 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 218 window on the user's screen. 219 220 You may create multiple windows, if the underlying platform supports it. You may check 221 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 222 SimpleWindow's constructor at runtime to handle those cases. 223 224 A single running event loop will handle as many windows as needed. 225 226 $(H3 $(ID topic-event-loops) Event loops) 227 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 228 229 The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there: 230 231 --- 232 // dmd example.d simpledisplay.d color.d 233 import arsd.simpledisplay; 234 void main() { 235 auto window = new SimpleWindow(200, 200); 236 window.eventLoop(0, 237 delegate (dchar) { /* got a character key press */ } 238 ); 239 } 240 --- 241 242 $(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.) 243 244 On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run. 245 246 On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 247 248 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 249 250 You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatically terminate when there are no open windows, you will want to have one anyway. 251 252 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 253 Notification area icons are currently implemented on X11 and Windows. On X11, it defaults to using `libnotify` to show bubbles, if available, and will do a custom bubble window if not. You can `version=without_libnotify` to avoid this run-time dependency, if you like. 254 255 See the [NotificationAreaIcon] class. 256 257 $(H3 $(ID topic-input-handling) Input handling) 258 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 259 260 See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent]. 261 262 $(H3 $(ID topic-2d-drawing) 2d Drawing) 263 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 264 265 Important: `ScreenPainter` double-buffers and will not actually update the window until its destructor is run. Always ensure the painter instance goes out-of-scope before proceeding. You can do this by calling it inside an event handler, a timer callback, or an small scope inside main. For example: 266 267 --- 268 // dmd example.d simpledisplay.d color.d 269 import arsd.simpledisplay; 270 void main() { 271 auto window = new SimpleWindow(200, 200); 272 { // introduce sub-scope 273 auto painter = window.draw(); // begin drawing 274 /* draw here */ 275 painter.outlineColor = Color.red; 276 painter.fillColor = Color.black; 277 painter.drawRectangle(Point(0, 0), 200, 200); 278 } // end scope, calling `painter`'s destructor, drawing to the screen. 279 window.eventLoop(0); // handle events 280 } 281 --- 282 283 Painting is done based on two color properties, a pen and a brush. 284 285 At this time, the 2d drawing does not support alpha blending, except for the [Sprite] class. If you need that, use a 2d OpenGL context instead. 286 287 FIXME Add example of 2d opengl drawing here. 288 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 289 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 290 291 Note that it is still possible to draw 2d on top of an OpenGL window, using the `draw` method, though I don't recommend it. 292 293 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 294 295 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 296 297 To force a redraw of the scene, call [SimpleWindow.redrawOpenGlSceneNow|window.redrawOpenGlSceneNow()] or to queue a redraw after processing the next batch of pending events, use [SimpleWindow.redrawOpenGlSceneSoon|window.redrawOpenGlSceneSoon]. 298 299 simpledisplay supports both old-style `glBegin` and newer-style shader-based code all through its built-in bindings. See the next section of the docs to see a shader-based program. 300 301 This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color: 302 303 --- 304 import arsd.simpledisplay; 305 306 void main() { 307 auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing); 308 309 float otherColor = 0.0; 310 float colorDelta = 0.05; 311 312 window.redrawOpenGlScene = delegate() { 313 glLoadIdentity(); 314 glBegin(GL_QUADS); 315 316 glColor3f(1.0, otherColor, 0); 317 glVertex3f(-0.8, -0.8, 0); 318 319 glColor3f(1.0, otherColor, 1.0); 320 glVertex3f(0.8, -0.8, 0); 321 322 glColor3f(0, 1.0, otherColor); 323 glVertex3f(0.8, 0.8, 0); 324 325 glColor3f(otherColor, 0, 1.0); 326 glVertex3f(-0.8, 0.8, 0); 327 328 glEnd(); 329 }; 330 331 window.eventLoop(50, () { 332 otherColor += colorDelta; 333 if(otherColor > 1.0) { 334 otherColor = 1.0; 335 colorDelta = -0.05; 336 } 337 if(otherColor < 0) { 338 otherColor = 0; 339 colorDelta = 0.05; 340 } 341 // at the end of the timer, we have to request a redraw 342 // or we won't see the changes. 343 window.redrawOpenGlSceneSoon(); 344 }); 345 } 346 --- 347 348 My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow]. 349 $(H3 $(ID topic-modern-opengl) Modern OpenGL) 350 simpledisplay's opengl support, by default, is for "legacy" opengl. To use "modern" functions, you must opt-into them with a little more setup. But the library provides helpers for this too. 351 352 This example program shows how you can set up a shader to draw a rectangle: 353 354 --- 355 module opengl3test; 356 import arsd.simpledisplay; 357 358 // based on https://learnopengl.com/Getting-started/Hello-Triangle 359 360 void main() { 361 // First thing we do, before creating the window, is declare what version we want. 362 setOpenGLContextVersion(3, 3); 363 // turning off legacy compat is required to use version 3.3 and newer 364 openGLContextCompatible = false; 365 366 uint VAO; 367 OpenGlShader shader; 368 369 // then we can create the window. 370 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 371 372 // additional setup needs to be done when it is visible, simpledisplay offers a property 373 // for exactly that: 374 window.visibleForTheFirstTime = delegate() { 375 // now with the window loaded, we can start loading the modern opengl functions. 376 377 // you MUST set the context first. 378 window.setAsCurrentOpenGlContext; 379 // then load the remainder of the library 380 gl3.loadDynamicLibrary(); 381 382 // now you can create the shaders, etc. 383 shader = new OpenGlShader( 384 OpenGlShader.Source(GL_VERTEX_SHADER, ` 385 #version 330 core 386 layout (location = 0) in vec3 aPos; 387 void main() { 388 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 389 } 390 `), 391 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 392 #version 330 core 393 out vec4 FragColor; 394 uniform vec4 mycolor; 395 void main() { 396 FragColor = mycolor; 397 } 398 `), 399 ); 400 401 // and do whatever other setup you want. 402 403 float[] vertices = [ 404 0.5f, 0.5f, 0.0f, // top right 405 0.5f, -0.5f, 0.0f, // bottom right 406 -0.5f, -0.5f, 0.0f, // bottom left 407 -0.5f, 0.5f, 0.0f // top left 408 ]; 409 uint[] indices = [ // note that we start from 0! 410 0, 1, 3, // first Triangle 411 1, 2, 3 // second Triangle 412 ]; 413 uint VBO, EBO; 414 glGenVertexArrays(1, &VAO); 415 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 416 glBindVertexArray(VAO); 417 418 glGenBuffers(1, &VBO); 419 glGenBuffers(1, &EBO); 420 421 glBindBuffer(GL_ARRAY_BUFFER, VBO); 422 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 423 424 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 425 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 426 427 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 428 glEnableVertexAttribArray(0); 429 430 // the library will set the initial viewport and trigger our first draw, 431 // so these next two lines are NOT needed. they are just here as comments 432 // to show what would happen next. 433 434 // glViewport(0, 0, window.width, window.height); 435 // window.redrawOpenGlSceneNow(); 436 }; 437 438 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 439 // it is our render method. 440 window.redrawOpenGlScene = delegate() { 441 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 442 glClear(GL_COLOR_BUFFER_BIT); 443 444 glUseProgram(shader.shaderProgram); 445 446 // the shader helper class has methods to set uniforms too 447 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 448 449 glBindVertexArray(VAO); 450 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 451 }; 452 453 window.eventLoop(0); 454 } 455 --- 456 457 This program only draws the image once because that's all that is necessary, since it is static. If you want to do animation, you might set a pulse timer (which would be a fixed max fps, not necessarily consistent) or use a render loop in a separate thread. 458 459 460 $(H3 $(ID topic-images) Displaying images) 461 You can also load PNG images using [arsd.png]. 462 463 --- 464 // dmd example.d simpledisplay.d color.d png.d 465 import arsd.simpledisplay; 466 import arsd.png; 467 468 void main() { 469 auto image = Image.fromMemoryImage(readPng("image.png")); 470 displayImage(image); 471 } 472 --- 473 474 Compile with `dmd example.d simpledisplay.d png.d`. 475 476 If you find an image file which is a valid png that [arsd.png] fails to load, please let me know. In the mean time of fixing the bug, you can probably convert the file into an easier-to-load format. Be sure to turn OFF png interlacing, as that isn't supported. Other things to try would be making the image smaller, or trying 24 bit truecolor mode with an alpha channel. 477 478 $(H3 $(ID topic-sprites) Sprites) 479 The [Sprite] class is used to make images on the display server for fast blitting to screen. This is especially important to use to support fast drawing of repeated images on a remote X11 link. 480 481 [Sprite] is also the only facility that currently supports alpha blending without using OpenGL . 482 483 $(H3 $(ID topic-clipboard) Clipboard) 484 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 485 486 It also has helpers for handling X-specific events. 487 488 $(H3 $(ID topic-dnd) Drag and Drop) 489 See [enableDragAndDrop] and [draggable]. 490 491 $(H3 $(ID topic-timers) Timers) 492 There are two timers in simpledisplay: one is the pulse timeout you can set on the call to `window.eventLoop`, and the other is a customizable class, [Timer]. 493 494 The pulse timeout is used by setting a non-zero interval as the first argument to `eventLoop` function and adding a zero-argument delegate to handle the pulse. 495 496 --- 497 import arsd.simpledisplay; 498 499 void main() { 500 auto window = new SimpleWindow(400, 400); 501 // every 100 ms, it will draw a random line 502 // on the window. 503 window.eventLoop(100, { 504 auto painter = window.draw(); 505 506 import std.random; 507 // random color 508 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 509 // random line 510 painter.drawLine( 511 Point(uniform(0, window.width), uniform(0, window.height)), 512 Point(uniform(0, window.width), uniform(0, window.height))); 513 514 }); 515 } 516 --- 517 518 The `Timer` class works similarly, but is created separately from the event loop. (It still fires through the event loop, though.) You may make as many instances of `Timer` as you wish. 519 520 The pulse timer and instances of the [Timer] class may be combined at will. 521 522 --- 523 import arsd.simpledisplay; 524 525 void main() { 526 auto window = new SimpleWindow(400, 400); 527 auto timer = new Timer(1000, delegate { 528 auto painter = window.draw(); 529 painter.clear(); 530 }); 531 532 window.eventLoop(0); 533 } 534 --- 535 536 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 537 538 $(H3 $(ID topic-os-helpers) OS-specific helpers) 539 simpledisplay carries a lot of code to help implement itself without extra dependencies, and much of this code is available for you too, so you may extend the functionality yourself. 540 541 See also: `xwindows.d` from my github. 542 543 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 544 `handleNativeEvent` and `handleNativeGlobalEvent`. 545 546 $(H3 $(ID topic-integration) Integration with other libraries) 547 Integration with a third-party event loop is possible. 548 549 On Linux, you might want to support both terminal input and GUI input. You can do this by using simpledisplay together with eventloop.d and terminal.d. 550 551 $(H3 $(ID topic-guis) GUI widgets) 552 simpledisplay does not provide GUI widgets such as text areas, buttons, checkboxes, etc. It only gives basic windows, the ability to draw on it, receive input from it, and access native information for extension. You may write your own gui widgets with these, but you don't have to because I already did for you! 553 554 Download `minigui.d` from my github repository and add it to your project. minigui builds these things on top of simpledisplay and offers its own Window class (and subclasses) to use that wrap SimpleWindow, adding a new event and drawing model that is hookable by subwidgets, represented by their own classes. 555 556 Migrating to minigui from simpledisplay is often easy though, because they both use the same ScreenPainter API, and the same simpledisplay events are available, if you want them. (Though you may like using the minigui model, especially if you are familiar with writing web apps in the browser with Javascript.) 557 558 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 559 560 $(H2 Platform-specific tips and tricks) 561 562 X_tips: 563 564 On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release. 565 566 Windows_tips: 567 568 You can add icons or manifest files to your exe using a resource file. 569 570 To create a Windows .ico file, use the gimp or something. I'll write a helper 571 program later. 572 573 Create `yourapp.rc`: 574 575 ```rc 576 1 ICON filename.ico 577 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 578 ``` 579 580 And `yourapp.exe.manifest`: 581 582 ```xml 583 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 584 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 585 <assemblyIdentity 586 version="1.0.0.0" 587 processorArchitecture="*" 588 name="CompanyName.ProductName.YourApplication" 589 type="win32" 590 /> 591 <description>Your application description here.</description> 592 <dependency> 593 <dependentAssembly> 594 <assemblyIdentity 595 type="win32" 596 name="Microsoft.Windows.Common-Controls" 597 version="6.0.0.0" 598 processorArchitecture="*" 599 publicKeyToken="6595b64144ccf1df" 600 language="*" 601 /> 602 </dependentAssembly> 603 </dependency> 604 <application xmlns="urn:schemas-microsoft-com:asm.v3"> 605 <windowsSettings> 606 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- old style --> 607 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <!-- new style --> 608 <!-- Un-comment the line below to enable GDI-scaling in this project. This will enable text --> 609 <!-- to render crisply in DPI-unaware contexts --> 610 <!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>--> 611 </windowsSettings> 612 </application> 613 </assembly> 614 ``` 615 616 You can also just distribute yourapp.exe.manifest as a separate file alongside yourapp.exe, or link it in to the exe with linker command lines `/manifest:embed` and `/manifestinput:yourfile.exe.manifest`. 617 618 Doing this lets you opt into various new things since Windows XP. 619 620 See: https://docs.microsoft.com/en-us/windows/win32/SbsCs/application-manifests 621 622 $(H2 Tips) 623 624 $(H3 Name conflicts) 625 626 simpledisplay has a lot of symbols and more are liable to be added without notice, since it contains its own bindings as needed to accomplish its goals. Some of these may conflict with other bindings you use. If so, you can use a static import in D, possibly combined with a selective import: 627 628 --- 629 static import sdpy = arsd.simpledisplay; 630 import arsd.simpledisplay : SimpleWindow; 631 632 void main() { 633 auto window = new SimpleWindow(); 634 sdpy.EventLoop.get.run(); 635 } 636 --- 637 638 $(H2 $(ID developer-notes) Developer notes) 639 640 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 641 implementation though. 642 643 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 644 suck. If I was rewriting it, I wouldn't do it that way again. 645 646 This file must not have any more required dependencies. If you need bindings, add 647 them right to this file. Once it gets into druntime and is there for a while, remove 648 bindings from here to avoid conflicts (or put them in an appropriate version block 649 so it continues to just work on old dmd), but wait a couple releases before making the 650 transition so this module remains usable with older versions of dmd. 651 652 You may have optional dependencies if needed by putting them in version blocks or 653 template functions. You may also extend the module with other modules with UFCS without 654 actually editing this - that is nice to do if you can. 655 656 Try to make functions work the same way across operating systems. I typically make 657 it thinly wrap Windows, then emulate that on Linux. 658 659 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 660 Phobos! So try to avoid it. 661 662 See more comments throughout the source. 663 664 I realize this file is fairly large, but over half that is just bindings at the bottom 665 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 666 to understand. I suggest you jump around the source by looking for a particular 667 declaration you're interested in, like `class SimpleWindow` using your editor's search 668 function, then look at one piece at a time. 669 670 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 671 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 672 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 673 674 I live in the eastern United States, so I will most likely not be around at night in 675 that US east timezone. 676 677 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 678 679 Building documentation: use my adrdox generator, `dub run adrdox`. 680 681 Examples: 682 683 $(DIV $(ID Event-example)) 684 $(H3 $(ID event-example) Event example) 685 This program creates a window and draws events inside them as they 686 happen, scrolling the text in the window as needed. Run this program 687 and experiment to get a feel for where basic input events take place 688 in the library. 689 690 --- 691 // dmd example.d simpledisplay.d color.d 692 import arsd.simpledisplay; 693 import std.conv; 694 695 void main() { 696 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 697 698 int y = 0; 699 700 void addLine(string text) { 701 auto painter = window.draw(); 702 703 if(y + painter.fontHeight >= window.height) { 704 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 705 y -= painter.fontHeight; 706 } 707 708 painter.outlineColor = Color.red; 709 painter.fillColor = Color.black; 710 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 711 712 painter.outlineColor = Color.white; 713 714 painter.drawText(Point(10, y), text); 715 716 y += painter.fontHeight; 717 } 718 719 window.eventLoop(1000, 720 () { 721 addLine("Timer went off!"); 722 }, 723 (KeyEvent event) { 724 addLine(to!string(event)); 725 }, 726 (MouseEvent event) { 727 addLine(to!string(event)); 728 }, 729 (dchar ch) { 730 addLine(to!string(ch)); 731 } 732 ); 733 } 734 --- 735 736 If you are interested in more game writing with D, check out my gamehelpers.d which builds upon simpledisplay, and its other stand-alone support modules, simpleaudio.d and joystick.d, too. 737 738 $(COMMENT 739 This program displays a pie chart. Clicking on a color will increase its share of the pie. 740 741 --- 742 743 --- 744 ) 745 746 747 +/ 748 module arsd.simpledisplay; 749 750 // FIXME: tetris demo 751 // FIXME: space invaders demo 752 // FIXME: asteroids demo 753 754 /++ $(ID Pong-example) 755 $(H3 Pong) 756 757 This program creates a little Pong-like game. Player one is controlled 758 with the keyboard. Player two is controlled with the mouse. It demos 759 the pulse timer, event handling, and some basic drawing. 760 +/ 761 version(demos) 762 unittest { 763 // dmd example.d simpledisplay.d color.d 764 import arsd.simpledisplay; 765 766 enum paddleMovementSpeed = 8; 767 enum paddleHeight = 48; 768 769 void main() { 770 auto window = new SimpleWindow(600, 400, "Pong game!"); 771 772 int playerOnePosition, playerTwoPosition; 773 int playerOneMovement, playerTwoMovement; 774 int playerOneScore, playerTwoScore; 775 776 int ballX, ballY; 777 int ballDx, ballDy; 778 779 void serve() { 780 import std.random; 781 782 ballX = window.width / 2; 783 ballY = window.height / 2; 784 ballDx = uniform(-4, 4) * 3; 785 ballDy = uniform(-4, 4) * 3; 786 if(ballDx == 0) 787 ballDx = uniform(0, 2) == 0 ? 3 : -3; 788 } 789 790 serve(); 791 792 window.eventLoop(50, // set a 50 ms timer pulls 793 // This runs once per timer pulse 794 delegate () { 795 auto painter = window.draw(); 796 797 painter.clear(); 798 799 // Update everyone's motion 800 playerOnePosition += playerOneMovement; 801 playerTwoPosition += playerTwoMovement; 802 803 ballX += ballDx; 804 ballY += ballDy; 805 806 // Bounce off the top and bottom edges of the window 807 if(ballY + 7 >= window.height) 808 ballDy = -ballDy; 809 if(ballY - 8 <= 0) 810 ballDy = -ballDy; 811 812 // Bounce off the paddle, if it is in position 813 if(ballX - 8 <= 16) { 814 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 815 ballDx = -ballDx + 1; // add some speed to keep it interesting 816 ballDy += playerOneMovement; // and y movement based on your controls too 817 ballX = 24; // move it past the paddle so it doesn't wiggle inside 818 } else { 819 // Missed it 820 playerTwoScore ++; 821 serve(); 822 } 823 } 824 825 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 826 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 827 ballDx = -ballDx - 1; 828 ballDy += playerTwoMovement; 829 ballX = window.width - 24; 830 } else { 831 // Missed it 832 playerOneScore ++; 833 serve(); 834 } 835 } 836 837 // Draw the paddles 838 painter.outlineColor = Color.black; 839 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 840 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 841 842 // Draw the ball 843 painter.fillColor = Color.red; 844 painter.outlineColor = Color.yellow; 845 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 846 847 // Draw the score 848 painter.outlineColor = Color.blue; 849 import std.conv; 850 painter.drawText(Point(64, 4), to!string(playerOneScore)); 851 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 852 853 }, 854 delegate (KeyEvent event) { 855 // Player 1's controls are the arrow keys on the keyboard 856 if(event.key == Key.Down) 857 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 858 if(event.key == Key.Up) 859 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 860 861 }, 862 delegate (MouseEvent event) { 863 // Player 2's controls are mouse movement while the left button is held down 864 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 865 if(event.dy > 0) 866 playerTwoMovement = paddleMovementSpeed; 867 else if(event.dy < 0) 868 playerTwoMovement = -paddleMovementSpeed; 869 } else { 870 playerTwoMovement = 0; 871 } 872 } 873 ); 874 } 875 } 876 877 /++ $(H3 $(ID example-minesweeper) Minesweeper) 878 879 This minesweeper demo shows how we can implement another classic 880 game with simpledisplay and shows some mouse input and basic output 881 code. 882 +/ 883 version(demos) 884 unittest { 885 import arsd.simpledisplay; 886 887 enum GameSquare { 888 mine = 0, 889 clear, 890 m1, m2, m3, m4, m5, m6, m7, m8 891 } 892 893 enum UserSquare { 894 unknown, 895 revealed, 896 flagged, 897 questioned 898 } 899 900 enum GameState { 901 inProgress, 902 lose, 903 win 904 } 905 906 GameSquare[] board; 907 UserSquare[] userState; 908 GameState gameState; 909 int boardWidth; 910 int boardHeight; 911 912 bool isMine(int x, int y) { 913 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 914 return false; 915 return board[y * boardWidth + x] == GameSquare.mine; 916 } 917 918 GameState reveal(int x, int y) { 919 if(board[y * boardWidth + x] == GameSquare.clear) { 920 floodFill(userState, boardWidth, boardHeight, 921 UserSquare.unknown, UserSquare.revealed, 922 x, y, 923 (x, y) { 924 if(board[y * boardWidth + x] == GameSquare.clear) 925 return true; 926 else { 927 userState[y * boardWidth + x] = UserSquare.revealed; 928 return false; 929 } 930 }); 931 } else { 932 userState[y * boardWidth + x] = UserSquare.revealed; 933 if(isMine(x, y)) 934 return GameState.lose; 935 } 936 937 foreach(state; userState) { 938 if(state == UserSquare.unknown || state == UserSquare.questioned) 939 return GameState.inProgress; 940 } 941 942 return GameState.win; 943 } 944 945 void initializeBoard(int width, int height, int numberOfMines) { 946 boardWidth = width; 947 boardHeight = height; 948 board.length = width * height; 949 950 userState.length = width * height; 951 userState[] = UserSquare.unknown; 952 953 import std.algorithm, std.random, std.range; 954 955 board[] = GameSquare.clear; 956 957 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 958 board[minePosition] = GameSquare.mine; 959 960 int x; 961 int y; 962 foreach(idx, ref square; board) { 963 if(square == GameSquare.clear) { 964 int danger = 0; 965 danger += isMine(x-1, y-1)?1:0; 966 danger += isMine(x-1, y)?1:0; 967 danger += isMine(x-1, y+1)?1:0; 968 danger += isMine(x, y-1)?1:0; 969 danger += isMine(x, y+1)?1:0; 970 danger += isMine(x+1, y-1)?1:0; 971 danger += isMine(x+1, y)?1:0; 972 danger += isMine(x+1, y+1)?1:0; 973 974 square = cast(GameSquare) (danger + 1); 975 } 976 977 x++; 978 if(x == width) { 979 x = 0; 980 y++; 981 } 982 } 983 } 984 985 void redraw(SimpleWindow window) { 986 import std.conv; 987 988 auto painter = window.draw(); 989 990 painter.clear(); 991 992 final switch(gameState) with(GameState) { 993 case inProgress: 994 break; 995 case win: 996 painter.fillColor = Color.green; 997 painter.drawRectangle(Point(0, 0), window.width, window.height); 998 return; 999 case lose: 1000 painter.fillColor = Color.red; 1001 painter.drawRectangle(Point(0, 0), window.width, window.height); 1002 return; 1003 } 1004 1005 int x = 0; 1006 int y = 0; 1007 1008 foreach(idx, square; board) { 1009 auto state = userState[idx]; 1010 1011 final switch(state) with(UserSquare) { 1012 case unknown: 1013 painter.outlineColor = Color.black; 1014 painter.fillColor = Color(128,128,128); 1015 1016 painter.drawRectangle( 1017 Point(x * 20, y * 20), 1018 20, 20 1019 ); 1020 break; 1021 case revealed: 1022 if(square == GameSquare.clear) { 1023 painter.outlineColor = Color.white; 1024 painter.fillColor = Color.white; 1025 1026 painter.drawRectangle( 1027 Point(x * 20, y * 20), 1028 20, 20 1029 ); 1030 } else { 1031 painter.outlineColor = Color.black; 1032 painter.fillColor = Color.white; 1033 1034 painter.drawText( 1035 Point(x * 20, y * 20), 1036 to!string(square)[1..2], 1037 Point(x * 20 + 20, y * 20 + 20), 1038 TextAlignment.Center | TextAlignment.VerticalCenter); 1039 } 1040 break; 1041 case flagged: 1042 painter.outlineColor = Color.black; 1043 painter.fillColor = Color.red; 1044 painter.drawRectangle( 1045 Point(x * 20, y * 20), 1046 20, 20 1047 ); 1048 break; 1049 case questioned: 1050 painter.outlineColor = Color.black; 1051 painter.fillColor = Color.yellow; 1052 painter.drawRectangle( 1053 Point(x * 20, y * 20), 1054 20, 20 1055 ); 1056 break; 1057 } 1058 1059 x++; 1060 if(x == boardWidth) { 1061 x = 0; 1062 y++; 1063 } 1064 } 1065 1066 } 1067 1068 void main() { 1069 auto window = new SimpleWindow(200, 200); 1070 1071 initializeBoard(10, 10, 10); 1072 1073 redraw(window); 1074 window.eventLoop(0, 1075 delegate (MouseEvent me) { 1076 if(me.type != MouseEventType.buttonPressed) 1077 return; 1078 auto x = me.x / 20; 1079 auto y = me.y / 20; 1080 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1081 if(me.button == MouseButton.left) { 1082 gameState = reveal(x, y); 1083 } else { 1084 userState[y*boardWidth+x] = UserSquare.flagged; 1085 } 1086 redraw(window); 1087 } 1088 } 1089 ); 1090 } 1091 } 1092 1093 /* 1094 version(OSX) { 1095 version=without_opengl; 1096 version=allow_unimplemented_features; 1097 version=OSXCocoa; 1098 pragma(linkerDirective, "-framework Cocoa"); 1099 } 1100 */ 1101 1102 version(without_opengl) { 1103 enum SdpyIsUsingIVGLBinds = false; 1104 } else /*version(Posix)*/ { 1105 static if (__traits(compiles, (){import iv.glbinds;})) { 1106 enum SdpyIsUsingIVGLBinds = true; 1107 public import iv.glbinds; 1108 //pragma(msg, "SDPY: using iv.glbinds"); 1109 } else { 1110 enum SdpyIsUsingIVGLBinds = false; 1111 } 1112 //} else { 1113 // enum SdpyIsUsingIVGLBinds = false; 1114 } 1115 1116 1117 version(Windows) { 1118 //import core.sys.windows.windows; 1119 import core.sys.windows.winnls; 1120 import core.sys.windows.windef; 1121 import core.sys.windows.basetyps; 1122 import core.sys.windows.winbase; 1123 import core.sys.windows.winuser; 1124 import core.sys.windows.shellapi; 1125 import core.sys.windows.wingdi; 1126 static import gdi = core.sys.windows.wingdi; // so i 1127 1128 pragma(lib, "gdi32"); 1129 pragma(lib, "user32"); 1130 1131 // for AlphaBlend... a breaking change.... 1132 version(CRuntime_DigitalMars) { } else 1133 pragma(lib, "msimg32"); 1134 } else version (linux) { 1135 //k8: this is hack for rdmd. sorry. 1136 static import core.sys.linux.epoll; 1137 static import core.sys.linux.timerfd; 1138 } 1139 1140 1141 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1142 1143 // http://wiki.dlang.org/Simpledisplay.d 1144 1145 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1146 1147 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1148 // but can i control the scroll lock led 1149 1150 1151 // Note: if you are using Image on X, you might want to do: 1152 /* 1153 static if(UsingSimpledisplayX11) { 1154 if(!Image.impl.xshmAvailable) { 1155 // the images will use the slower XPutImage, you might 1156 // want to consider an alternative method to get better speed 1157 } 1158 } 1159 1160 If the shared memory extension is available though, simpledisplay uses it 1161 for a significant speed boost whenever you draw large Images. 1162 */ 1163 1164 // CHANGE FROM LAST VERSION: the window background is no longer fixed, so you might want to fill the screen with a particular color before drawing. 1165 1166 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1167 1168 /* 1169 Biggest FIXME: 1170 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1171 1172 clean up opengl contexts when their windows close 1173 1174 fix resizing the bitmaps/pixmaps 1175 */ 1176 1177 // BTW on Windows: 1178 // -L/SUBSYSTEM:WINDOWS:5.0 1179 // to dmd will make a nice windows binary w/o a console if you want that. 1180 1181 /* 1182 Stuff to add: 1183 1184 use multibyte functions everywhere we can 1185 1186 OpenGL windows 1187 more event stuff 1188 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1189 1190 1191 resizeEvent 1192 and make the windows non-resizable by default, 1193 or perhaps stretched (if I can find something in X like StretchBlt) 1194 1195 take a screenshot function! 1196 1197 Pens and brushes? 1198 Maybe a global event loop? 1199 1200 Mouse deltas 1201 Key items 1202 */ 1203 1204 /* 1205 From MSDN: 1206 1207 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1208 1209 Important Do not use the LOWORD or HIWORD macros to extract the x- and y- coordinates of the cursor position because these macros return incorrect results on systems with multiple monitors. Systems with multiple monitors can have negative x- and y- coordinates, and LOWORD and HIWORD treat the coordinates as unsigned quantities. 1210 1211 */ 1212 1213 version(linux) { 1214 version = X11; 1215 version(without_libnotify) { 1216 // we cool 1217 } 1218 else 1219 version = libnotify; 1220 } 1221 1222 version(libnotify) { 1223 pragma(lib, "dl"); 1224 import core.sys.posix.dlfcn; 1225 1226 void delegate()[int] libnotify_action_delegates; 1227 int libnotify_action_delegates_count; 1228 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1229 auto idx = cast(int) user_data; 1230 if(auto dgptr = idx in libnotify_action_delegates) { 1231 (*dgptr)(); 1232 libnotify_action_delegates.remove(idx); 1233 } 1234 } 1235 1236 struct C_DynamicLibrary { 1237 void* handle; 1238 this(string name) { 1239 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1240 if(handle is null) 1241 throw new Exception("dlopen"); 1242 } 1243 1244 void close() { 1245 dlclose(handle); 1246 } 1247 1248 ~this() { 1249 // close 1250 } 1251 1252 // FIXME: this looks up by name every time.... 1253 template call(string func, Ret, Args...) { 1254 extern(C) Ret function(Args) fptr; 1255 typeof(fptr) call() { 1256 fptr = cast(typeof(fptr)) dlsym(handle, func); 1257 return fptr; 1258 } 1259 } 1260 } 1261 1262 C_DynamicLibrary* libnotify; 1263 } 1264 1265 version(OSX) { 1266 version(OSXCocoa) {} 1267 else { version = X11; } 1268 } 1269 //version = OSXCocoa; // this was written by KennyTM 1270 version(FreeBSD) 1271 version = X11; 1272 version(Solaris) 1273 version = X11; 1274 1275 version(X11) { 1276 version(without_xft) {} 1277 else version=with_xft; 1278 } 1279 1280 void featureNotImplemented()() { 1281 version(allow_unimplemented_features) 1282 throw new NotYetImplementedException(); 1283 else 1284 static assert(0); 1285 } 1286 1287 // these are so the static asserts don't trigger unless you want to 1288 // add support to it for an OS 1289 version(Windows) 1290 version = with_timer; 1291 version(linux) 1292 version = with_timer; 1293 1294 version(with_timer) 1295 enum bool SimpledisplayTimerAvailable = true; 1296 else 1297 enum bool SimpledisplayTimerAvailable = false; 1298 1299 /// If you have to get down and dirty with implementation details, this helps figure out if Windows is available you can `static if(UsingSimpledisplayWindows) ...` more reliably than `version()` because `version` is module-local. 1300 version(Windows) 1301 enum bool UsingSimpledisplayWindows = true; 1302 else 1303 enum bool UsingSimpledisplayWindows = false; 1304 1305 /// If you have to get down and dirty with implementation details, this helps figure out if X is available you can `static if(UsingSimpledisplayX11) ...` more reliably than `version()` because `version` is module-local. 1306 version(X11) 1307 enum bool UsingSimpledisplayX11 = true; 1308 else 1309 enum bool UsingSimpledisplayX11 = false; 1310 1311 /// If you have to get down and dirty with implementation details, this helps figure out if Cocoa is available you can `static if(UsingSimpledisplayCocoa) ...` more reliably than `version()` because `version` is module-local. 1312 version(OSXCocoa) 1313 enum bool UsingSimpledisplayCocoa = true; 1314 else 1315 enum bool UsingSimpledisplayCocoa = false; 1316 1317 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1318 version(Windows) 1319 enum multipleWindowsSupported = true; 1320 else version(X11) 1321 enum multipleWindowsSupported = true; 1322 else version(OSXCocoa) 1323 enum multipleWindowsSupported = true; 1324 else 1325 static assert(0); 1326 1327 version(without_opengl) 1328 enum bool OpenGlEnabled = false; 1329 else 1330 enum bool OpenGlEnabled = true; 1331 1332 /++ 1333 Adds the necessary pragmas to your application to use the Windows gui subsystem. 1334 If you mix this in above your `main` function, you no longer need to use the linker 1335 flags explicitly. It does the necessary version blocks for various compilers and runtimes. 1336 1337 It does nothing if not compiling for Windows, so you need not version it out yourself. 1338 1339 Please note that Windows gui subsystem applications must NOT use std.stdio's stdout and 1340 stderr writeln. It will fail and throw an exception. 1341 1342 This will NOT work with plain `dmd` on Windows; you must use `dmd -m32mscoff` or `dmd -m64`. 1343 1344 History: 1345 Added November 24, 2021 (dub v10.4) 1346 +/ 1347 mixin template EnableWindowsSubsystem() { 1348 version(Windows) 1349 version(CRuntime_Microsoft) { 1350 pragma(linkerDirective, "/subsystem:windows"); 1351 version(LDC) 1352 pragma(linkerDirective, "/entry:wmainCRTStartup"); 1353 else 1354 pragma(linkerDirective, "/entry:mainCRTStartup"); 1355 } 1356 } 1357 1358 1359 /++ 1360 After selecting a type from [WindowTypes], you may further customize 1361 its behavior by setting one or more of these flags. 1362 1363 1364 The different window types have different meanings of `normal`. If the 1365 window type already is a good match for what you want to do, you should 1366 just use [WindowFlags.normal], the default, which will do the right thing 1367 for your users. 1368 1369 The window flags will not always be honored by the operating system 1370 and window managers; they are hints, not commands. 1371 +/ 1372 enum WindowFlags : int { 1373 normal = 0, /// 1374 skipTaskbar = 1, /// 1375 alwaysOnTop = 2, /// 1376 alwaysOnBottom = 4, /// 1377 cannotBeActivated = 8, /// 1378 alwaysRequestMouseMotionEvents = 16, /// By default, simpledisplay will attempt to optimize mouse motion event reporting when it detects a remote connection, causing them to only be issued if input is grabbed (see: [SimpleWindow.grabInput]). This means doing hover effects and mouse game control on a remote X connection may not work right. Include this flag to override this optimization and always request the motion events. However btw, if you are doing mouse game control, you probably want to grab input anyway, and hover events are usually expendable! So think before you use this flag. 1379 extraComposite = 32, /// On windows this will make this a layered windows (not supported for child windows before windows 8) to support transparency and improve animation performance. 1380 /++ 1381 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1382 it is still a top-level window. This should NOT be set separately for most window types. 1383 1384 A transient window will not keep the application open if its main window closes. 1385 1386 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1387 1388 1389 From the ICCM: 1390 1391 $(BLOCKQUOTE 1392 It is important not to confuse WM_TRANSIENT_FOR with override-redirect. WM_TRANSIENT_FOR should be used in those cases where the pointer is not grabbed while the window is mapped (in other words, if other windows are allowed to be active while the transient is up). If other windows must be prevented from processing input (for example, when implementing pop-up menus), use override-redirect and grab the pointer while the window is mapped. 1393 1394 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1395 ) 1396 1397 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1398 1399 History: 1400 Added February 23, 2021 but not yet stabilized. 1401 +/ 1402 transient = 64, 1403 /++ 1404 This indicates that the window manages its own platform-specific child window input focus. You must use a delegate, [SimpleWindow.setRequestedInputFocus], to set the input when requested. This delegate returns the handle to the window that should receive the focus. 1405 1406 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 1407 1408 History: 1409 Added April 1, 2022 1410 +/ 1411 managesChildWindowFocus = 128, 1412 1413 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1414 } 1415 1416 /++ 1417 When creating a window, you can pass a type to SimpleWindow's constructor, 1418 then further customize the window by changing `WindowFlags`. 1419 1420 1421 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1422 use. The others are there to build a foundation for a higher level GUI toolkit, 1423 but are themselves not as high level as you might think from their names. 1424 1425 This list is based on the EMWH spec for X11. 1426 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1427 +/ 1428 enum WindowTypes : int { 1429 /// An ordinary application window. 1430 normal, 1431 /// A generic window without a title bar or border. You can draw on the entire area of the screen it takes up and use it as you wish. Remember that users don't really expect these though, so don't use it where a window of any other type is appropriate. 1432 undecorated, 1433 /// A window that doesn't actually display on screen. You can use it for cases where you need a dummy window handle to communicate with or something. 1434 eventOnly, 1435 /// A drop down menu, such as from a menu bar 1436 dropdownMenu, 1437 /// A popup menu, such as from a right click 1438 popupMenu, 1439 /// A popup bubble notification 1440 notification, 1441 /* 1442 menu, /// a tearable menu bar 1443 splashScreen, /// a loading splash screen for your application 1444 tooltip, /// A tiny window showing temporary help text or something. 1445 comboBoxDropdown, 1446 dialog, 1447 toolbar 1448 */ 1449 /// a child nested inside the parent. You must pass a parent window to the ctor 1450 nestedChild, 1451 } 1452 1453 1454 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1455 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1456 private __gshared char* sdpyWindowClassStr = null; 1457 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1458 1459 /** 1460 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1461 You may want to change context version if you want to use advanced shaders or 1462 other modern OpenGL techinques. This setting doesn't affect already created 1463 windows. You may use version 2.1 as your default, which should be supported 1464 by any box since 2006, so seems to be a reasonable choice. 1465 1466 Note that by default version is set to `0`, which forces SimpleDisplay to use 1467 old context creation code without any version specified. This is the safest 1468 way to init OpenGL, but it may not give you access to advanced features. 1469 1470 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1471 */ 1472 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1473 1474 /** 1475 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1476 pipeline functions, and without "compatible" mode you won't be able to use 1477 your old non-shader-based code with such contexts. By default SimpleDisplay 1478 creates compatible context, so you can gradually upgrade your OpenGL code if 1479 you want to (or leave it as is, as it should "just work"). 1480 */ 1481 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1482 1483 /** 1484 Set to `true` to allow creating OpenGL context with lower version than requested 1485 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1486 `openGLContextFallbackActivated()` will return `true`. 1487 */ 1488 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1489 1490 /** 1491 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1492 */ 1493 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1494 1495 1496 /** 1497 Set window class name for all following `new SimpleWindow()` calls. 1498 1499 WARNING! For Windows, you should set your class name before creating any 1500 window, and NEVER change it after that! 1501 */ 1502 void sdpyWindowClass (const(char)[] v) { 1503 import core.stdc.stdlib : realloc; 1504 if (v.length == 0) v = "SimpleDisplayWindow"; 1505 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1506 if (sdpyWindowClassStr is null) return; // oops 1507 sdpyWindowClassStr[0..v.length+1] = 0; 1508 sdpyWindowClassStr[0..v.length] = v[]; 1509 } 1510 1511 /** 1512 Get current window class name. 1513 */ 1514 string sdpyWindowClass () { 1515 if (sdpyWindowClassStr is null) return null; 1516 foreach (immutable idx; 0..size_t.max-1) { 1517 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1518 } 1519 return null; 1520 } 1521 1522 /++ 1523 Returns the logical DPI of the default monitor. [0] is width, [1] is height (they are usually the same though). You may wish to round the numbers off. This isn't necessarily related to the physical side of the screen; it is associated with a user-defined scaling factor. 1524 1525 If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. 1526 +/ 1527 float[2] getDpi() { 1528 float[2] dpi; 1529 version(Windows) { 1530 HDC screen = GetDC(null); 1531 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1532 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1533 } else version(X11) { 1534 auto display = XDisplayConnection.get; 1535 auto screen = DefaultScreen(display); 1536 1537 void fallback() { 1538 /+ 1539 // 25.4 millimeters in an inch... 1540 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1541 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1542 +/ 1543 1544 // the physical size isn't actually as important as the logical size since this is 1545 // all about scaling really 1546 dpi[0] = 96; 1547 dpi[1] = 96; 1548 } 1549 1550 auto xft = getXftDpi(); 1551 if(xft is float.init) 1552 fallback(); 1553 else { 1554 dpi[0] = xft; 1555 dpi[1] = xft; 1556 } 1557 } 1558 1559 return dpi; 1560 } 1561 1562 version(X11) 1563 float getXftDpi() { 1564 auto display = XDisplayConnection.get; 1565 1566 char* resourceString = XResourceManagerString(display); 1567 XrmInitialize(); 1568 1569 if (resourceString) { 1570 auto db = XrmGetStringDatabase(resourceString); 1571 XrmValue value; 1572 char* type; 1573 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1574 if (value.addr) { 1575 import core.stdc.stdlib; 1576 return atof(cast(char*) value.addr); 1577 } 1578 } 1579 } 1580 1581 return float.init; 1582 } 1583 1584 /++ 1585 Implementation used by [SimpleWindow.takeScreenshot]. 1586 1587 Params: 1588 handle = the native window handle. If `NativeWindowHandle.init`, it will attempt to get the whole screen. 1589 width = the width of the image you wish to capture. If 0, it will attempt to capture the full width of the target. 1590 height = the height of the image you wish to capture. If 0, it will attempt to capture the full height of the target. 1591 x = the x-offset of the image to capture, from the left. 1592 y = the y-offset of the image to capture, from the top. 1593 1594 History: 1595 Added on March 14, 2021 1596 1597 Documented public on September 23, 2021 with full support for null params (dub 10.3) 1598 1599 +/ 1600 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width = 0, int height = 0, int x = 0, int y = 0) { 1601 TrueColorImage got; 1602 version(X11) { 1603 auto display = XDisplayConnection.get; 1604 if(handle == 0) 1605 handle = RootWindow(display, DefaultScreen(display)); 1606 1607 if(width == 0 || height == 0) { 1608 Window root; 1609 int xpos, ypos; 1610 uint widthret, heightret, borderret, depthret; 1611 XGetGeometry(display, handle, &root, &xpos, &ypos, &widthret, &heightret, &borderret, &depthret); 1612 1613 if(width == 0) 1614 width = widthret; 1615 if(height == 0) 1616 height = heightret; 1617 } 1618 1619 auto image = XGetImage(display, handle, x, y, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1620 1621 // https://github.com/adamdruppe/arsd/issues/98 1622 1623 auto i = new Image(image); 1624 got = i.toTrueColorImage(); 1625 1626 XDestroyImage(image); 1627 } else version(Windows) { 1628 auto hdc = GetDC(handle); 1629 scope(exit) ReleaseDC(handle, hdc); 1630 1631 if(width == 0 || height == 0) { 1632 BITMAP bmHeader; 1633 auto bm = GetCurrentObject(hdc, OBJ_BITMAP); 1634 GetObject(bm, BITMAP.sizeof, &bmHeader); 1635 if(width == 0) 1636 width = bmHeader.bmWidth; 1637 if(height == 0) 1638 height = bmHeader.bmHeight; 1639 } 1640 1641 auto i = new Image(width, height); 1642 HDC hdcMem = CreateCompatibleDC(hdc); 1643 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1644 BitBlt(hdcMem, x, y, width, height, hdc, 0, 0, SRCCOPY); 1645 SelectObject(hdcMem, hbmOld); 1646 DeleteDC(hdcMem); 1647 1648 got = i.toTrueColorImage(); 1649 } else featureNotImplemented(); 1650 1651 return got; 1652 } 1653 1654 version(Windows) extern(Windows) private alias SetProcessDpiAwarenessContext_t = BOOL function(HANDLE); 1655 version(Windows) extern(Windows) private __gshared UINT function(HWND) GetDpiForWindow; 1656 version(Windows) extern(Windows) private __gshared BOOL function(UINT, UINT, PVOID, UINT, UINT) SystemParametersInfoForDpi; 1657 1658 version(Windows) 1659 shared static this() { 1660 auto lib = LoadLibrary("User32.dll"); 1661 if(lib is null) 1662 return; 1663 //scope(exit) 1664 //FreeLibrary(lib); 1665 1666 SetProcessDpiAwarenessContext_t SetProcessDpiAwarenessContext = cast(SetProcessDpiAwarenessContext_t) GetProcAddress(lib, "SetProcessDpiAwarenessContext"); 1667 1668 if(SetProcessDpiAwarenessContext is null) 1669 return; 1670 1671 enum DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(HANDLE) -4; 1672 if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { 1673 //writeln(GetLastError()); 1674 } 1675 1676 GetDpiForWindow = cast(typeof(GetDpiForWindow)) GetProcAddress(lib, "GetDpiForWindow"); 1677 SystemParametersInfoForDpi = cast(typeof(SystemParametersInfoForDpi)) GetProcAddress(lib, "SystemParametersInfoForDpi"); 1678 } 1679 1680 /++ 1681 Blocking mode for event loop calls associated with a window instance. 1682 1683 History: 1684 Added December 8, 2021 (dub v10.5). Prior to that, all calls to 1685 `window.eventLoop` were the same as calls to `EventLoop.get.run`; that 1686 is, all would block until the application quit. 1687 1688 That behavior can still be achieved here with `untilApplicationQuits`, 1689 or explicitly calling the top-level `EventLoop.get.run` function. 1690 +/ 1691 enum BlockingMode { 1692 /++ 1693 The event loop call will block until the whole application is ready 1694 to quit if it is the only one running, but if it is nested inside 1695 another one, it will only block until the window you're calling it on 1696 closes. 1697 +/ 1698 automatic = 0x00, 1699 /++ 1700 The event loop call will only return when the whole application 1701 is ready to quit. This usually means all windows have been closed. 1702 1703 This is appropriate for your main application event loop. 1704 +/ 1705 untilApplicationQuits = 0x01, 1706 /++ 1707 The event loop will return when the window you're calling it on 1708 closes. If there are other windows still open, they may be destroyed 1709 unless you have another event loop running later. 1710 1711 This might be appropriate for a modal dialog box loop. Remember that 1712 other windows are still processing input though, so you can end up 1713 with a lengthy call stack if this happens in a loop, similar to a 1714 recursive function (well, it literally is a recursive function, just 1715 not an obvious looking one). 1716 +/ 1717 untilWindowCloses = 0x02, 1718 /++ 1719 If an event loop is already running, this call will immediately 1720 return, allowing the existing loop to handle it. If not, this call 1721 will block until the condition you bitwise-or into the flag. 1722 1723 The default is to block until the application quits, same as with 1724 the `automatic` setting (since if it were nested, which triggers until 1725 window closes in automatic, this flag would instead not block at all), 1726 but if you used `BlockingMode.onlyIfNotNested | BlockingMode.untilWindowCloses`, 1727 it will only nest until the window closes. You might want that if you are 1728 going to open two windows simultaneously and want closing just one of them 1729 to trigger the event loop return. 1730 +/ 1731 onlyIfNotNested = 0x10, 1732 } 1733 1734 /++ 1735 The flagship window class. 1736 1737 1738 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1739 out of more advanced or complex features of the underlying windowing system. 1740 1741 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1742 and get a suitable window to work with. 1743 1744 From there, you can opt into additional features, like custom resizability and OpenGL support 1745 with the next two constructor arguments. Or, if you need even more, you can set a window type 1746 and customization flags with the final two constructor arguments. 1747 1748 If none of that works for you, you can also create a window using native function calls, then 1749 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1750 though, if you do this, managing the window is still your own responsibility! Notably, you 1751 will need to destroy it yourself. 1752 +/ 1753 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1754 1755 /++ 1756 Copies the window's current state into a [TrueColorImage]. 1757 1758 Be warned: this can be a very slow operation 1759 1760 History: 1761 Actually implemented on March 14, 2021 1762 +/ 1763 TrueColorImage takeScreenshot() { 1764 version(Windows) 1765 return trueColorImageFromNativeHandle(impl.hwnd, width, height); 1766 else version(OSXCocoa) 1767 throw new NotYetImplementedException(); 1768 else 1769 return trueColorImageFromNativeHandle(impl.window, width, height); 1770 } 1771 1772 /++ 1773 Returns the actual logical DPI for the window on its current display monitor. If the window 1774 straddles monitors, it will return the value of one or the other in a platform-defined manner. 1775 1776 Please note this function may return zero if it doesn't know the answer! 1777 1778 1779 On Windows, it returns the dpi per monitor if the operating system supports it (Windows 10), 1780 or a system dpi value if not, which will live-update if the OS supports it (Windows 8 and up). 1781 1782 On X, it reads the xrandr extension to determine monitor positions and sizes. On some systems, 1783 this is not provided, meaning it will return 0. Otherwise, it will determine which monitor the 1784 window primarily resides on by checking the center point of the window against the monitor map. 1785 1786 Returns: 1787 0 if unknown. Otherwise, a rounded value of dots per inch reported by the monitor. It 1788 assumes the X and Y dpi are the same. 1789 1790 History: 1791 Added November 26, 2021 (dub v10.4) 1792 1793 It said "physical dpi" in the description prior to July 29, 2022, but the behavior was 1794 always a logical value on Windows and usually one on Linux too, so now the docs reflect 1795 that. 1796 1797 Bugs: 1798 Probably plenty. I haven't done a lot of tests on this. I know it doesn't automatically 1799 just work on linux; you need to set ARSD_SCALING_FACTOR as an environment variable to 1800 set it. Set ARSD_SCALING_FACTOR=1;1.5 for example to set it to 1x on the primary monitor 1801 and 1.5 on the secondary monitor. 1802 1803 The local dpi is not necessarily related to the physical dpi of the monitor. The name 1804 is a historical misnomer - the real thing of interest is the scale factor and due to 1805 compatibility concerns the scale would modify dpi values to trick applications. But since 1806 that's the terminology common out there, I used it too. 1807 1808 See_Also: 1809 [getDpi] gives the value provided for the default monitor. Not necessarily the same 1810 as this since the window many be on a different monitor, but it is a reasonable fallback 1811 to use if `actualDpi` returns 0. 1812 1813 [onDpiChanged] is changed when `actualDpi` has changed. 1814 +/ 1815 int actualDpi() { 1816 if(!actualDpiLoadAttempted) { 1817 // FIXME: do the actual monitor we are on 1818 // and on X this is a good chance to load the monitor map. 1819 version(Windows) { 1820 if(GetDpiForWindow) 1821 actualDpi_ = GetDpiForWindow(impl.hwnd); 1822 } else version(X11) { 1823 if(!xRandrInfoLoadAttemped) { 1824 xRandrInfoLoadAttemped = true; 1825 if(!XRandrLibrary.attempted) { 1826 XRandrLibrary.loadDynamicLibrary(); 1827 } 1828 1829 if(XRandrLibrary.loadSuccessful) { 1830 auto display = XDisplayConnection.get; 1831 int scratch; 1832 int major, minor; 1833 if(!XRRQueryExtension(display, &xrrEventBase, &scratch)) 1834 goto fallback; 1835 1836 XRRQueryVersion(display, &major, &minor); 1837 if(major <= 1 && minor < 5) 1838 goto fallback; 1839 1840 int count; 1841 XRRMonitorInfo *monitors = XRRGetMonitors(display, RootWindow(display, DefaultScreen(display)), true, &count); 1842 if(monitors is null) 1843 goto fallback; 1844 scope(exit) XRRFreeMonitors(monitors); 1845 1846 MonitorInfo.info = MonitorInfo.info[0 .. 0]; 1847 MonitorInfo.info.assumeSafeAppend(); 1848 foreach(idx, monitor; monitors[0 .. count]) { 1849 MonitorInfo.info ~= MonitorInfo( 1850 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1851 Size(monitor.mwidth, monitor.mheight), 1852 cast(int) (customScalingFactorForMonitor(cast(int) idx) * getDpi()[0]) 1853 ); 1854 1855 /+ 1856 if(monitor.mwidth == 0 || monitor.mheight == 0) 1857 // unknown physical size, just guess 96 to avoid divide by zero 1858 MonitorInfo.info ~= MonitorInfo( 1859 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1860 Size(monitor.mwidth, monitor.mheight), 1861 96 1862 ); 1863 else 1864 // and actual thing 1865 MonitorInfo.info ~= MonitorInfo( 1866 Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), 1867 Size(monitor.mwidth, monitor.mheight), 1868 minInternal( 1869 // millimeter to int then rounding up. 1870 cast(int)(monitor.width * 25.4 / monitor.mwidth + 0.5), 1871 cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) 1872 ) 1873 ); 1874 +/ 1875 } 1876 // import std.stdio; writeln("Here", MonitorInfo.info); 1877 } 1878 } 1879 1880 if(XRandrLibrary.loadSuccessful) { 1881 updateActualDpi(true); 1882 //import std.stdio; writeln("updated"); 1883 1884 if(!requestedInput) { 1885 // this is what requests live updates should the configuration change 1886 // each time you select input, it sends an initial event, so very important 1887 // to not get into a loop of selecting input, getting event, updating data, 1888 // and reselecting input... 1889 requestedInput = true; 1890 XRRSelectInput(display, impl.window, RRScreenChangeNotifyMask); 1891 //import std.stdio; writeln("requested input"); 1892 } 1893 } else { 1894 fallback: 1895 // make sure we disable events that aren't coming 1896 xrrEventBase = -1; 1897 // best guess... respect the custom scaling user command to some extent at least though 1898 actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); 1899 } 1900 } 1901 actualDpiLoadAttempted = true; 1902 } 1903 return actualDpi_; 1904 } 1905 1906 private int actualDpi_; 1907 private bool actualDpiLoadAttempted; 1908 1909 version(X11) private { 1910 bool requestedInput; 1911 static bool xRandrInfoLoadAttemped; 1912 struct MonitorInfo { 1913 Rectangle position; 1914 Size size; 1915 int dpi; 1916 1917 static MonitorInfo[] info; 1918 } 1919 bool screenPositionKnown; 1920 int screenPositionX; 1921 int screenPositionY; 1922 void updateActualDpi(bool loadingNow = false) { 1923 if(!loadingNow && !actualDpiLoadAttempted) 1924 actualDpi(); // just to make it do the load 1925 foreach(idx, m; MonitorInfo.info) { 1926 if(m.position.contains(Point(screenPositionX + this.width / 2, screenPositionY + this.height / 2))) { 1927 bool changed = actualDpi_ && actualDpi_ != m.dpi; 1928 actualDpi_ = m.dpi; 1929 //import std.stdio; writeln("monitor ", idx); 1930 if(changed && onDpiChanged) 1931 onDpiChanged(); 1932 break; 1933 } 1934 } 1935 } 1936 } 1937 1938 /++ 1939 Sent when the window is moved to a new DPI context, for example, when it is dragged between monitors 1940 or if the window is moved to a new remote connection or a monitor is hot-swapped. 1941 1942 History: 1943 Added November 26, 2021 (dub v10.4) 1944 1945 See_Also: 1946 [actualDpi] 1947 +/ 1948 void delegate() onDpiChanged; 1949 1950 version(X11) { 1951 void recreateAfterDisconnect() { 1952 if(!stateDiscarded) return; 1953 1954 if(_parent !is null && _parent.stateDiscarded) 1955 _parent.recreateAfterDisconnect(); 1956 1957 bool wasHidden = hidden; 1958 1959 activeScreenPainter = null; // should already be done but just to confirm 1960 1961 actualDpi_ = 0; 1962 actualDpiLoadAttempted = false; 1963 xRandrInfoLoadAttemped = false; 1964 1965 impl.createWindow(_width, _height, _title, openglMode, _parent); 1966 1967 if(auto dh = dropHandler) { 1968 dropHandler = null; 1969 enableDragAndDrop(this, dh); 1970 } 1971 1972 if(recreateAdditionalConnectionState) 1973 recreateAdditionalConnectionState(); 1974 1975 hidden = wasHidden; 1976 stateDiscarded = false; 1977 } 1978 1979 bool stateDiscarded; 1980 void discardConnectionState() { 1981 if(XDisplayConnection.display) 1982 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 1983 if(discardAdditionalConnectionState) 1984 discardAdditionalConnectionState(); 1985 stateDiscarded = true; 1986 } 1987 1988 void delegate() discardAdditionalConnectionState; 1989 void delegate() recreateAdditionalConnectionState; 1990 1991 } 1992 1993 private DropHandler dropHandler; 1994 1995 SimpleWindow _parent; 1996 bool beingOpenKeepsAppOpen = true; 1997 /++ 1998 This creates a window with the given options. The window will be visible and able to receive input as soon as you start your event loop. You may draw on it immediately after creating the window, without needing to wait for the event loop to start if you want. 1999 2000 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 2001 2002 Params: 2003 2004 width = the width of the window's client area, in pixels 2005 height = the height of the window's client area, in pixels 2006 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 2007 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 2008 resizable = [Resizability] has three options: 2009 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 2010 $(P `fixedSize` will not allow the user to resize the window.) 2011 $(P `automaticallyScaleIfPossible` will allow the user to resize, but will still present the original size to the API user. The contents you draw will be scaled to the size the user chose. If this scaling is not efficient, the window will be fixed size. The `windowResized` event handler will never be called. This is the default.) 2012 windowType = The type of window you want to make. 2013 customizationFlags = A way to make a window without a border, always on top, skip taskbar, and more. Do not use this if one of the pre-defined [WindowTypes], given in the `windowType` argument, is a good match for what you need. 2014 parent = the parent window, if applicable. This makes the child window nested inside the parent unless you set [WindowFlags.transient], which makes it a top-level window merely owned by the "parent". 2015 +/ 2016 this(int width = 640, int height = 480, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2017 claimGuiThread(); 2018 version(sdpy_thread_checks) assert(thisIsGuiThread); 2019 this._width = width; 2020 this._height = height; 2021 this.openglMode = opengl; 2022 this.resizability = resizable; 2023 this.windowType = windowType; 2024 this.customizationFlags = customizationFlags; 2025 this._title = (title is null ? "D Application" : title); 2026 this._parent = parent; 2027 impl.createWindow(width, height, this._title, opengl, parent); 2028 2029 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 2030 beingOpenKeepsAppOpen = false; 2031 } 2032 2033 /// ditto 2034 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 2035 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 2036 } 2037 2038 /// Same as above, except using the `Size` struct instead of separate width and height. 2039 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 2040 this(size.width, size.height, title, opengl, resizable); 2041 } 2042 2043 /// ditto 2044 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 2045 this(size, title, opengl, resizable); 2046 } 2047 2048 2049 /++ 2050 Creates a window based on the given [Image]. It's client area 2051 width and height is equal to the image. (A window's client area 2052 is the drawable space inside; it excludes the title bar, etc.) 2053 2054 Windows based on images will not be resizable and do not use OpenGL. 2055 2056 It will draw the image in upon creation, but this will be overwritten 2057 upon any draws, including the initial window visible event. 2058 2059 You probably do not want to use this and it may be removed from 2060 the library eventually, or I might change it to be a "permanent" 2061 background image; one that is automatically drawn on it before any 2062 other drawing event. idk. 2063 +/ 2064 this(Image image, string title = null) { 2065 this(image.width, image.height, title); 2066 this.image = image; 2067 } 2068 2069 /++ 2070 Wraps a native window handle with very little additional processing - notably no destruction 2071 this is incomplete so don't use it for much right now. The purpose of this is to make native 2072 windows created through the low level API (so you can use platform-specific options and 2073 other details SimpleWindow does not expose) available to the event loop wrappers. 2074 +/ 2075 this(NativeWindowHandle nativeWindow) { 2076 version(Windows) 2077 impl.hwnd = nativeWindow; 2078 else version(X11) { 2079 impl.window = nativeWindow; 2080 if(nativeWindow) 2081 display = XDisplayConnection.get(); // get initial display to not segfault 2082 } else version(OSXCocoa) 2083 throw new NotYetImplementedException(); 2084 else featureNotImplemented(); 2085 // FIXME: set the size correctly 2086 _width = 1; 2087 _height = 1; 2088 if(nativeWindow) 2089 nativeMapping[nativeWindow] = this; 2090 2091 beingOpenKeepsAppOpen = false; 2092 2093 if(nativeWindow) 2094 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 2095 _suppressDestruction = true; // so it doesn't try to close 2096 } 2097 2098 /++ 2099 Used iff [WindowFlags.managesChildWindowFocus] is set when the window is created. 2100 The delegate will be called when the window manager asks you to take focus. 2101 2102 This is currently only used for the WM_TAKE_FOCUS protocol on X11 at this time. 2103 2104 History: 2105 Added April 1, 2022 (dub v10.8) 2106 +/ 2107 SimpleWindow delegate() setRequestedInputFocus; 2108 2109 /// Experimental, do not use yet 2110 /++ 2111 Grabs exclusive input from the user until you release it with 2112 [releaseInputGrab]. 2113 2114 2115 Note: it is extremely rude to do this without good reason. 2116 Reasons may include doing some kind of mouse drag operation 2117 or popping up a temporary menu that should get events and will 2118 be dismissed at ease by the user clicking away. 2119 2120 Params: 2121 keyboard = do you want to grab keyboard input? 2122 mouse = grab mouse input? 2123 confine = confine the mouse cursor to inside this window? 2124 2125 History: 2126 Prior to March 11, 2021, grabbing the keyboard would always also 2127 set the X input focus. Now, it only focuses if it is a non-transient 2128 window and otherwise manages the input direction internally. 2129 2130 This means spurious focus/blur events will no longer be sent and the 2131 application will not steal focus from other applications (which the 2132 window manager may have rejected anyway). 2133 +/ 2134 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 2135 static if(UsingSimpledisplayX11) { 2136 XSync(XDisplayConnection.get, 0); 2137 if(keyboard) { 2138 if(isTransient && _parent) { 2139 /* 2140 FIXME: 2141 setting the keyboard focus is not actually that helpful, what I more likely want 2142 is the events from the parent window to be sent over here if we're transient. 2143 */ 2144 2145 _parent.inputProxy = this; 2146 } else { 2147 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 2148 } 2149 } 2150 if(mouse) { 2151 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 2152 EventMask.PointerMotionMask // FIXME: not efficient 2153 | EventMask.ButtonPressMask 2154 | EventMask.ButtonReleaseMask 2155 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 2156 ) 2157 { 2158 XSync(XDisplayConnection.get, 0); 2159 import core.stdc.stdio; 2160 printf("Grab input failed %d\n", res); 2161 //throw new Exception("Grab input failed"); 2162 } else { 2163 // cool 2164 } 2165 } 2166 2167 } else version(Windows) { 2168 // FIXME: keyboard? 2169 SetCapture(impl.hwnd); 2170 if(confine) { 2171 RECT rcClip; 2172 //RECT rcOldClip; 2173 //GetClipCursor(&rcOldClip); 2174 GetWindowRect(hwnd, &rcClip); 2175 ClipCursor(&rcClip); 2176 } 2177 } else version(OSXCocoa) { 2178 throw new NotYetImplementedException(); 2179 } else static assert(0); 2180 } 2181 2182 /++ 2183 Returns the native window. 2184 2185 History: 2186 Added November 5, 2021 (dub v10.4). Prior to that, you'd have 2187 to access it through the `impl` member (which is semi-supported 2188 but platform specific and here it is simple enough to offer an accessor). 2189 2190 Bugs: 2191 Not implemented outside Windows or X11. 2192 +/ 2193 NativeWindowHandle nativeWindowHandle() { 2194 version(X11) 2195 return impl.window; 2196 else version(Windows) 2197 return impl.hwnd; 2198 else 2199 throw new NotYetImplementedException(); 2200 } 2201 2202 private bool isTransient() { 2203 with(WindowTypes) 2204 final switch(windowType) { 2205 case normal, undecorated, eventOnly: 2206 case nestedChild: 2207 return (customizationFlags & WindowFlags.transient) ? true : false; 2208 case dropdownMenu, popupMenu, notification: 2209 return true; 2210 } 2211 } 2212 2213 private SimpleWindow inputProxy; 2214 2215 /++ 2216 Releases the grab acquired by [grabInput]. 2217 +/ 2218 void releaseInputGrab() { 2219 static if(UsingSimpledisplayX11) { 2220 XUngrabPointer(XDisplayConnection.get, CurrentTime); 2221 if(_parent) 2222 _parent.inputProxy = null; 2223 } else version(Windows) { 2224 ReleaseCapture(); 2225 ClipCursor(null); 2226 } else version(OSXCocoa) { 2227 throw new NotYetImplementedException(); 2228 } else static assert(0); 2229 } 2230 2231 /++ 2232 Sets the input focus to this window. 2233 2234 You shouldn't call this very often - please let the user control the input focus. 2235 +/ 2236 void focus() { 2237 static if(UsingSimpledisplayX11) { 2238 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 2239 } else version(Windows) { 2240 SetFocus(this.impl.hwnd); 2241 } else version(OSXCocoa) { 2242 throw new NotYetImplementedException(); 2243 } else static assert(0); 2244 } 2245 2246 /++ 2247 Requests attention from the user for this window. 2248 2249 2250 The typical result of this function is to change the color 2251 of the taskbar icon, though it may be tweaked on specific 2252 platforms. 2253 2254 It is meant to unobtrusively tell the user that something 2255 relevant to them happened in the background and they should 2256 check the window when they get a chance. Upon receiving the 2257 keyboard focus, the window will automatically return to its 2258 natural state. 2259 2260 If the window already has the keyboard focus, this function 2261 may do nothing, because the user is presumed to already be 2262 giving the window attention. 2263 2264 Implementation_note: 2265 2266 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 2267 atom on X11 and the FlashWindow function on Windows. 2268 +/ 2269 void requestAttention() { 2270 if(_focused) 2271 return; 2272 2273 version(Windows) { 2274 FLASHWINFO info; 2275 info.cbSize = info.sizeof; 2276 info.hwnd = impl.hwnd; 2277 info.dwFlags = FLASHW_TRAY; 2278 info.uCount = 1; 2279 2280 FlashWindowEx(&info); 2281 2282 } else version(X11) { 2283 demandingAttention = true; 2284 demandAttention(this, true); 2285 } else version(OSXCocoa) { 2286 throw new NotYetImplementedException(); 2287 } else static assert(0); 2288 } 2289 2290 private bool _focused; 2291 2292 version(X11) private bool demandingAttention; 2293 2294 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 2295 /// You'll have to call `close()` manually if you set this delegate. 2296 void delegate () closeQuery; 2297 2298 /// This will be called when window visibility was changed. 2299 void delegate (bool becomesVisible) visibilityChanged; 2300 2301 /// This will be called when window becomes visible for the first time. 2302 /// You can do OpenGL initialization here. Note that in X11 you can't call 2303 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 2304 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 2305 private bool _visibleForTheFirstTimeCalled; 2306 void delegate () visibleForTheFirstTime; 2307 2308 /// Returns true if the window has been closed. 2309 final @property bool closed() { return _closed; } 2310 2311 private final @property bool notClosed() { return !_closed; } 2312 2313 /// Returns true if the window is focused. 2314 final @property bool focused() { return _focused; } 2315 2316 private bool _visible; 2317 /// Returns true if the window is visible (mapped). 2318 final @property bool visible() { return _visible; } 2319 2320 /// Closes the window. If there are no more open windows, the event loop will terminate. 2321 void close() { 2322 if (!_closed) { 2323 runInGuiThread( { 2324 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 2325 if (onClosing !is null) onClosing(); 2326 impl.closeWindow(); 2327 _closed = true; 2328 } ); 2329 } 2330 } 2331 2332 /++ 2333 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 2334 2335 History: 2336 Overload added on March 7, 2021. 2337 +/ 2338 void close() shared { 2339 (cast() this).close(); 2340 } 2341 2342 /++ 2343 2344 +/ 2345 void maximize() { 2346 version(Windows) 2347 ShowWindow(impl.hwnd, SW_MAXIMIZE); 2348 else version(X11) { 2349 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 2350 2351 // also note _NET_WM_STATE_FULLSCREEN 2352 } 2353 2354 } 2355 2356 private bool _fullscreen; 2357 version(Windows) 2358 private WINDOWPLACEMENT g_wpPrev; 2359 2360 /// not fully implemented but planned for a future release 2361 void fullscreen(bool yes) { 2362 version(Windows) { 2363 g_wpPrev.length = WINDOWPLACEMENT.sizeof; 2364 DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); 2365 if (dwStyle & WS_OVERLAPPEDWINDOW) { 2366 MONITORINFO mi; 2367 mi.cbSize = MONITORINFO.sizeof; 2368 if (GetWindowPlacement(hwnd, &g_wpPrev) && 2369 GetMonitorInfo(MonitorFromWindow(hwnd, 2370 MONITOR_DEFAULTTOPRIMARY), &mi)) { 2371 SetWindowLong(hwnd, GWL_STYLE, 2372 dwStyle & ~WS_OVERLAPPEDWINDOW); 2373 SetWindowPos(hwnd, HWND_TOP, 2374 mi.rcMonitor.left, mi.rcMonitor.top, 2375 mi.rcMonitor.right - mi.rcMonitor.left, 2376 mi.rcMonitor.bottom - mi.rcMonitor.top, 2377 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2378 } 2379 } else { 2380 SetWindowLong(hwnd, GWL_STYLE, 2381 dwStyle | WS_OVERLAPPEDWINDOW); 2382 SetWindowPlacement(hwnd, &g_wpPrev); 2383 SetWindowPos(hwnd, null, 0, 0, 0, 0, 2384 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | 2385 SWP_NOOWNERZORDER | SWP_FRAMECHANGED); 2386 } 2387 2388 } else version(X11) { 2389 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 2390 } 2391 2392 _fullscreen = yes; 2393 2394 } 2395 2396 bool fullscreen() { 2397 return _fullscreen; 2398 } 2399 2400 /++ 2401 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 2402 2403 +/ 2404 void minimize() { 2405 version(Windows) 2406 ShowWindow(impl.hwnd, SW_MINIMIZE); 2407 //else version(X11) 2408 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 2409 } 2410 2411 /// Alias for `hidden = false` 2412 void show() { 2413 hidden = false; 2414 } 2415 2416 /// Alias for `hidden = true` 2417 void hide() { 2418 hidden = true; 2419 } 2420 2421 /// Hide cursor when it enters the window. 2422 void hideCursor() { 2423 version(OSXCocoa) throw new NotYetImplementedException(); else 2424 if (!_closed) impl.hideCursor(); 2425 } 2426 2427 /// Don't hide cursor when it enters the window. 2428 void showCursor() { 2429 version(OSXCocoa) throw new NotYetImplementedException(); else 2430 if (!_closed) impl.showCursor(); 2431 } 2432 2433 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 2434 * 2435 * Please remember that the cursor is a shared resource that should usually be left to the user's 2436 * control. Try to think for other approaches before using this function. 2437 * 2438 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2439 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2440 * receive "mouse moved here" event. 2441 */ 2442 bool warpMouse (int x, int y) { 2443 version(X11) { 2444 if (!_closed) { impl.warpMouse(x, y); return true; } 2445 } else version(Windows) { 2446 if (!_closed) { 2447 POINT point; 2448 point.x = x; 2449 point.y = y; 2450 if(ClientToScreen(impl.hwnd, &point)) { 2451 SetCursorPos(point.x, point.y); 2452 return true; 2453 } 2454 } 2455 } 2456 return false; 2457 } 2458 2459 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2460 void sendDummyEvent () { 2461 version(X11) { 2462 if (!_closed) { impl.sendDummyEvent(); } 2463 } 2464 } 2465 2466 /// Set window minimal size. 2467 void setMinSize (int minwidth, int minheight) { 2468 version(OSXCocoa) throw new NotYetImplementedException(); else 2469 if (!_closed) impl.setMinSize(minwidth, minheight); 2470 } 2471 2472 /// Set window maximal size. 2473 void setMaxSize (int maxwidth, int maxheight) { 2474 version(OSXCocoa) throw new NotYetImplementedException(); else 2475 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2476 } 2477 2478 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2479 /// Currently only supported on X11. 2480 void setResizeGranularity (int granx, int grany) { 2481 version(OSXCocoa) throw new NotYetImplementedException(); else 2482 if (!_closed) impl.setResizeGranularity(granx, grany); 2483 } 2484 2485 /// Move window. 2486 void move(int x, int y) { 2487 version(OSXCocoa) throw new NotYetImplementedException(); else 2488 if (!_closed) impl.move(x, y); 2489 } 2490 2491 /// ditto 2492 void move(Point p) { 2493 version(OSXCocoa) throw new NotYetImplementedException(); else 2494 if (!_closed) impl.move(p.x, p.y); 2495 } 2496 2497 /++ 2498 Resize window. 2499 2500 Note that the width and height of the window are NOT instantly 2501 updated - it waits for the window manager to approve the resize 2502 request, which means you must return to the event loop before the 2503 width and height are actually changed. 2504 +/ 2505 void resize(int w, int h) { 2506 if(!_closed && _fullscreen) fullscreen = false; 2507 version(OSXCocoa) throw new NotYetImplementedException(); else 2508 if (!_closed) impl.resize(w, h); 2509 } 2510 2511 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2512 void moveResize (int x, int y, int w, int h) { 2513 if(!_closed && _fullscreen) fullscreen = false; 2514 version(OSXCocoa) throw new NotYetImplementedException(); else 2515 if (!_closed) impl.moveResize(x, y, w, h); 2516 } 2517 2518 private bool _hidden; 2519 2520 /// Returns true if the window is hidden. 2521 final @property bool hidden() { 2522 return _hidden; 2523 } 2524 2525 /// Shows or hides the window based on the bool argument. 2526 final @property void hidden(bool b) { 2527 _hidden = b; 2528 version(Windows) { 2529 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2530 } else version(X11) { 2531 if(b) 2532 //XUnmapWindow(impl.display, impl.window); 2533 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2534 else 2535 XMapWindow(impl.display, impl.window); 2536 } else version(OSXCocoa) { 2537 throw new NotYetImplementedException(); 2538 } else static assert(0); 2539 } 2540 2541 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2542 void opacity(double opacity) @property 2543 in { 2544 assert(opacity >= 0 && opacity <= 1); 2545 } do { 2546 version (Windows) { 2547 impl.setOpacity(cast(ubyte)(255 * opacity)); 2548 } else version (X11) { 2549 impl.setOpacity(cast(uint)(uint.max * opacity)); 2550 } else throw new NotYetImplementedException(); 2551 } 2552 2553 /++ 2554 Sets your event handlers, without entering the event loop. Useful if you 2555 have multiple windows - set the handlers on each window, then only do 2556 [eventLoop] on your main window or call `EventLoop.get.run();`. 2557 2558 This assigns the given handlers to [handleKeyEvent], [handleCharEvent], 2559 [handlePulse], and [handleMouseEvent] automatically based on the provide 2560 delegate signatures. 2561 +/ 2562 void setEventHandlers(T...)(T eventHandlers) { 2563 // FIXME: add more events 2564 foreach(handler; eventHandlers) { 2565 static if(__traits(compiles, handleKeyEvent = handler)) { 2566 handleKeyEvent = handler; 2567 } else static if(__traits(compiles, handleCharEvent = handler)) { 2568 handleCharEvent = handler; 2569 } else static if(__traits(compiles, handlePulse = handler)) { 2570 handlePulse = handler; 2571 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2572 handleMouseEvent = handler; 2573 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2574 } 2575 } 2576 2577 /++ 2578 The event loop automatically returns when the window is closed 2579 pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2580 pulse timer is created. The event loop will block until an event 2581 arrives or the pulse timer goes off. 2582 2583 The given `eventHandlers` are passed to [setEventHandlers], which in turn 2584 assigns them to [handleKeyEvent], [handleCharEvent], [handlePulse], and 2585 [handleMouseEvent], based on the signature of delegates you provide. 2586 2587 Give one with no parameters to set a timer pulse handler. Give one that 2588 takes [KeyEvent] for a key handler, [MouseEvent], for a mouse handler, 2589 and one that takes `dchar` for a char event handler. You can use as many 2590 or as few handlers as you need for your application. 2591 2592 History: 2593 The overload without `pulseTimeout` was added on December 8, 2021. 2594 2595 On December 9, 2021, the default blocking mode (which is now configurable 2596 because [eventLoopWithBlockingMode] was added) switched from 2597 [BlockingMode.untilApplicationQuits] over to [BlockingMode.automatic]. This 2598 should almost never be noticeable to you since the typical simpledisplay 2599 paradigm has been (and I still recommend) to have one `eventLoop` call. 2600 2601 See_Also: 2602 [eventLoopWithBlockingMode] 2603 +/ 2604 final int eventLoop(T...)( 2605 long pulseTimeout, /// set to zero if you don't want a pulse. 2606 T eventHandlers) /// delegate list like std.concurrency.receive 2607 { 2608 return eventLoopWithBlockingMode(BlockingMode.automatic, pulseTimeout, eventHandlers); 2609 } 2610 2611 /// ditto 2612 final int eventLoop(T...)(T eventHandlers) if(T.length == 0 || is(T[0] == delegate)) 2613 { 2614 return eventLoopWithBlockingMode(BlockingMode.automatic, 0, eventHandlers); 2615 } 2616 2617 /++ 2618 This is the function [eventLoop] forwards to. It, in turn, forwards to `EventLoop.get.run`. 2619 2620 History: 2621 Added December 8, 2021 (dub v10.5) 2622 2623 Previously, this implementation was right inside [eventLoop], but when I wanted 2624 to add the new [BlockingMode] parameter, the compiler got in a trouble loop so I 2625 just renamed it instead of adding as an overload. Besides, the new name makes it 2626 easier to remember the order and avoids ambiguity between two int-like params anyway. 2627 2628 See_Also: 2629 [SimpleWindow.eventLoop], [EventLoop] 2630 2631 Bugs: 2632 The blocking mode is not implemented on OSX Cocoa nor on the (deprecated) arsd.eventloop. 2633 +/ 2634 final int eventLoopWithBlockingMode(T...)( 2635 BlockingMode blockingMode, /// when you want this function to block until 2636 long pulseTimeout, /// set to zero if you don't want a pulse. 2637 T eventHandlers) /// delegate list like std.concurrency.receive 2638 { 2639 setEventHandlers(eventHandlers); 2640 2641 version(with_eventloop) { 2642 // delegates event loop to my other module 2643 version(X11) 2644 XFlush(display); 2645 2646 import arsd.eventloop; 2647 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2648 scope(exit) clearInterval(handle); 2649 2650 loop(); 2651 return 0; 2652 } else version(OSXCocoa) { 2653 // FIXME 2654 if (handlePulse !is null && pulseTimeout != 0) { 2655 timer = scheduledTimer(pulseTimeout*1e-3, 2656 view, sel_registerName("simpledisplay_pulse"), 2657 null, true); 2658 } 2659 2660 setNeedsDisplay(view, true); 2661 run(NSApp); 2662 return 0; 2663 } else { 2664 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2665 2666 if((blockingMode & BlockingMode.onlyIfNotNested) && el.impl.refcount > 1) 2667 return 0; 2668 2669 return el.run( 2670 ((blockingMode & 0x0f) == BlockingMode.untilApplicationQuits) ? 2671 null : 2672 &this.notClosed 2673 ); 2674 } 2675 } 2676 2677 /++ 2678 This lets you draw on the window (or its backing buffer) using basic 2679 2D primitives. 2680 2681 Be sure to call this in a limited scope because your changes will not 2682 actually appear on the window until ScreenPainter's destructor runs. 2683 2684 Returns: an instance of [ScreenPainter], which has the drawing methods 2685 on it to draw on this window. 2686 2687 Params: 2688 manualInvalidations = if you set this to true, you will need to 2689 set the invalid rectangle on the painter yourself. If false, it 2690 assumes the whole window has been redrawn each time you draw. 2691 2692 Only invalidated rectangles are blitted back to the window when 2693 the destructor runs. Doing this yourself can reduce flickering 2694 of child windows. 2695 2696 History: 2697 The `manualInvalidations` parameter overload was added on 2698 December 30, 2021 (dub v10.5) 2699 +/ 2700 ScreenPainter draw() { 2701 return draw(false); 2702 } 2703 /// ditto 2704 ScreenPainter draw(bool manualInvalidations) { 2705 return impl.getPainter(manualInvalidations); 2706 } 2707 2708 // This is here to implement the interface we use for various native handlers. 2709 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2710 2711 // maps native window handles to SimpleWindow instances, if there are any 2712 // you shouldn't need this, but it is public in case you do in a native event handler or something 2713 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2714 2715 /// Width of the window's drawable client area, in pixels. 2716 @scriptable 2717 final @property int width() const pure nothrow @safe @nogc { return _width; } 2718 2719 /// Height of the window's drawable client area, in pixels. 2720 @scriptable 2721 final @property int height() const pure nothrow @safe @nogc { return _height; } 2722 2723 private int _width; 2724 private int _height; 2725 2726 // HACK: making the best of some copy constructor woes with refcounting 2727 private ScreenPainterImplementation* activeScreenPainter_; 2728 2729 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2730 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2731 2732 private OpenGlOptions openglMode; 2733 private Resizability resizability; 2734 private WindowTypes windowType; 2735 private int customizationFlags; 2736 2737 /// `true` if OpenGL was initialized for this window. 2738 @property bool isOpenGL () const pure nothrow @safe @nogc { 2739 version(without_opengl) 2740 return false; 2741 else 2742 return (openglMode == OpenGlOptions.yes); 2743 } 2744 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2745 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2746 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2747 2748 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2749 /// to call this, as it's not recommended to share window between threads. 2750 void mtLock () { 2751 version(X11) { 2752 XLockDisplay(this.display); 2753 } 2754 } 2755 2756 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2757 /// to call this, as it's not recommended to share window between threads. 2758 void mtUnlock () { 2759 version(X11) { 2760 XUnlockDisplay(this.display); 2761 } 2762 } 2763 2764 /// Emit a beep to get user's attention. 2765 void beep () { 2766 version(X11) { 2767 XBell(this.display, 100); 2768 } else version(Windows) { 2769 MessageBeep(0xFFFFFFFF); 2770 } 2771 } 2772 2773 2774 2775 version(without_opengl) {} else { 2776 2777 /// 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`. 2778 void delegate() redrawOpenGlScene; 2779 2780 /// This will allow you to change OpenGL vsync state. 2781 final @property void vsync (bool wait) { 2782 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2783 version(X11) { 2784 setAsCurrentOpenGlContext(); 2785 glxSetVSync(display, impl.window, wait); 2786 } else version(Windows) { 2787 setAsCurrentOpenGlContext(); 2788 wglSetVSync(wait); 2789 } 2790 } 2791 2792 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 2793 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 2794 /// enough without waiting 'em to finish their frame bussiness. 2795 bool useGLFinish = true; 2796 2797 // FIXME: it should schedule it for the end of the current iteration of the event loop... 2798 /// 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. 2799 void redrawOpenGlSceneNow() { 2800 version(X11) if (!this._visible) return; // no need to do this if window is invisible 2801 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2802 if(redrawOpenGlScene is null) 2803 return; 2804 2805 this.mtLock(); 2806 scope(exit) this.mtUnlock(); 2807 2808 this.setAsCurrentOpenGlContext(); 2809 2810 redrawOpenGlScene(); 2811 2812 this.swapOpenGlBuffers(); 2813 // 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. 2814 if (useGLFinish) glFinish(); 2815 } 2816 2817 private bool redrawOpenGlSceneSoonSet = false; 2818 private static class RedrawOpenGlSceneEvent { 2819 SimpleWindow w; 2820 this(SimpleWindow w) { this.w = w; } 2821 } 2822 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 2823 /++ 2824 Queues an opengl redraw as soon as the other pending events are cleared. 2825 +/ 2826 void redrawOpenGlSceneSoon() { 2827 if(!redrawOpenGlSceneSoonSet) { 2828 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 2829 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 2830 redrawOpenGlSceneSoonSet = true; 2831 } 2832 this.postEvent(redrawOpenGlSceneEvent, true); 2833 } 2834 2835 2836 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2837 void setAsCurrentOpenGlContext() { 2838 assert(openglMode == OpenGlOptions.yes); 2839 version(X11) { 2840 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 2841 throw new Exception("glXMakeCurrent"); 2842 } else version(Windows) { 2843 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2844 if (!wglMakeCurrent(ghDC, ghRC)) 2845 throw new Exception("wglMakeCurrent"); // let windows users suffer too 2846 } 2847 } 2848 2849 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2850 /// This doesn't throw, returning success flag instead. 2851 bool setAsCurrentOpenGlContextNT() nothrow { 2852 assert(openglMode == OpenGlOptions.yes); 2853 version(X11) { 2854 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 2855 } else version(Windows) { 2856 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2857 return wglMakeCurrent(ghDC, ghRC) ? true : false; 2858 } 2859 } 2860 2861 /// 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. 2862 /// This doesn't throw, returning success flag instead. 2863 bool releaseCurrentOpenGlContext() nothrow { 2864 assert(openglMode == OpenGlOptions.yes); 2865 version(X11) { 2866 return (glXMakeCurrent(display, 0, null) != 0); 2867 } else version(Windows) { 2868 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2869 return wglMakeCurrent(ghDC, null) ? true : false; 2870 } 2871 } 2872 2873 /++ 2874 simpledisplay always uses double buffering, usually automatically. This 2875 manually swaps the OpenGL buffers. 2876 2877 2878 You should not need to call this yourself because simpledisplay will do it 2879 for you after calling your `redrawOpenGlScene`. 2880 2881 Remember that this may throw an exception, which you can catch in a multithreaded 2882 application to keep your thread from dying from an unhandled exception. 2883 +/ 2884 void swapOpenGlBuffers() { 2885 assert(openglMode == OpenGlOptions.yes); 2886 version(X11) { 2887 if (!this._visible) return; // no need to do this if window is invisible 2888 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2889 glXSwapBuffers(display, impl.window); 2890 } else version(Windows) { 2891 SwapBuffers(ghDC); 2892 } 2893 } 2894 } 2895 2896 /++ 2897 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 2898 2899 2900 --- 2901 auto window = new SimpleWindow(100, 100, "First title"); 2902 window.title = "A new title"; 2903 --- 2904 2905 You may call this function at any time. 2906 +/ 2907 @property void title(string title) { 2908 _title = title; 2909 version(OSXCocoa) throw new NotYetImplementedException(); else 2910 impl.setTitle(title); 2911 } 2912 2913 private string _title; 2914 2915 /// Gets the title 2916 @property string title() { 2917 if(_title is null) 2918 _title = getRealTitle(); 2919 return _title; 2920 } 2921 2922 /++ 2923 Get the title as set by the window manager. 2924 May not match what you attempted to set. 2925 +/ 2926 string getRealTitle() { 2927 static if(is(typeof(impl.getTitle()))) 2928 return impl.getTitle(); 2929 else 2930 return null; 2931 } 2932 2933 // don't use this generally it is not yet really released 2934 version(X11) 2935 @property Image secret_icon() { 2936 return secret_icon_inner; 2937 } 2938 private Image secret_icon_inner; 2939 2940 2941 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. 2942 @property void icon(MemoryImage icon) { 2943 if(icon is null) 2944 return; 2945 auto tci = icon.getAsTrueColorImage(); 2946 version(Windows) { 2947 winIcon = new WindowsIcon(icon); 2948 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 2949 } else version(X11) { 2950 secret_icon_inner = Image.fromMemoryImage(icon); 2951 // FIXME: ensure this is correct 2952 auto display = XDisplayConnection.get; 2953 arch_ulong[] buffer; 2954 buffer ~= icon.width; 2955 buffer ~= icon.height; 2956 foreach(c; tci.imageData.colors) { 2957 arch_ulong b; 2958 b |= c.a << 24; 2959 b |= c.r << 16; 2960 b |= c.g << 8; 2961 b |= c.b; 2962 buffer ~= b; 2963 } 2964 2965 XChangeProperty( 2966 display, 2967 impl.window, 2968 GetAtom!("_NET_WM_ICON", true)(display), 2969 GetAtom!"CARDINAL"(display), 2970 32 /* bits */, 2971 0 /*PropModeReplace*/, 2972 buffer.ptr, 2973 cast(int) buffer.length); 2974 } else version(OSXCocoa) { 2975 throw new NotYetImplementedException(); 2976 } else static assert(0); 2977 } 2978 2979 version(Windows) 2980 private WindowsIcon winIcon; 2981 2982 bool _suppressDestruction; 2983 2984 ~this() { 2985 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 2986 if(_suppressDestruction) 2987 return; 2988 impl.dispose(); 2989 } 2990 2991 private bool _closed; 2992 2993 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 2994 /* 2995 ScreenPainter drawTransiently() { 2996 return impl.getPainter(); 2997 } 2998 */ 2999 3000 /// Draws an image on the window. This is meant to provide quick look 3001 /// of a static image generated elsewhere. 3002 @property void image(Image i) { 3003 /+ 3004 version(Windows) { 3005 BITMAP bm; 3006 HDC hdc = GetDC(hwnd); 3007 HDC hdcMem = CreateCompatibleDC(hdc); 3008 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 3009 3010 GetObject(i.handle, bm.sizeof, &bm); 3011 3012 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 3013 3014 SelectObject(hdcMem, hbmOld); 3015 DeleteDC(hdcMem); 3016 ReleaseDC(hwnd, hdc); 3017 3018 /* 3019 RECT r; 3020 r.right = i.width; 3021 r.bottom = i.height; 3022 InvalidateRect(hwnd, &r, false); 3023 */ 3024 } else 3025 version(X11) { 3026 if(!destroyed) { 3027 if(i.usingXshm) 3028 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 3029 else 3030 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 3031 } 3032 } else 3033 version(OSXCocoa) { 3034 draw().drawImage(Point(0, 0), i); 3035 setNeedsDisplay(view, true); 3036 } else static assert(0); 3037 +/ 3038 auto painter = this.draw; 3039 painter.drawImage(Point(0, 0), i); 3040 } 3041 3042 /++ 3043 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 3044 3045 --- 3046 window.cursor = GenericCursor.Help; 3047 // now the window mouse cursor is set to a generic help 3048 --- 3049 3050 +/ 3051 @property void cursor(MouseCursor cursor) { 3052 version(OSXCocoa) 3053 featureNotImplemented(); 3054 else 3055 if(this.impl.curHidden <= 0) { 3056 static if(UsingSimpledisplayX11) { 3057 auto ch = cursor.cursorHandle; 3058 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 3059 } else version(Windows) { 3060 auto ch = cursor.cursorHandle; 3061 impl.currentCursor = ch; 3062 SetCursor(ch); // redraw without waiting for mouse movement to update 3063 } else featureNotImplemented(); 3064 } 3065 3066 } 3067 3068 /// What follows are the event handlers. These are set automatically 3069 /// by the eventLoop function, but are still public so you can change 3070 /// them later. wasPressed == true means key down. false == key up. 3071 3072 /// Handles a low-level keyboard event. Settable through setEventHandlers. 3073 void delegate(KeyEvent ke) handleKeyEvent; 3074 3075 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 3076 void delegate(dchar c) handleCharEvent; 3077 3078 /// Handles a timer pulse. Settable through setEventHandlers. 3079 void delegate() handlePulse; 3080 3081 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 3082 void delegate(bool) onFocusChange; 3083 3084 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 3085 * Sometimes it is easier to setup the delegate instead of subclassing. */ 3086 void delegate() onClosing; 3087 3088 /** Called when we received destroy notification. At this stage we cannot do much with our window 3089 * (as it is already dead, and it's native handle cannot be used), but we still can do some 3090 * last minute cleanup. */ 3091 void delegate() onDestroyed; 3092 3093 static if (UsingSimpledisplayX11) 3094 /** Called when Expose event comes. See Xlib manual to understand the arguments. 3095 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 3096 * You will probably never need to setup this handler, it is for very low-level stuff. 3097 * 3098 * WARNING! Xlib is multithread-locked when this handles is called! */ 3099 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 3100 3101 //version(Windows) 3102 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 3103 3104 private { 3105 int lastMouseX = int.min; 3106 int lastMouseY = int.min; 3107 void mdx(ref MouseEvent ev) { 3108 if(lastMouseX == int.min || lastMouseY == int.min) { 3109 ev.dx = 0; 3110 ev.dy = 0; 3111 } else { 3112 ev.dx = ev.x - lastMouseX; 3113 ev.dy = ev.y - lastMouseY; 3114 } 3115 3116 lastMouseX = ev.x; 3117 lastMouseY = ev.y; 3118 } 3119 } 3120 3121 /// Mouse event handler. Settable through setEventHandlers. 3122 void delegate(MouseEvent) handleMouseEvent; 3123 3124 /// use to redraw child widgets if you use system apis to add stuff 3125 void delegate() paintingFinished; 3126 3127 void delegate() paintingFinishedDg() { 3128 return paintingFinished; 3129 } 3130 3131 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 3132 /// for this to ever happen. 3133 void delegate(int width, int height) windowResized; 3134 3135 /++ 3136 Platform specific - handle any native message this window gets. 3137 3138 Note: this is called *in addition to* other event handlers, unless you either: 3139 3140 1) On X11, return 0 indicating that you handled it. Any other return value is simply discarded. 3141 3142 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. 3143 3144 On Windows, your delegate takes the form of `int delegate(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, out int mustReturn)`. 3145 3146 On X, it takes the form of `int delegate(XEvent)`. 3147 3148 History: 3149 In ancient versions, this was `static`. If you want a global hook, use [handleNativeGlobalEvent] instead. 3150 3151 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. 3152 +/ 3153 NativeEventHandler handleNativeEvent_; 3154 3155 @property NativeEventHandler handleNativeEvent() nothrow pure @nogc const @safe { 3156 return handleNativeEvent_; 3157 } 3158 @property void handleNativeEvent(NativeEventHandler neh) nothrow pure @nogc @safe { 3159 handleNativeEvent_ = neh; 3160 } 3161 3162 version(Windows) 3163 // compatibility shim with the old deprecated way 3164 // in this one, if you return 0, it means you must return. otherwise the ret value is ignored. 3165 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) { 3166 handleNativeEvent_ = delegate int(HWND h, UINT m, WPARAM w, LPARAM l, out int r) { 3167 auto ret = dg(h, m, w, l); 3168 if(ret == 0) 3169 r = 1; 3170 return ret; 3171 }; 3172 } 3173 3174 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 3175 /// If you used to use handleNativeEvent depending on it being static, just change it to use 3176 /// this instead and it will work the same way. 3177 __gshared NativeEventHandler handleNativeGlobalEvent; 3178 3179 // private: 3180 /// The native implementation is available, but you shouldn't use it unless you are 3181 /// familiar with the underlying operating system, don't mind depending on it, and 3182 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 3183 /// do what you need to do with handleNativeEvent instead. 3184 /// 3185 /// This is likely to eventually change to be just a struct holding platform-specific 3186 /// handles instead of a template mixin at some point because I'm not happy with the 3187 /// code duplication here (ironically). 3188 mixin NativeSimpleWindowImplementation!() impl; 3189 3190 /** 3191 This is in-process one-way (from anything to window) event sending mechanics. 3192 It is thread-safe, so it can be used in multi-threaded applications to send, 3193 for example, "wake up and repaint" events when thread completed some operation. 3194 This will allow to avoid using timer pulse to check events with synchronization, 3195 'cause event handler will be called in UI thread. You can stop guessing which 3196 pulse frequency will be enough for your app. 3197 Note that events handlers may be called in arbitrary order, i.e. last registered 3198 handler can be called first, and vice versa. 3199 */ 3200 public: 3201 /** Is our custom event queue empty? Can be used in simple cases to prevent 3202 * "spamming" window with events it can't cope with. 3203 * It is safe to call this from non-UI threads. 3204 */ 3205 @property bool eventQueueEmpty() () { 3206 synchronized(this) { 3207 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 3208 } 3209 return true; 3210 } 3211 3212 /** Does our custom event queue contains at least one with the given type? 3213 * Can be used in simple cases to prevent "spamming" window with events 3214 * it can't cope with. 3215 * It is safe to call this from non-UI threads. 3216 */ 3217 @property bool eventQueued(ET:Object) () { 3218 synchronized(this) { 3219 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 3220 if (!o.doProcess) { 3221 if (cast(ET)(o.evt)) return true; 3222 } 3223 } 3224 } 3225 return false; 3226 } 3227 3228 /++ 3229 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 3230 3231 History: 3232 Added May 12, 2021 3233 +/ 3234 void delegate(Exception e) nothrow eventUncaughtException; 3235 3236 /** Add listener for custom event. Can be used like this: 3237 * 3238 * --------------------- 3239 * auto eid = win.addEventListener((MyStruct evt) { ... }); 3240 * ... 3241 * win.removeEventListener(eid); 3242 * --------------------- 3243 * 3244 * Returns: 0 on failure (should never happen, so ignore it) 3245 * 3246 * $(WARNING Don't use this method in object destructors!) 3247 * 3248 * $(WARNING It is better to register all event handlers and don't remove 'em, 3249 * 'cause if event handler id counter will overflow, you won't be able 3250 * to register any more events.) 3251 */ 3252 uint addEventListener(ET:Object) (void delegate (ET) dg) { 3253 if (dg is null) return 0; // ignore empty handlers 3254 synchronized(this) { 3255 //FIXME: abort on overflow? 3256 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 3257 EventHandlerEntry e; 3258 e.dg = delegate (Object o) { 3259 if (auto co = cast(ET)o) { 3260 try { 3261 dg(co); 3262 } catch (Exception e) { 3263 // sorry! 3264 if(eventUncaughtException) 3265 eventUncaughtException(e); 3266 } 3267 return true; 3268 } 3269 return false; 3270 }; 3271 e.id = lastUsedHandlerId; 3272 auto optr = eventHandlers.ptr; 3273 eventHandlers ~= e; 3274 if (eventHandlers.ptr !is optr) { 3275 import core.memory : GC; 3276 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 3277 } 3278 return lastUsedHandlerId; 3279 } 3280 } 3281 3282 /// Remove event listener. It is safe to pass invalid event id here. 3283 /// $(WARNING Don't use this method in object destructors!) 3284 void removeEventListener() (uint id) { 3285 if (id == 0 || id > lastUsedHandlerId) return; 3286 synchronized(this) { 3287 foreach (immutable idx; 0..eventHandlers.length) { 3288 if (eventHandlers[idx].id == id) { 3289 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 3290 eventHandlers[$-1].dg = null; 3291 eventHandlers.length -= 1; 3292 eventHandlers.assumeSafeAppend; 3293 return; 3294 } 3295 } 3296 } 3297 } 3298 3299 /// Post event to queue. It is safe to call this from non-UI threads. 3300 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 3301 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3302 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3303 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 3304 if (this.closed) return false; // closed windows can't handle events 3305 3306 // remove all events of type `ET` 3307 void removeAllET () { 3308 uint eidx = 0, ec = eventQueueUsed; 3309 auto eptr = eventQueue.ptr; 3310 while (eidx < ec) { 3311 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 3312 if (cast(ET)eptr.evt !is null) { 3313 // i found her! 3314 if (inCustomEventProcessor) { 3315 // if we're in custom event processing loop, processor will clear it for us 3316 eptr.evt = null; 3317 ++eidx; 3318 ++eptr; 3319 } else { 3320 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3321 ec = --eventQueueUsed; 3322 // clear last event (it is already copied) 3323 eventQueue.ptr[ec].evt = null; 3324 } 3325 } else { 3326 ++eidx; 3327 ++eptr; 3328 } 3329 } 3330 } 3331 3332 if (evt is null) { 3333 if (replace) { synchronized(this) removeAllET(); } 3334 // ignore empty events, they can't be handled anyway 3335 return false; 3336 } 3337 3338 // add events even if no event FD/event object created yet 3339 synchronized(this) { 3340 if (replace) removeAllET(); 3341 if (eventQueueUsed == uint.max) return false; // just in case 3342 if (eventQueueUsed < eventQueue.length) { 3343 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 3344 } else { 3345 if (eventQueue.capacity == eventQueue.length) { 3346 // need to reallocate; do a trick to ensure that old array is cleared 3347 auto oarr = eventQueue; 3348 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3349 // just in case, do yet another check 3350 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 3351 import core.memory : GC; 3352 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 3353 } else { 3354 auto optr = eventQueue.ptr; 3355 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 3356 assert(eventQueue.ptr is optr); 3357 } 3358 ++eventQueueUsed; 3359 assert(eventQueueUsed == eventQueue.length); 3360 } 3361 if (!eventWakeUp()) { 3362 // can't wake up event processor, so there is no reason to keep the event 3363 assert(eventQueueUsed > 0); 3364 eventQueue[--eventQueueUsed].evt = null; 3365 return false; 3366 } 3367 return true; 3368 } 3369 } 3370 3371 /// Post event to queue. It is safe to call this from non-UI threads. 3372 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 3373 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 3374 bool postEvent(ET:Object) (ET evt, bool replace=false) { 3375 return postTimeout!ET(evt, 0, replace); 3376 } 3377 3378 private: 3379 private import core.time : MonoTime; 3380 3381 version(Posix) { 3382 __gshared int customEventFDRead = -1; 3383 __gshared int customEventFDWrite = -1; 3384 __gshared int customSignalFD = -1; 3385 } else version(Windows) { 3386 __gshared HANDLE customEventH = null; 3387 } 3388 3389 // wake up event processor 3390 static bool eventWakeUp () { 3391 version(X11) { 3392 import core.sys.posix.unistd : write; 3393 ulong n = 1; 3394 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 3395 return true; 3396 } else version(Windows) { 3397 if (customEventH !is null) SetEvent(customEventH); 3398 return true; 3399 } else { 3400 // not implemented for other OSes 3401 return false; 3402 } 3403 } 3404 3405 static struct QueuedEvent { 3406 Object evt; 3407 bool timed = false; 3408 MonoTime hittime = MonoTime.zero; 3409 bool doProcess = false; // process event at the current iteration (internal flag) 3410 3411 this (Object aevt, uint toutmsecs) { 3412 evt = aevt; 3413 if (toutmsecs > 0) { 3414 import core.time : msecs; 3415 timed = true; 3416 hittime = MonoTime.currTime+toutmsecs.msecs; 3417 } 3418 } 3419 } 3420 3421 alias CustomEventHandler = bool delegate (Object o) nothrow; 3422 static struct EventHandlerEntry { 3423 CustomEventHandler dg; 3424 uint id; 3425 } 3426 3427 uint lastUsedHandlerId; 3428 EventHandlerEntry[] eventHandlers; 3429 QueuedEvent[] eventQueue = null; 3430 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 3431 bool inCustomEventProcessor = false; // required to properly remove events 3432 3433 // process queued events and call custom event handlers 3434 // this will not process events posted from called handlers (such events are postponed for the next iteration) 3435 void processCustomEvents () { 3436 bool hasSomethingToDo = false; 3437 uint ecount; 3438 bool ocep; 3439 synchronized(this) { 3440 ocep = inCustomEventProcessor; 3441 inCustomEventProcessor = true; 3442 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 3443 auto ctt = MonoTime.currTime; 3444 bool hasEmpty = false; 3445 // mark events to process (this is required for `eventQueued()`) 3446 foreach (ref qe; eventQueue[0..ecount]) { 3447 if (qe.evt is null) { hasEmpty = true; continue; } 3448 if (qe.timed) { 3449 qe.doProcess = (qe.hittime <= ctt); 3450 } else { 3451 qe.doProcess = true; 3452 } 3453 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 3454 } 3455 if (!hasSomethingToDo) { 3456 // remove empty events 3457 if (hasEmpty) { 3458 uint eidx = 0, ec = eventQueueUsed; 3459 auto eptr = eventQueue.ptr; 3460 while (eidx < ec) { 3461 if (eptr.evt is null) { 3462 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3463 ec = --eventQueueUsed; 3464 eventQueue.ptr[ec].evt = null; // make GC life easier 3465 } else { 3466 ++eidx; 3467 ++eptr; 3468 } 3469 } 3470 } 3471 inCustomEventProcessor = ocep; 3472 return; 3473 } 3474 } 3475 // process marked events 3476 uint efree = 0; // non-processed events will be put at this index 3477 EventHandlerEntry[] eh; 3478 Object evt; 3479 foreach (immutable eidx; 0..ecount) { 3480 synchronized(this) { 3481 if (!eventQueue[eidx].doProcess) { 3482 // skip this event 3483 assert(efree <= eidx); 3484 if (efree != eidx) { 3485 // copy this event to queue start 3486 eventQueue[efree] = eventQueue[eidx]; 3487 eventQueue[eidx].evt = null; // just in case 3488 } 3489 ++efree; 3490 continue; 3491 } 3492 evt = eventQueue[eidx].evt; 3493 eventQueue[eidx].evt = null; // in case event handler will hit GC 3494 if (evt is null) continue; // just in case 3495 // try all handlers; this can be slow, but meh... 3496 eh = eventHandlers; 3497 } 3498 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 3499 evt = null; 3500 eh = null; 3501 } 3502 synchronized(this) { 3503 // move all unprocessed events to queue top; efree holds first "free index" 3504 foreach (immutable eidx; ecount..eventQueueUsed) { 3505 assert(efree <= eidx); 3506 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 3507 ++efree; 3508 } 3509 eventQueueUsed = efree; 3510 // wake up event processor on next event loop iteration if we have more queued events 3511 // also, remove empty events 3512 bool awaken = false; 3513 uint eidx = 0, ec = eventQueueUsed; 3514 auto eptr = eventQueue.ptr; 3515 while (eidx < ec) { 3516 if (eptr.evt is null) { 3517 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 3518 ec = --eventQueueUsed; 3519 eventQueue.ptr[ec].evt = null; // make GC life easier 3520 } else { 3521 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 3522 ++eidx; 3523 ++eptr; 3524 } 3525 } 3526 inCustomEventProcessor = ocep; 3527 } 3528 } 3529 3530 // for all windows in nativeMapping 3531 package static void processAllCustomEvents () { 3532 3533 cleanupQueue.process(); 3534 3535 justCommunication.processCustomEvents(); 3536 3537 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3538 if (sw is null || sw.closed) continue; 3539 sw.processCustomEvents(); 3540 } 3541 3542 runPendingRunInGuiThreadDelegates(); 3543 } 3544 3545 // 0: infinite (i.e. no scheduled events in queue) 3546 uint eventQueueTimeoutMSecs () { 3547 synchronized(this) { 3548 if (eventQueueUsed == 0) return 0; 3549 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3550 uint res = int.max; 3551 auto ctt = MonoTime.currTime; 3552 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 3553 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 3554 if (qe.doProcess) continue; // just in case 3555 if (!qe.timed) return 1; // minimal 3556 if (qe.hittime <= ctt) return 1; // minimal 3557 auto tms = (qe.hittime-ctt).total!"msecs"; 3558 if (tms < 1) tms = 1; // safety net 3559 if (tms >= int.max) tms = int.max-1; // and another safety net 3560 if (res > tms) res = cast(uint)tms; 3561 } 3562 return (res >= int.max ? 0 : res); 3563 } 3564 } 3565 3566 // for all windows in nativeMapping 3567 static uint eventAllQueueTimeoutMSecs () { 3568 uint res = uint.max; 3569 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3570 if (sw is null || sw.closed) continue; 3571 uint to = sw.eventQueueTimeoutMSecs(); 3572 if (to && to < res) { 3573 res = to; 3574 if (to == 1) break; // can't have less than this 3575 } 3576 } 3577 return (res >= int.max ? 0 : res); 3578 } 3579 3580 version(X11) { 3581 ResizeEvent pendingResizeEvent; 3582 } 3583 } 3584 3585 /++ 3586 Magic pseudo-window for just posting events to a global queue. 3587 3588 Not entirely supported, I might delete it at any time. 3589 3590 Added Nov 5, 2021. 3591 +/ 3592 __gshared SimpleWindow justCommunication = new SimpleWindow(NativeWindowHandle.init); 3593 3594 /* Drag and drop support { */ 3595 version(X11) { 3596 3597 } else version(Windows) { 3598 import core.sys.windows.uuid; 3599 import core.sys.windows.ole2; 3600 import core.sys.windows.oleidl; 3601 import core.sys.windows.objidl; 3602 import core.sys.windows.wtypes; 3603 3604 pragma(lib, "ole32"); 3605 void initDnd() { 3606 auto err = OleInitialize(null); 3607 if(err != S_OK && err != S_FALSE) 3608 throw new Exception("init");//err); 3609 } 3610 } 3611 /* } End drag and drop support */ 3612 3613 3614 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 3615 /// See [GenericCursor]. 3616 class MouseCursor { 3617 int osId; 3618 bool isStockCursor; 3619 private this(int osId) { 3620 this.osId = osId; 3621 this.isStockCursor = true; 3622 } 3623 3624 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 3625 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 3626 3627 version(Windows) { 3628 HCURSOR cursor_; 3629 HCURSOR cursorHandle() { 3630 if(cursor_ is null) 3631 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 3632 return cursor_; 3633 } 3634 3635 } else static if(UsingSimpledisplayX11) { 3636 Cursor cursor_ = None; 3637 int xDisplaySequence; 3638 3639 Cursor cursorHandle() { 3640 if(this.osId == None) 3641 return None; 3642 3643 // we need to reload if we on a new X connection 3644 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 3645 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 3646 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 3647 } 3648 return cursor_; 3649 } 3650 } 3651 } 3652 3653 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 3654 // https://tronche.com/gui/x/xlib/appendix/b/ 3655 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 3656 /// 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. 3657 enum GenericCursorType { 3658 Default, /// The default arrow pointer. 3659 Wait, /// A cursor indicating something is loading and the user must wait. 3660 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3661 Help, /// A cursor indicating the user can get help about the pointer location. 3662 Cross, /// A crosshair. 3663 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3664 Move, /// Pointer indicating movement is possible. May also be used as SizeAll. 3665 UpArrow, /// An arrow pointing straight up. 3666 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3667 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3668 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator). 3669 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator). 3670 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator). 3671 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator). 3672 3673 } 3674 3675 /* 3676 X_plus == css cell == Windows ? 3677 */ 3678 3679 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3680 static struct GenericCursor { 3681 static: 3682 /// 3683 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3684 static MouseCursor mc; 3685 3686 auto type = __traits(getMember, GenericCursorType, str); 3687 3688 if(mc is null) { 3689 3690 version(Windows) { 3691 int osId; 3692 final switch(type) { 3693 case GenericCursorType.Default: osId = IDC_ARROW; break; 3694 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3695 case GenericCursorType.Hand: osId = IDC_HAND; break; 3696 case GenericCursorType.Help: osId = IDC_HELP; break; 3697 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3698 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3699 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3700 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3701 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3702 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3703 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3704 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3705 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3706 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3707 } 3708 } else static if(UsingSimpledisplayX11) { 3709 int osId; 3710 final switch(type) { 3711 case GenericCursorType.Default: osId = None; break; 3712 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3713 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3714 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3715 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3716 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3717 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3718 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3719 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3720 3721 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3722 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3723 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3724 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3725 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3726 } 3727 3728 } else featureNotImplemented(); 3729 3730 mc = new MouseCursor(osId); 3731 } 3732 return mc; 3733 } 3734 } 3735 3736 3737 /++ 3738 If you want to get more control over the event loop, you can use this. 3739 3740 Typically though, you can just call [SimpleWindow.eventLoop] which forwards 3741 to `EventLoop.get.run`. 3742 +/ 3743 struct EventLoop { 3744 @disable this(); 3745 3746 /// Gets a reference to an existing event loop 3747 static EventLoop get() { 3748 return EventLoop(0, null); 3749 } 3750 3751 static void quitApplication() { 3752 EventLoop.get().exit(); 3753 } 3754 3755 private __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 3756 3757 /// Construct an application-global event loop for yourself 3758 /// See_Also: [SimpleWindow.setEventHandlers] 3759 this(long pulseTimeout, void delegate() handlePulse) { 3760 synchronized(monitor) { 3761 if(impl is null) { 3762 claimGuiThread(); 3763 version(sdpy_thread_checks) assert(thisIsGuiThread); 3764 impl = new EventLoopImpl(pulseTimeout, handlePulse); 3765 } else { 3766 if(pulseTimeout) { 3767 impl.pulseTimeout = pulseTimeout; 3768 impl.handlePulse = handlePulse; 3769 } 3770 } 3771 impl.refcount++; 3772 } 3773 } 3774 3775 ~this() { 3776 if(impl is null) 3777 return; 3778 impl.refcount--; 3779 if(impl.refcount == 0) { 3780 impl.dispose(); 3781 if(thisIsGuiThread) 3782 guiThreadFinalize(); 3783 } 3784 3785 } 3786 3787 this(this) { 3788 if(impl is null) 3789 return; 3790 impl.refcount++; 3791 } 3792 3793 /// Runs the event loop until the whileCondition, if present, returns false 3794 int run(bool delegate() whileCondition = null) { 3795 assert(impl !is null); 3796 impl.notExited = true; 3797 return impl.run(whileCondition); 3798 } 3799 3800 /// Exits the event loop 3801 void exit() { 3802 assert(impl !is null); 3803 impl.notExited = false; 3804 } 3805 3806 version(linux) 3807 ref void delegate(int) signalHandler() { 3808 assert(impl !is null); 3809 return impl.signalHandler; 3810 } 3811 3812 __gshared static EventLoopImpl* impl; 3813 } 3814 3815 version(linux) 3816 void delegate(int, int) globalHupHandler; 3817 3818 version(Posix) 3819 void makeNonBlocking(int fd) { 3820 import fcntl = core.sys.posix.fcntl; 3821 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 3822 if(flags == -1) 3823 throw new Exception("fcntl get"); 3824 flags |= fcntl.O_NONBLOCK; 3825 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 3826 if(s == -1) 3827 throw new Exception("fcntl set"); 3828 } 3829 3830 struct EventLoopImpl { 3831 int refcount; 3832 3833 bool notExited = true; 3834 3835 version(linux) { 3836 static import ep = core.sys.linux.epoll; 3837 static import unix = core.sys.posix.unistd; 3838 static import err = core.stdc.errno; 3839 import core.sys.linux.timerfd; 3840 3841 void delegate(int) signalHandler; 3842 } 3843 3844 version(X11) { 3845 int pulseFd = -1; 3846 version(linux) ep.epoll_event[16] events = void; 3847 } else version(Windows) { 3848 Timer pulser; 3849 HANDLE[] handles; 3850 } 3851 3852 3853 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3854 /// to call this, as it's not recommended to share window between threads. 3855 void mtLock () { 3856 version(X11) { 3857 XLockDisplay(this.display); 3858 } 3859 } 3860 3861 version(X11) 3862 auto display() { return XDisplayConnection.get; } 3863 3864 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 3865 /// to call this, as it's not recommended to share window between threads. 3866 void mtUnlock () { 3867 version(X11) { 3868 XUnlockDisplay(this.display); 3869 } 3870 } 3871 3872 version(with_eventloop) 3873 void initialize(long pulseTimeout) {} 3874 else 3875 void initialize(long pulseTimeout) { 3876 version(Windows) { 3877 if(pulseTimeout && handlePulse !is null) 3878 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 3879 3880 if (customEventH is null) { 3881 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 3882 if (customEventH !is null) { 3883 handles ~= customEventH; 3884 } else { 3885 // this is something that should not be; better be safe than sorry 3886 throw new Exception("can't create eventfd for custom event processing"); 3887 } 3888 } 3889 3890 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 3891 } 3892 3893 version(linux) { 3894 prepareEventLoop(); 3895 { 3896 auto display = XDisplayConnection.get; 3897 // adding Xlib file 3898 ep.epoll_event ev = void; 3899 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3900 ev.events = ep.EPOLLIN; 3901 ev.data.fd = display.fd; 3902 //import std.conv; 3903 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 3904 throw new Exception("add x fd");// ~ to!string(epollFd)); 3905 displayFd = display.fd; 3906 } 3907 3908 if(pulseTimeout && handlePulse !is null) { 3909 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 3910 if(pulseFd == -1) 3911 throw new Exception("pulse timer create failed"); 3912 3913 itimerspec value; 3914 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 3915 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 3916 3917 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 3918 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 3919 3920 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 3921 throw new Exception("couldn't make pulse timer"); 3922 3923 ep.epoll_event ev = void; 3924 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3925 ev.events = ep.EPOLLIN; 3926 ev.data.fd = pulseFd; 3927 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 3928 } 3929 3930 // eventfd for custom events 3931 if (customEventFDWrite == -1) { 3932 customEventFDWrite = eventfd(0, 0); 3933 customEventFDRead = customEventFDWrite; 3934 if (customEventFDRead >= 0) { 3935 ep.epoll_event ev = void; 3936 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3937 ev.events = ep.EPOLLIN; 3938 ev.data.fd = customEventFDRead; 3939 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 3940 } else { 3941 // this is something that should not be; better be safe than sorry 3942 throw new Exception("can't create eventfd for custom event processing"); 3943 } 3944 } 3945 3946 if (customSignalFD == -1) { 3947 import core.sys.linux.sys.signalfd; 3948 3949 sigset_t sigset; 3950 auto err = sigemptyset(&sigset); 3951 assert(!err); 3952 err = sigaddset(&sigset, SIGINT); 3953 assert(!err); 3954 err = sigaddset(&sigset, SIGHUP); 3955 assert(!err); 3956 err = sigprocmask(SIG_BLOCK, &sigset, null); 3957 assert(!err); 3958 3959 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 3960 assert(customSignalFD != -1); 3961 3962 ep.epoll_event ev = void; 3963 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3964 ev.events = ep.EPOLLIN; 3965 ev.data.fd = customSignalFD; 3966 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 3967 } 3968 } else version(Posix) { 3969 prepareEventLoop(); 3970 if (customEventFDRead == -1) { 3971 int[2] bfr; 3972 import core.sys.posix.unistd; 3973 auto ret = pipe(bfr); 3974 if(ret == -1) throw new Exception("pipe"); 3975 customEventFDRead = bfr[0]; 3976 customEventFDWrite = bfr[1]; 3977 } 3978 3979 } 3980 3981 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 3982 3983 version(linux) { 3984 this.mtLock(); 3985 scope(exit) this.mtUnlock(); 3986 XPending(display); // no, really 3987 } 3988 3989 disposed = false; 3990 } 3991 3992 bool disposed = true; 3993 version(X11) 3994 int displayFd = -1; 3995 3996 version(with_eventloop) 3997 void dispose() {} 3998 else 3999 void dispose() { 4000 disposed = true; 4001 version(X11) { 4002 if(pulseFd != -1) { 4003 import unix = core.sys.posix.unistd; 4004 unix.close(pulseFd); 4005 pulseFd = -1; 4006 } 4007 4008 version(linux) 4009 if(displayFd != -1) { 4010 // 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 4011 ep.epoll_event ev = void; 4012 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 4013 ev.events = ep.EPOLLIN; 4014 ev.data.fd = displayFd; 4015 //import std.conv; 4016 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 4017 displayFd = -1; 4018 } 4019 4020 } else version(Windows) { 4021 if(pulser !is null) { 4022 pulser.destroy(); 4023 pulser = null; 4024 } 4025 if (customEventH !is null) { 4026 CloseHandle(customEventH); 4027 customEventH = null; 4028 } 4029 } 4030 } 4031 4032 this(long pulseTimeout, void delegate() handlePulse) { 4033 this.pulseTimeout = pulseTimeout; 4034 this.handlePulse = handlePulse; 4035 initialize(pulseTimeout); 4036 } 4037 4038 private long pulseTimeout; 4039 void delegate() handlePulse; 4040 4041 ~this() { 4042 dispose(); 4043 } 4044 4045 version(Posix) 4046 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 4047 version(Posix) 4048 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 4049 version(linux) 4050 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 4051 version(Windows) 4052 ref auto customEventH() { return SimpleWindow.customEventH; } 4053 4054 version(with_eventloop) { 4055 int loopHelper(bool delegate() whileCondition) { 4056 // FIXME: whileCondition 4057 import arsd.eventloop; 4058 loop(); 4059 return 0; 4060 } 4061 } else 4062 int loopHelper(bool delegate() whileCondition) { 4063 version(X11) { 4064 bool done = false; 4065 4066 XFlush(display); 4067 insideXEventLoop = true; 4068 scope(exit) insideXEventLoop = false; 4069 4070 version(linux) { 4071 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 4072 bool forceXPending = false; 4073 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4074 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 4075 { 4076 this.mtLock(); 4077 scope(exit) this.mtUnlock(); 4078 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 4079 } 4080 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 4081 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 4082 if(nfds == -1) { 4083 if(err.errno == err.EINTR) { 4084 //if(forceXPending) goto xpending; 4085 continue; // interrupted by signal, just try again 4086 } 4087 throw new Exception("epoll wait failure"); 4088 } 4089 4090 SimpleWindow.processAllCustomEvents(); // anyway 4091 //version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 4092 foreach(idx; 0 .. nfds) { 4093 if(done) break; 4094 auto fd = events[idx].data.fd; 4095 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 4096 auto flags = events[idx].events; 4097 if(flags & ep.EPOLLIN) { 4098 if (fd == customSignalFD) { 4099 version(linux) { 4100 import core.sys.linux.sys.signalfd; 4101 import core.sys.posix.unistd : read; 4102 signalfd_siginfo info; 4103 read(customSignalFD, &info, info.sizeof); 4104 4105 auto sig = info.ssi_signo; 4106 4107 if(EventLoop.get.signalHandler !is null) { 4108 EventLoop.get.signalHandler()(sig); 4109 } else { 4110 EventLoop.get.exit(); 4111 } 4112 } 4113 } else if(fd == display.fd) { 4114 version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); } 4115 this.mtLock(); 4116 scope(exit) this.mtUnlock(); 4117 while(!done && XPending(display)) { 4118 done = doXNextEvent(this.display); 4119 } 4120 forceXPending = false; 4121 } else if(fd == pulseFd) { 4122 long expirationCount; 4123 // 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... 4124 4125 handlePulse(); 4126 4127 // read just to clear the buffer so poll doesn't trigger again 4128 // BTW I read AFTER the pulse because if the pulse handler takes 4129 // a lot of time to execute, we don't want the app to get stuck 4130 // in a loop of timer hits without a chance to do anything else 4131 // 4132 // IOW handlePulse happens at most once per pulse interval. 4133 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 4134 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 4135 } else if (fd == customEventFDRead) { 4136 // we have some custom events; process 'em 4137 import core.sys.posix.unistd : read; 4138 ulong n; 4139 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 4140 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 4141 //SimpleWindow.processAllCustomEvents(); 4142 } else { 4143 // some other timer 4144 version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); } 4145 4146 if(Timer* t = fd in Timer.mapping) 4147 (*t).trigger(); 4148 4149 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4150 (*pfr).ready(flags); 4151 4152 // or i might add support for other FDs too 4153 // but for now it is just timer 4154 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 4155 } 4156 } 4157 if(flags & ep.EPOLLHUP) { 4158 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 4159 (*pfr).hup(flags); 4160 if(globalHupHandler) 4161 globalHupHandler(fd, flags); 4162 } 4163 /+ 4164 } else { 4165 // not interested in OUT, we are just reading here. 4166 // 4167 // error or hup might also be reported 4168 // but it shouldn't here since we are only 4169 // using a few types of FD and Xlib will report 4170 // if it dies. 4171 // so instead of thoughtfully handling it, I'll 4172 // just throw. for now at least 4173 4174 throw new Exception("epoll did something else"); 4175 } 4176 +/ 4177 } 4178 // if we won't call `XPending()` here, libX may delay some internal event delivery. 4179 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 4180 xpending: 4181 if (!done && forceXPending) { 4182 this.mtLock(); 4183 scope(exit) this.mtUnlock(); 4184 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 4185 while(!done && XPending(display)) { 4186 done = doXNextEvent(this.display); 4187 } 4188 } 4189 } 4190 } else { 4191 // Generic fallback: yes to simple pulse support, 4192 // but NO timer support! 4193 4194 // FIXME: we could probably support the POSIX timer_create 4195 // signal-based option, but I'm in no rush to write it since 4196 // I prefer the fd-based functions. 4197 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 4198 4199 import core.sys.posix.poll; 4200 4201 pollfd[] pfds; 4202 pollfd[32] pfdsBuffer; 4203 auto len = PosixFdReader.mapping.length + 2; 4204 // FIXME: i should just reuse the buffer 4205 if(len < pfdsBuffer.length) 4206 pfds = pfdsBuffer[0 .. len]; 4207 else 4208 pfds = new pollfd[](len); 4209 4210 pfds[0].fd = display.fd; 4211 pfds[0].events = POLLIN; 4212 pfds[0].revents = 0; 4213 4214 int slot = 1; 4215 4216 if(customEventFDRead != -1) { 4217 pfds[slot].fd = customEventFDRead; 4218 pfds[slot].events = POLLIN; 4219 pfds[slot].revents = 0; 4220 4221 slot++; 4222 } 4223 4224 foreach(fd, obj; PosixFdReader.mapping) { 4225 if(!obj.enabled) continue; 4226 pfds[slot].fd = fd; 4227 pfds[slot].events = POLLIN; 4228 pfds[slot].revents = 0; 4229 4230 slot++; 4231 } 4232 4233 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 4234 if(ret == -1) throw new Exception("poll"); 4235 4236 if(ret == 0) { 4237 // FIXME it may not necessarily time out if events keep coming 4238 if(handlePulse !is null) 4239 handlePulse(); 4240 } else { 4241 foreach(s; 0 .. slot) { 4242 if(pfds[s].revents == 0) continue; 4243 4244 if(pfds[s].fd == display.fd) { 4245 while(!done && XPending(display)) { 4246 this.mtLock(); 4247 scope(exit) this.mtUnlock(); 4248 done = doXNextEvent(this.display); 4249 } 4250 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 4251 4252 import core.sys.posix.unistd : read; 4253 ulong n; 4254 read(customEventFDRead, &n, n.sizeof); 4255 SimpleWindow.processAllCustomEvents(); 4256 } else { 4257 auto obj = PosixFdReader.mapping[pfds[s].fd]; 4258 if(pfds[s].revents & POLLNVAL) { 4259 obj.dispose(); 4260 } else { 4261 obj.ready(pfds[s].revents); 4262 } 4263 } 4264 4265 ret--; 4266 if(ret == 0) break; 4267 } 4268 } 4269 } 4270 } 4271 } 4272 4273 version(Windows) { 4274 int ret = -1; 4275 MSG message; 4276 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 4277 eventLoopRound++; 4278 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 4279 auto waitResult = MsgWaitForMultipleObjectsEx( 4280 cast(int) handles.length, handles.ptr, 4281 (wto == 0 ? INFINITE : wto), /* timeout */ 4282 0x04FF, /* QS_ALLINPUT */ 4283 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 4284 4285 SimpleWindow.processAllCustomEvents(); // anyway 4286 enum WAIT_OBJECT_0 = 0; 4287 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 4288 auto h = handles[waitResult - WAIT_OBJECT_0]; 4289 if(auto e = h in WindowsHandleReader.mapping) { 4290 (*e).ready(); 4291 } 4292 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 4293 // message ready 4294 int count; 4295 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 4296 ret = GetMessage(&message, null, 0, 0); 4297 if(ret == -1) 4298 throw new Exception("GetMessage failed"); 4299 TranslateMessage(&message); 4300 DispatchMessage(&message); 4301 4302 count++; 4303 if(count > 10) 4304 break; // take the opportunity to catch up on other events 4305 4306 if(ret == 0) { // WM_QUIT 4307 EventLoop.quitApplication(); 4308 break; 4309 } 4310 } 4311 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 4312 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 4313 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 4314 // timeout, should never happen since we aren't using it 4315 } else if(waitResult == 0xFFFFFFFF) { 4316 // failed 4317 throw new Exception("MsgWaitForMultipleObjectsEx failed"); 4318 } else { 4319 // idk.... 4320 } 4321 } 4322 4323 // return message.wParam; 4324 return 0; 4325 } else { 4326 return 0; 4327 } 4328 } 4329 4330 int run(bool delegate() whileCondition = null) { 4331 if(disposed) 4332 initialize(this.pulseTimeout); 4333 4334 version(X11) { 4335 try { 4336 return loopHelper(whileCondition); 4337 } catch(XDisconnectException e) { 4338 if(e.userRequested) { 4339 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 4340 item.discardConnectionState(); 4341 XCloseDisplay(XDisplayConnection.display); 4342 } 4343 4344 XDisplayConnection.display = null; 4345 4346 this.dispose(); 4347 4348 throw e; 4349 } 4350 } else { 4351 return loopHelper(whileCondition); 4352 } 4353 } 4354 } 4355 4356 4357 /++ 4358 Provides an icon on the system notification area (also known as the system tray). 4359 4360 4361 If a notification area is not available with the NotificationIcon object is created, 4362 it will silently succeed and simply attempt to create one when an area becomes available. 4363 4364 4365 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. 4366 If this is wrong, pass -version=WindowsXP to dmd when compiling and it will 4367 use the older version. 4368 +/ 4369 version(OSXCocoa) {} else // NotYetImplementedException 4370 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 4371 4372 version(X11) { 4373 void recreateAfterDisconnect() { 4374 stateDiscarded = false; 4375 clippixmap = None; 4376 throw new Exception("NOT IMPLEMENTED"); 4377 } 4378 4379 bool stateDiscarded; 4380 void discardConnectionState() { 4381 stateDiscarded = true; 4382 } 4383 } 4384 4385 4386 version(X11) { 4387 Image img; 4388 4389 NativeEventHandler getNativeEventHandler() { 4390 return delegate int(XEvent e) { 4391 switch(e.type) { 4392 case EventType.Expose: 4393 //case EventType.VisibilityNotify: 4394 redraw(); 4395 break; 4396 case EventType.ClientMessage: 4397 version(sddddd) { 4398 import std.stdio; 4399 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 4400 writeln("\t", e.xclient.format); 4401 writeln("\t", e.xclient.data.l); 4402 } 4403 break; 4404 case EventType.ButtonPress: 4405 auto event = e.xbutton; 4406 if (onClick !is null || onClickEx !is null) { 4407 MouseButton mb = cast(MouseButton)0; 4408 switch (event.button) { 4409 case 1: mb = MouseButton.left; break; // left 4410 case 2: mb = MouseButton.middle; break; // middle 4411 case 3: mb = MouseButton.right; break; // right 4412 case 4: mb = MouseButton.wheelUp; break; // scroll up 4413 case 5: mb = MouseButton.wheelDown; break; // scroll down 4414 case 6: break; // idk 4415 case 7: break; // idk 4416 case 8: mb = MouseButton.backButton; break; 4417 case 9: mb = MouseButton.forwardButton; break; 4418 default: 4419 } 4420 if (mb) { 4421 try { onClick()(mb); } catch (Exception) {} 4422 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 4423 } 4424 } 4425 break; 4426 case EventType.EnterNotify: 4427 if (onEnter !is null) { 4428 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 4429 } 4430 break; 4431 case EventType.LeaveNotify: 4432 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 4433 break; 4434 case EventType.DestroyNotify: 4435 active = false; 4436 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 4437 break; 4438 case EventType.ConfigureNotify: 4439 auto event = e.xconfigure; 4440 this.width = event.width; 4441 this.height = event.height; 4442 //import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y); 4443 redraw(); 4444 break; 4445 default: return 1; 4446 } 4447 return 1; 4448 }; 4449 } 4450 4451 /* private */ void hideBalloon() { 4452 balloon.close(); 4453 version(with_timer) 4454 timer.destroy(); 4455 balloon = null; 4456 version(with_timer) 4457 timer = null; 4458 } 4459 4460 void redraw() { 4461 if (!active) return; 4462 4463 auto display = XDisplayConnection.get; 4464 auto gc = DefaultGC(display, DefaultScreen(display)); 4465 XClearWindow(display, nativeHandle); 4466 4467 XSetClipMask(display, gc, clippixmap); 4468 4469 XSetForeground(display, gc, 4470 cast(uint) 0 << 16 | 4471 cast(uint) 0 << 8 | 4472 cast(uint) 0); 4473 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 4474 4475 if (img is null) { 4476 XSetForeground(display, gc, 4477 cast(uint) 0 << 16 | 4478 cast(uint) 127 << 8 | 4479 cast(uint) 0); 4480 XFillArc(display, nativeHandle, 4481 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 4482 } else { 4483 int dx = 0; 4484 int dy = 0; 4485 if(width > img.width) 4486 dx = (width - img.width) / 2; 4487 if(height > img.height) 4488 dy = (height - img.height) / 2; 4489 XSetClipOrigin(display, gc, dx, dy); 4490 4491 if (img.usingXshm) 4492 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false); 4493 else 4494 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height); 4495 } 4496 XSetClipMask(display, gc, None); 4497 flushGui(); 4498 } 4499 4500 static Window getTrayOwner() { 4501 auto display = XDisplayConnection.get; 4502 auto i = cast(int) DefaultScreen(display); 4503 if(i < 10 && i >= 0) { 4504 static Atom atom; 4505 if(atom == None) 4506 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 4507 return XGetSelectionOwner(display, atom); 4508 } 4509 return None; 4510 } 4511 4512 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 4513 auto to = getTrayOwner(); 4514 auto display = XDisplayConnection.get; 4515 XEvent ev; 4516 ev.xclient.type = EventType.ClientMessage; 4517 ev.xclient.window = to; 4518 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 4519 ev.xclient.format = 32; 4520 ev.xclient.data.l[0] = CurrentTime; 4521 ev.xclient.data.l[1] = message; 4522 ev.xclient.data.l[2] = d1; 4523 ev.xclient.data.l[3] = d2; 4524 ev.xclient.data.l[4] = d3; 4525 4526 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 4527 } 4528 4529 private static NotificationAreaIcon[] activeIcons; 4530 4531 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 4532 private void newManager() { 4533 close(); 4534 createXWin(); 4535 4536 if(this.clippixmap) 4537 XFreePixmap(XDisplayConnection.get, clippixmap); 4538 if(this.originalMemoryImage) 4539 this.icon = this.originalMemoryImage; 4540 else if(this.img) 4541 this.icon = this.img; 4542 } 4543 4544 private void createXWin () { 4545 // create window 4546 auto display = XDisplayConnection.get; 4547 4548 // to check for MANAGER on root window to catch new/changed tray owners 4549 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 4550 // so if a thing does appear, we can handle it 4551 foreach(ai; activeIcons) 4552 if(ai is this) 4553 goto alreadythere; 4554 activeIcons ~= this; 4555 alreadythere: 4556 4557 // and check for an existing tray 4558 auto trayOwner = getTrayOwner(); 4559 if(trayOwner == None) 4560 return; 4561 //throw new Exception("No notification area found"); 4562 4563 Visual* v = cast(Visual*) CopyFromParent; 4564 /+ 4565 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 4566 if(visualProp !is null) { 4567 c_ulong[] info = cast(c_ulong[]) visualProp; 4568 if(info.length == 1) { 4569 auto vid = info[0]; 4570 int returned; 4571 XVisualInfo t; 4572 t.visualid = vid; 4573 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 4574 if(got !is null) { 4575 if(returned == 1) { 4576 v = got.visual; 4577 import std.stdio; 4578 writeln("using special visual ", *got); 4579 } 4580 XFree(got); 4581 } 4582 } 4583 } 4584 +/ 4585 4586 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null); 4587 assert(nativeWindow); 4588 4589 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4590 4591 nativeHandle = nativeWindow; 4592 4593 ///+ 4594 arch_ulong[2] info; 4595 info[0] = 0; 4596 info[1] = 1; 4597 4598 string title = this.name is null ? "simpledisplay.d program" : this.name; 4599 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4600 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4601 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4602 4603 XChangeProperty( 4604 display, 4605 nativeWindow, 4606 GetAtom!("_XEMBED_INFO", true)(display), 4607 GetAtom!("_XEMBED_INFO", true)(display), 4608 32 /* bits */, 4609 0 /*PropModeReplace*/, 4610 info.ptr, 4611 2); 4612 4613 import core.sys.posix.unistd; 4614 arch_ulong pid = getpid(); 4615 4616 XChangeProperty( 4617 display, 4618 nativeWindow, 4619 GetAtom!("_NET_WM_PID", true)(display), 4620 XA_CARDINAL, 4621 32 /* bits */, 4622 0 /*PropModeReplace*/, 4623 &pid, 4624 1); 4625 4626 updateNetWmIcon(); 4627 4628 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4629 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4630 XClassHint klass; 4631 XWMHints wh; 4632 XSizeHints size; 4633 klass.res_name = sdpyWindowClassStr; 4634 klass.res_class = sdpyWindowClassStr; 4635 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4636 } 4637 4638 // believe it or not, THIS is what xfce needed for the 9999 issue 4639 XSizeHints sh; 4640 c_long spr; 4641 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4642 sh.flags |= PMaxSize | PMinSize; 4643 // FIXME maybe nicer resizing 4644 sh.min_width = 16; 4645 sh.min_height = 16; 4646 sh.max_width = 16; 4647 sh.max_height = 16; 4648 XSetWMNormalHints(display, nativeWindow, &sh); 4649 4650 4651 //+/ 4652 4653 4654 XSelectInput(display, nativeWindow, 4655 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 4656 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 4657 4658 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 4659 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 4660 active = true; 4661 } 4662 4663 void updateNetWmIcon() { 4664 if(img is null) return; 4665 auto display = XDisplayConnection.get; 4666 // FIXME: ensure this is correct 4667 arch_ulong[] buffer; 4668 auto imgMi = img.toTrueColorImage; 4669 buffer ~= imgMi.width; 4670 buffer ~= imgMi.height; 4671 foreach(c; imgMi.imageData.colors) { 4672 arch_ulong b; 4673 b |= c.a << 24; 4674 b |= c.r << 16; 4675 b |= c.g << 8; 4676 b |= c.b; 4677 buffer ~= b; 4678 } 4679 4680 XChangeProperty( 4681 display, 4682 nativeHandle, 4683 GetAtom!"_NET_WM_ICON"(display), 4684 GetAtom!"CARDINAL"(display), 4685 32 /* bits */, 4686 0 /*PropModeReplace*/, 4687 buffer.ptr, 4688 cast(int) buffer.length); 4689 } 4690 4691 4692 4693 private SimpleWindow balloon; 4694 version(with_timer) 4695 private Timer timer; 4696 4697 private Window nativeHandle; 4698 private Pixmap clippixmap = None; 4699 private int width = 16; 4700 private int height = 16; 4701 private bool active = false; 4702 4703 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 4704 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 4705 void delegate () onLeave; /// X11 only. 4706 4707 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 4708 4709 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 4710 void getWindowRect (out int x, out int y, out int width, out int height) { 4711 if (!active) { width = 1; height = 1; return; } // 1: just in case 4712 Window dummyw; 4713 auto dpy = XDisplayConnection.get; 4714 //XWindowAttributes xwa; 4715 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 4716 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 4717 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 4718 width = this.width; 4719 height = this.height; 4720 } 4721 } 4722 4723 /+ 4724 What I actually want from this: 4725 4726 * set / change: icon, tooltip 4727 * handle: mouse click, right click 4728 * show: notification bubble. 4729 +/ 4730 4731 version(Windows) { 4732 WindowsIcon win32Icon; 4733 HWND hwnd; 4734 4735 NOTIFYICONDATAW data; 4736 4737 NativeEventHandler getNativeEventHandler() { 4738 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 4739 if(msg == WM_USER) { 4740 auto event = LOWORD(lParam); 4741 auto iconId = HIWORD(lParam); 4742 //auto x = GET_X_LPARAM(wParam); 4743 //auto y = GET_Y_LPARAM(wParam); 4744 switch(event) { 4745 case WM_LBUTTONDOWN: 4746 onClick()(MouseButton.left); 4747 break; 4748 case WM_RBUTTONDOWN: 4749 onClick()(MouseButton.right); 4750 break; 4751 case WM_MBUTTONDOWN: 4752 onClick()(MouseButton.middle); 4753 break; 4754 case WM_MOUSEMOVE: 4755 // sent, we could use it. 4756 break; 4757 case WM_MOUSEWHEEL: 4758 // NOT SENT 4759 break; 4760 //case NIN_KEYSELECT: 4761 //case NIN_SELECT: 4762 //break; 4763 default: {} 4764 } 4765 } 4766 return 0; 4767 }; 4768 } 4769 4770 enum NIF_SHOWTIP = 0x00000080; 4771 4772 private static struct NOTIFYICONDATAW { 4773 DWORD cbSize; 4774 HWND hWnd; 4775 UINT uID; 4776 UINT uFlags; 4777 UINT uCallbackMessage; 4778 HICON hIcon; 4779 WCHAR[128] szTip; 4780 DWORD dwState; 4781 DWORD dwStateMask; 4782 WCHAR[256] szInfo; 4783 union { 4784 UINT uTimeout; 4785 UINT uVersion; 4786 } 4787 WCHAR[64] szInfoTitle; 4788 DWORD dwInfoFlags; 4789 GUID guidItem; 4790 HICON hBalloonIcon; 4791 } 4792 4793 } 4794 4795 /++ 4796 Note that on Windows, only left, right, and middle buttons are sent. 4797 Mouse wheel buttons are NOT set, so don't rely on those events if your 4798 program is meant to be used on Windows too. 4799 +/ 4800 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 4801 // The canonical constructor for Windows needs the MemoryImage, so it is here, 4802 // but on X, we need an Image, so its canonical ctor is there. They should 4803 // forward to each other though. 4804 version(X11) { 4805 this.name = name; 4806 this.onClick = onClick; 4807 createXWin(); 4808 this.icon = icon; 4809 } else version(Windows) { 4810 this.onClick = onClick; 4811 this.win32Icon = new WindowsIcon(icon); 4812 4813 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 4814 4815 static bool registered = false; 4816 if(!registered) { 4817 WNDCLASSEX wc; 4818 wc.cbSize = wc.sizeof; 4819 wc.hInstance = hInstance; 4820 wc.lpfnWndProc = &WndProc; 4821 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 4822 if(!RegisterClassExW(&wc)) 4823 throw new WindowsApiException("RegisterClass"); 4824 registered = true; 4825 } 4826 4827 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 4828 if(hwnd is null) 4829 throw new Exception("CreateWindow"); 4830 4831 data.cbSize = data.sizeof; 4832 data.hWnd = hwnd; 4833 data.uID = cast(uint) cast(void*) this; 4834 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 4835 // NIF_INFO means show balloon 4836 data.uCallbackMessage = WM_USER; 4837 data.hIcon = this.win32Icon.hIcon; 4838 data.szTip = ""; // FIXME 4839 data.dwState = 0; // NIS_HIDDEN; // windows vista 4840 data.dwStateMask = NIS_HIDDEN; // windows vista 4841 4842 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 4843 4844 4845 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 4846 4847 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 4848 } else version(OSXCocoa) { 4849 throw new NotYetImplementedException(); 4850 } else static assert(0); 4851 } 4852 4853 /// ditto 4854 this(string name, Image icon, void delegate(MouseButton button) onClick) { 4855 version(X11) { 4856 this.onClick = onClick; 4857 this.name = name; 4858 createXWin(); 4859 this.icon = icon; 4860 } else version(Windows) { 4861 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 4862 } else version(OSXCocoa) { 4863 throw new NotYetImplementedException(); 4864 } else static assert(0); 4865 } 4866 4867 version(X11) { 4868 /++ 4869 X-specific extension (for now at least) 4870 +/ 4871 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 4872 this.onClickEx = onClickEx; 4873 createXWin(); 4874 if (icon !is null) this.icon = icon; 4875 } 4876 4877 /// ditto 4878 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 4879 this.onClickEx = onClickEx; 4880 createXWin(); 4881 this.icon = icon; 4882 } 4883 } 4884 4885 private void delegate (MouseButton button) onClick_; 4886 4887 /// 4888 @property final void delegate(MouseButton) onClick() { 4889 if(onClick_ is null) 4890 onClick_ = delegate void(MouseButton) {}; 4891 return onClick_; 4892 } 4893 4894 /// ditto 4895 @property final void onClick(void delegate(MouseButton) handler) { 4896 // I made this a property setter so we can wrap smaller arg 4897 // delegates and just forward all to onClickEx or something. 4898 onClick_ = handler; 4899 } 4900 4901 4902 string name_; 4903 @property void name(string n) { 4904 name_ = n; 4905 } 4906 4907 @property string name() { 4908 return name_; 4909 } 4910 4911 private MemoryImage originalMemoryImage; 4912 4913 /// 4914 @property void icon(MemoryImage i) { 4915 version(X11) { 4916 this.originalMemoryImage = i; 4917 if (!active) return; 4918 if (i !is null) { 4919 this.img = Image.fromMemoryImage(i); 4920 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 4921 //import std.stdio; writeln("using pixmap ", clippixmap); 4922 updateNetWmIcon(); 4923 redraw(); 4924 } else { 4925 if (this.img !is null) { 4926 this.img = null; 4927 redraw(); 4928 } 4929 } 4930 } else version(Windows) { 4931 this.win32Icon = new WindowsIcon(i); 4932 4933 data.uFlags = NIF_ICON; 4934 data.hIcon = this.win32Icon.hIcon; 4935 4936 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4937 } else version(OSXCocoa) { 4938 throw new NotYetImplementedException(); 4939 } else static assert(0); 4940 } 4941 4942 /// ditto 4943 @property void icon (Image i) { 4944 version(X11) { 4945 if (!active) return; 4946 if (i !is img) { 4947 originalMemoryImage = null; 4948 img = i; 4949 redraw(); 4950 } 4951 } else version(Windows) { 4952 this.icon(i is null ? null : i.toTrueColorImage()); 4953 } else version(OSXCocoa) { 4954 throw new NotYetImplementedException(); 4955 } else static assert(0); 4956 } 4957 4958 /++ 4959 Shows a balloon notification. You can only show one balloon at a time, if you call 4960 it twice while one is already up, the first balloon will be replaced. 4961 4962 4963 The user is free to block notifications and they will automatically disappear after 4964 a timeout period. 4965 4966 Params: 4967 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 4968 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 4969 icon = the icon to display with the notification. If null, it uses your existing icon. 4970 onclick = delegate called if the user clicks the balloon. (not yet implemented) 4971 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 4972 +/ 4973 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 4974 bool useCustom = true; 4975 version(libnotify) { 4976 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 4977 try { 4978 if(!active) return; 4979 4980 if(libnotify is null) { 4981 libnotify = new C_DynamicLibrary("libnotify.so"); 4982 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 4983 } 4984 4985 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 4986 4987 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 4988 4989 if(onclick) { 4990 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 4991 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); 4992 libnotify_action_delegates_count++; 4993 } 4994 4995 // FIXME icon 4996 4997 // set hint image-data 4998 // set default action for onclick 4999 5000 void* error; 5001 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 5002 5003 useCustom = false; 5004 } catch(Exception e) { 5005 5006 } 5007 } 5008 5009 version(X11) { 5010 if(useCustom) { 5011 if(!active) return; 5012 if(balloon) { 5013 hideBalloon(); 5014 } 5015 // I know there are two specs for this, but one is never 5016 // implemented by any window manager I have ever seen, and 5017 // the other is a bloated mess and too complicated for simpledisplay... 5018 // so doing my own little window instead. 5019 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 5020 5021 int x, y, width, height; 5022 getWindowRect(x, y, width, height); 5023 5024 int bx = x - balloon.width; 5025 int by = y - balloon.height; 5026 if(bx < 0) 5027 bx = x + width + balloon.width; 5028 if(by < 0) 5029 by = y + height; 5030 5031 // just in case, make sure it is actually on scren 5032 if(bx < 0) 5033 bx = 0; 5034 if(by < 0) 5035 by = 0; 5036 5037 balloon.move(bx, by); 5038 auto painter = balloon.draw(); 5039 painter.fillColor = Color(220, 220, 220); 5040 painter.outlineColor = Color.black; 5041 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 5042 auto iconWidth = icon is null ? 0 : icon.width; 5043 if(icon) 5044 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 5045 iconWidth += 6; // margin around the icon 5046 5047 // draw a close button 5048 painter.outlineColor = Color(44, 44, 44); 5049 painter.fillColor = Color(255, 255, 255); 5050 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 5051 painter.pen = Pen(Color.black, 3); 5052 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 5053 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 5054 painter.pen = Pen(Color.black, 1); 5055 painter.fillColor = Color(220, 220, 220); 5056 5057 // Draw the title and message 5058 painter.drawText(Point(4 + iconWidth, 4), title); 5059 painter.drawLine( 5060 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 5061 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 5062 ); 5063 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 5064 5065 balloon.setEventHandlers( 5066 (MouseEvent ev) { 5067 if(ev.type == MouseEventType.buttonPressed) { 5068 if(ev.x > balloon.width - 16 && ev.y < 16) 5069 hideBalloon(); 5070 else if(onclick) 5071 onclick(); 5072 } 5073 } 5074 ); 5075 balloon.show(); 5076 5077 version(with_timer) 5078 timer = new Timer(timeout, &hideBalloon); 5079 else {} // FIXME 5080 } 5081 } else version(Windows) { 5082 enum NIF_INFO = 0x00000010; 5083 5084 data.uFlags = NIF_INFO; 5085 5086 // FIXME: go back to the last valid unicode code point 5087 if(title.length > 40) 5088 title = title[0 .. 40]; 5089 if(message.length > 220) 5090 message = message[0 .. 220]; 5091 5092 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 5093 enum NIIF_LARGE_ICON = 0x00000020; 5094 enum NIIF_NOSOUND = 0x00000010; 5095 enum NIIF_USER = 0x00000004; 5096 enum NIIF_ERROR = 0x00000003; 5097 enum NIIF_WARNING = 0x00000002; 5098 enum NIIF_INFO = 0x00000001; 5099 enum NIIF_NONE = 0; 5100 5101 WCharzBuffer t = WCharzBuffer(title); 5102 WCharzBuffer m = WCharzBuffer(message); 5103 5104 t.copyInto(data.szInfoTitle); 5105 m.copyInto(data.szInfo); 5106 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 5107 5108 if(icon !is null) { 5109 auto i = new WindowsIcon(icon); 5110 data.hBalloonIcon = i.hIcon; 5111 data.dwInfoFlags |= NIIF_USER; 5112 } 5113 5114 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5115 } else version(OSXCocoa) { 5116 throw new NotYetImplementedException(); 5117 } else static assert(0); 5118 } 5119 5120 /// 5121 //version(Windows) 5122 void show() { 5123 version(X11) { 5124 if(!hidden) 5125 return; 5126 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 5127 hidden = false; 5128 } else version(Windows) { 5129 data.uFlags = NIF_STATE; 5130 data.dwState = 0; // NIS_HIDDEN; // windows vista 5131 data.dwStateMask = NIS_HIDDEN; // windows vista 5132 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5133 } else version(OSXCocoa) { 5134 throw new NotYetImplementedException(); 5135 } else static assert(0); 5136 } 5137 5138 version(X11) 5139 bool hidden = false; 5140 5141 /// 5142 //version(Windows) 5143 void hide() { 5144 version(X11) { 5145 if(hidden) 5146 return; 5147 hidden = true; 5148 XUnmapWindow(XDisplayConnection.get, nativeHandle); 5149 } else version(Windows) { 5150 data.uFlags = NIF_STATE; 5151 data.dwState = NIS_HIDDEN; // windows vista 5152 data.dwStateMask = NIS_HIDDEN; // windows vista 5153 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 5154 } else version(OSXCocoa) { 5155 throw new NotYetImplementedException(); 5156 } else static assert(0); 5157 } 5158 5159 /// 5160 void close () { 5161 version(X11) { 5162 if (active) { 5163 active = false; // event handler will set this too, but meh 5164 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 5165 XDestroyWindow(XDisplayConnection.get, nativeHandle); 5166 flushGui(); 5167 } 5168 } else version(Windows) { 5169 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 5170 } else version(OSXCocoa) { 5171 throw new NotYetImplementedException(); 5172 } else static assert(0); 5173 } 5174 5175 ~this() { 5176 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 5177 version(X11) 5178 if(clippixmap != None) 5179 XFreePixmap(XDisplayConnection.get, clippixmap); 5180 close(); 5181 } 5182 } 5183 5184 version(X11) 5185 /// Call `XFreePixmap` on the return value. 5186 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 5187 char[] data = new char[](i.width * i.height / 8 + 2); 5188 data[] = 0; 5189 5190 int bitOffset = 0; 5191 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 5192 ubyte v = c.a > 128 ? 1 : 0; 5193 data[bitOffset / 8] |= v << (bitOffset%8); 5194 bitOffset++; 5195 } 5196 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 5197 return handle; 5198 } 5199 5200 5201 // basic functions to make timers 5202 /** 5203 A timer that will trigger your function on a given interval. 5204 5205 5206 You create a timer with an interval and a callback. It will continue 5207 to fire on the interval until it is destroyed. 5208 5209 There are currently no one-off timers (instead, just create one and 5210 destroy it when it is triggered) nor are there pause/resume functions - 5211 the timer must again be destroyed and recreated if you want to pause it. 5212 5213 auto timer = new Timer(50, { it happened!; }); 5214 timer.destroy(); 5215 5216 Timers can only be expected to fire when the event loop is running and only 5217 once per iteration through the event loop. 5218 5219 History: 5220 Prior to December 9, 2020, a timer pulse set too high with a handler too 5221 slow could lock up the event loop. It now guarantees other things will 5222 get a chance to run between timer calls, even if that means not keeping up 5223 with the requested interval. 5224 */ 5225 version(with_timer) { 5226 class Timer { 5227 // FIXME: needs pause and unpause 5228 // FIXME: I might add overloads for ones that take a count of 5229 // how many elapsed since last time (on Windows, it will divide 5230 // the ticks thing given, on Linux it is just available) and 5231 // maybe one that takes an instance of the Timer itself too 5232 /// Create a timer with a callback when it triggers. 5233 this(int intervalInMilliseconds, void delegate() onPulse) { 5234 assert(onPulse !is null); 5235 5236 this.intervalInMilliseconds = intervalInMilliseconds; 5237 this.onPulse = onPulse; 5238 5239 version(Windows) { 5240 /* 5241 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5242 if(handle == 0) 5243 throw new Exception("SetTimer fail"); 5244 */ 5245 5246 // thanks to Archival 998 for the WaitableTimer blocks 5247 handle = CreateWaitableTimer(null, false, null); 5248 long initialTime = -intervalInMilliseconds; 5249 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5250 throw new Exception("SetWaitableTimer Failed"); 5251 5252 mapping[handle] = this; 5253 5254 } else version(linux) { 5255 static import ep = core.sys.linux.epoll; 5256 5257 import core.sys.linux.timerfd; 5258 5259 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5260 if(fd == -1) 5261 throw new Exception("timer create failed"); 5262 5263 mapping[fd] = this; 5264 5265 itimerspec value; 5266 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5267 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5268 5269 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 5270 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 5271 5272 if(timerfd_settime(fd, 0, &value, null) == -1) 5273 throw new Exception("couldn't make pulse timer"); 5274 5275 version(with_eventloop) { 5276 import arsd.eventloop; 5277 addFileEventListeners(fd, &trigger, null, null); 5278 } else { 5279 prepareEventLoop(); 5280 5281 ep.epoll_event ev = void; 5282 ev.events = ep.EPOLLIN; 5283 ev.data.fd = fd; 5284 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5285 } 5286 } else featureNotImplemented(); 5287 } 5288 5289 private int intervalInMilliseconds; 5290 5291 // just cuz I sometimes call it this. 5292 alias dispose = destroy; 5293 5294 /// Stop and destroy the timer object. 5295 void destroy() { 5296 version(Windows) { 5297 staticDestroy(handle); 5298 handle = null; 5299 } else version(linux) { 5300 staticDestroy(fd); 5301 fd = -1; 5302 } else featureNotImplemented(); 5303 } 5304 5305 version(Windows) 5306 static void staticDestroy(HANDLE handle) { 5307 if(handle) { 5308 // KillTimer(null, handle); 5309 CancelWaitableTimer(cast(void*)handle); 5310 mapping.remove(handle); 5311 CloseHandle(handle); 5312 } 5313 } 5314 else version(linux) 5315 static void staticDestroy(int fd) { 5316 if(fd != -1) { 5317 import unix = core.sys.posix.unistd; 5318 static import ep = core.sys.linux.epoll; 5319 5320 version(with_eventloop) { 5321 import arsd.eventloop; 5322 removeFileEventListeners(fd); 5323 } else { 5324 ep.epoll_event ev = void; 5325 ev.events = ep.EPOLLIN; 5326 ev.data.fd = fd; 5327 5328 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5329 } 5330 unix.close(fd); 5331 mapping.remove(fd); 5332 } 5333 } 5334 5335 ~this() { 5336 version(Windows) { if(handle) 5337 cleanupQueue.queue!staticDestroy(handle); 5338 } else version(linux) { if(fd != -1) 5339 cleanupQueue.queue!staticDestroy(fd); 5340 } 5341 } 5342 5343 5344 void changeTime(int intervalInMilliseconds) 5345 { 5346 this.intervalInMilliseconds = intervalInMilliseconds; 5347 version(Windows) 5348 { 5349 if(handle) 5350 { 5351 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 5352 long initialTime = -intervalInMilliseconds; 5353 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 5354 throw new Exception("couldn't change pulse timer"); 5355 } 5356 } 5357 } 5358 5359 5360 private: 5361 5362 void delegate() onPulse; 5363 5364 int lastEventLoopRoundTriggered; 5365 5366 void trigger() { 5367 version(linux) { 5368 import unix = core.sys.posix.unistd; 5369 long val; 5370 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 5371 } else version(Windows) { 5372 if(this.lastEventLoopRoundTriggered == eventLoopRound) 5373 return; // never try to actually run faster than the event loop 5374 lastEventLoopRoundTriggered = eventLoopRound; 5375 } else featureNotImplemented(); 5376 5377 onPulse(); 5378 } 5379 5380 version(Windows) 5381 void rearm() { 5382 5383 } 5384 5385 version(Windows) 5386 extern(Windows) 5387 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 5388 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 5389 if(Timer* t = timer in mapping) { 5390 try 5391 (*t).trigger(); 5392 catch(Exception e) { sdpy_abort(e); assert(0); } 5393 } 5394 } 5395 5396 version(Windows) { 5397 //UINT_PTR handle; 5398 //static Timer[UINT_PTR] mapping; 5399 HANDLE handle; 5400 __gshared Timer[HANDLE] mapping; 5401 } else version(linux) { 5402 int fd = -1; 5403 __gshared Timer[int] mapping; 5404 } else static assert(0, "timer not supported"); 5405 } 5406 } 5407 5408 version(Windows) 5409 private int eventLoopRound; 5410 5411 version(Windows) 5412 /// 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 5413 class WindowsHandleReader { 5414 /// 5415 this(void delegate() onReady, HANDLE handle) { 5416 this.onReady = onReady; 5417 this.handle = handle; 5418 5419 mapping[handle] = this; 5420 5421 enable(); 5422 } 5423 5424 /// 5425 void enable() { 5426 auto el = EventLoop.get().impl; 5427 el.handles ~= handle; 5428 } 5429 5430 /// 5431 void disable() { 5432 auto el = EventLoop.get().impl; 5433 for(int i = 0; i < el.handles.length; i++) { 5434 if(el.handles[i] is handle) { 5435 el.handles[i] = el.handles[$-1]; 5436 el.handles = el.handles[0 .. $-1]; 5437 return; 5438 } 5439 } 5440 } 5441 5442 void dispose() { 5443 disable(); 5444 if(handle) 5445 mapping.remove(handle); 5446 handle = null; 5447 } 5448 5449 void ready() { 5450 if(onReady) 5451 onReady(); 5452 } 5453 5454 HANDLE handle; 5455 void delegate() onReady; 5456 5457 __gshared WindowsHandleReader[HANDLE] mapping; 5458 } 5459 5460 version(Posix) 5461 /// Lets you add files to the event loop for reading. Use at your own risk. 5462 class PosixFdReader { 5463 /// 5464 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5465 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 5466 } 5467 5468 /// 5469 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5470 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 5471 } 5472 5473 /// 5474 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 5475 this.onReady = onReady; 5476 this.fd = fd; 5477 this.captureWrites = captureWrites; 5478 this.captureReads = captureReads; 5479 5480 mapping[fd] = this; 5481 5482 version(with_eventloop) { 5483 import arsd.eventloop; 5484 addFileEventListeners(fd, &readyel); 5485 } else { 5486 enable(); 5487 } 5488 } 5489 5490 bool captureReads; 5491 bool captureWrites; 5492 5493 version(with_eventloop) {} else 5494 /// 5495 void enable() { 5496 prepareEventLoop(); 5497 5498 enabled = true; 5499 5500 version(linux) { 5501 static import ep = core.sys.linux.epoll; 5502 ep.epoll_event ev = void; 5503 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5504 //import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites); 5505 ev.data.fd = fd; 5506 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 5507 } else { 5508 5509 } 5510 } 5511 5512 version(with_eventloop) {} else 5513 /// 5514 void disable() { 5515 prepareEventLoop(); 5516 5517 enabled = false; 5518 5519 version(linux) { 5520 static import ep = core.sys.linux.epoll; 5521 ep.epoll_event ev = void; 5522 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 5523 //import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites); 5524 ev.data.fd = fd; 5525 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 5526 } 5527 } 5528 5529 version(with_eventloop) {} else 5530 /// 5531 void dispose() { 5532 if(enabled) 5533 disable(); 5534 if(fd != -1) 5535 mapping.remove(fd); 5536 fd = -1; 5537 } 5538 5539 void delegate(int, bool, bool) onReady; 5540 5541 version(with_eventloop) 5542 void readyel() { 5543 onReady(fd, true, true); 5544 } 5545 5546 void ready(uint flags) { 5547 version(linux) { 5548 static import ep = core.sys.linux.epoll; 5549 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 5550 } else { 5551 import core.sys.posix.poll; 5552 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 5553 } 5554 } 5555 5556 void hup(uint flags) { 5557 if(onHup) 5558 onHup(); 5559 } 5560 5561 void delegate() onHup; 5562 5563 int fd = -1; 5564 private bool enabled; 5565 __gshared PosixFdReader[int] mapping; 5566 } 5567 5568 // basic functions to access the clipboard 5569 /+ 5570 5571 5572 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 5573 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 5574 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5575 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 5576 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 5577 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 5578 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 5579 5580 +/ 5581 5582 /++ 5583 this does a delegate because it is actually an async call on X... 5584 the receiver may never be called if the clipboard is empty or unavailable 5585 gets plain text from the clipboard. 5586 +/ 5587 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 5588 version(Windows) { 5589 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5590 if(OpenClipboard(hwndOwner) == 0) 5591 throw new Exception("OpenClipboard"); 5592 scope(exit) 5593 CloseClipboard(); 5594 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 5595 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 5596 5597 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 5598 scope(exit) 5599 GlobalUnlock(dataHandle); 5600 5601 // FIXME: CR/LF conversions 5602 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 5603 int len = 0; 5604 auto d = data; 5605 while(*d) { 5606 d++; 5607 len++; 5608 } 5609 string s; 5610 s.reserve(len); 5611 foreach(dchar ch; data[0 .. len]) { 5612 s ~= ch; 5613 } 5614 receiver(s); 5615 } 5616 } 5617 } else version(X11) { 5618 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5619 } else version(OSXCocoa) { 5620 throw new NotYetImplementedException(); 5621 } else static assert(0); 5622 } 5623 5624 // FIXME: a clipboard listener might be cool btw 5625 5626 /++ 5627 this does a delegate because it is actually an async call on X... 5628 the receiver may never be called if the clipboard is empty or unavailable 5629 gets image from the clipboard. 5630 5631 templated because it introduces an optional dependency on arsd.bmp 5632 +/ 5633 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 5634 version(Windows) { 5635 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5636 if(OpenClipboard(hwndOwner) == 0) 5637 throw new Exception("OpenClipboard"); 5638 scope(exit) 5639 CloseClipboard(); 5640 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 5641 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 5642 scope(exit) 5643 GlobalUnlock(dataHandle); 5644 5645 auto len = GlobalSize(dataHandle); 5646 5647 import arsd.bmp; 5648 auto img = readBmp(data[0 .. len], false); 5649 receiver(img); 5650 } 5651 } 5652 } else version(X11) { 5653 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5654 } else version(OSXCocoa) { 5655 throw new NotYetImplementedException(); 5656 } else static assert(0); 5657 } 5658 5659 version(Windows) 5660 struct WCharzBuffer { 5661 wchar[] buffer; 5662 wchar[256] staticBuffer = void; 5663 5664 size_t length() { 5665 return buffer.length; 5666 } 5667 5668 wchar* ptr() { 5669 return buffer.ptr; 5670 } 5671 5672 wchar[] slice() { 5673 return buffer; 5674 } 5675 5676 void copyInto(R)(ref R r) { 5677 static if(is(R == wchar[N], size_t N)) { 5678 r[0 .. this.length] = slice[]; 5679 r[this.length] = 0; 5680 } else static assert(0, "can only copy into wchar[n], not " ~ R.stringof); 5681 } 5682 5683 /++ 5684 conversionFlags = [WindowsStringConversionFlags] 5685 +/ 5686 this(in char[] data, int conversionFlags = 0) { 5687 conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name 5688 auto sz = sizeOfConvertedWstring(data, conversionFlags); 5689 if(sz > staticBuffer.length) 5690 buffer = new wchar[](sz); 5691 else 5692 buffer = staticBuffer[]; 5693 5694 buffer = makeWindowsString(data, buffer, conversionFlags); 5695 } 5696 } 5697 5698 version(Windows) 5699 int sizeOfConvertedWstring(in char[] s, int conversionFlags) { 5700 int size = 0; 5701 5702 if(conversionFlags & WindowsStringConversionFlags.convertNewLines) { 5703 // need to convert line endings, which means the length will get bigger. 5704 5705 // BTW I betcha this could be faster with some simd stuff. 5706 char last; 5707 foreach(char ch; s) { 5708 if(ch == 10 && last != 13) 5709 size++; // will add a 13 before it... 5710 size++; 5711 last = ch; 5712 } 5713 } else { 5714 // no conversion necessary, just estimate based on length 5715 /* 5716 I don't think there's any string with a longer length 5717 in code units when encoded in UTF-16 than it has in UTF-8. 5718 This will probably over allocate, but that's OK. 5719 */ 5720 size = cast(int) s.length; 5721 } 5722 5723 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) 5724 size++; 5725 5726 return size; 5727 } 5728 5729 version(Windows) 5730 enum WindowsStringConversionFlags : int { 5731 zeroTerminate = 1, 5732 convertNewLines = 2, 5733 } 5734 5735 version(Windows) 5736 class WindowsApiException : Exception { 5737 char[256] buffer; 5738 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 5739 assert(msg.length < 100); 5740 5741 auto error = GetLastError(); 5742 buffer[0 .. msg.length] = msg; 5743 buffer[msg.length] = ' '; 5744 5745 int pos = cast(int) msg.length + 1; 5746 5747 if(error == 0) 5748 buffer[pos++] = '0'; 5749 else { 5750 5751 auto ec = error; 5752 auto init = pos; 5753 while(ec) { 5754 buffer[pos++] = (ec % 10) + '0'; 5755 ec /= 10; 5756 } 5757 5758 buffer[pos++] = ' '; 5759 5760 size_t size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, null, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &(buffer[pos]), cast(DWORD) buffer.length - pos, null); 5761 5762 pos += size; 5763 } 5764 5765 5766 super(cast(string) buffer[0 .. pos], file, line, next); 5767 } 5768 } 5769 5770 class ErrnoApiException : Exception { 5771 char[256] buffer; 5772 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 5773 assert(msg.length < 100); 5774 5775 import core.stdc.errno; 5776 auto error = errno; 5777 buffer[0 .. msg.length] = msg; 5778 buffer[msg.length] = ' '; 5779 5780 int pos = cast(int) msg.length + 1; 5781 5782 if(error == 0) 5783 buffer[pos++] = '0'; 5784 else { 5785 auto init = pos; 5786 while(error) { 5787 buffer[pos++] = (error % 10) + '0'; 5788 error /= 10; 5789 } 5790 for(int i = 0; i < (pos - init) / 2; i++) { 5791 char c = buffer[i + init]; 5792 buffer[i + init] = buffer[pos - (i + init) - 1]; 5793 buffer[pos - (i + init) - 1] = c; 5794 } 5795 } 5796 5797 5798 super(cast(string) buffer[0 .. pos], file, line, next); 5799 } 5800 5801 } 5802 5803 version(Windows) 5804 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) { 5805 if(str.length == 0) 5806 return null; 5807 5808 int pos = 0; 5809 dchar last; 5810 foreach(dchar c; str) { 5811 if(c <= 0xFFFF) { 5812 if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13) 5813 buffer[pos++] = 13; 5814 buffer[pos++] = cast(wchar) c; 5815 } else if(c <= 0x10FFFF) { 5816 buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 5817 buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); 5818 } 5819 5820 last = c; 5821 } 5822 5823 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) { 5824 buffer[pos] = 0; 5825 } 5826 5827 return buffer[0 .. pos]; 5828 } 5829 5830 version(Windows) 5831 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) { 5832 if(str.length == 0) 5833 return null; 5834 5835 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null); 5836 if(got == 0) { 5837 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 5838 throw new Exception("not enough buffer"); 5839 else 5840 throw new Exception("conversion"); // FIXME: GetLastError 5841 } 5842 return buffer[0 .. got]; 5843 } 5844 5845 version(Windows) 5846 string makeUtf8StringFromWindowsString(in wchar[] str) { 5847 char[] buffer; 5848 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null); 5849 buffer.length = got; 5850 5851 // it is unique because we just allocated it above! 5852 return cast(string) makeUtf8StringFromWindowsString(str, buffer); 5853 } 5854 5855 version(Windows) 5856 string makeUtf8StringFromWindowsString(wchar* str) { 5857 char[] buffer; 5858 auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null); 5859 buffer.length = got; 5860 5861 got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null); 5862 if(got == 0) { 5863 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 5864 throw new Exception("not enough buffer"); 5865 else 5866 throw new Exception("conversion"); // FIXME: GetLastError 5867 } 5868 return cast(string) buffer[0 .. got]; 5869 } 5870 5871 int findIndexOfZero(in wchar[] str) { 5872 foreach(idx, wchar ch; str) 5873 if(ch == 0) 5874 return cast(int) idx; 5875 return cast(int) str.length; 5876 } 5877 int findIndexOfZero(in char[] str) { 5878 foreach(idx, char ch; str) 5879 if(ch == 0) 5880 return cast(int) idx; 5881 return cast(int) str.length; 5882 } 5883 5884 /// Copies some text to the clipboard. 5885 void setClipboardText(SimpleWindow clipboardOwner, string text) { 5886 assert(clipboardOwner !is null); 5887 version(Windows) { 5888 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5889 throw new Exception("OpenClipboard"); 5890 scope(exit) 5891 CloseClipboard(); 5892 EmptyClipboard(); 5893 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5894 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 5895 if(handle is null) throw new Exception("GlobalAlloc"); 5896 if(auto data = cast(wchar*) GlobalLock(handle)) { 5897 auto slice = data[0 .. sz]; 5898 scope(failure) 5899 GlobalUnlock(handle); 5900 5901 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5902 5903 GlobalUnlock(handle); 5904 SetClipboardData(CF_UNICODETEXT, handle); 5905 } 5906 } else version(X11) { 5907 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 5908 } else version(OSXCocoa) { 5909 throw new NotYetImplementedException(); 5910 } else static assert(0); 5911 } 5912 5913 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 5914 assert(clipboardOwner !is null); 5915 version(Windows) { 5916 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5917 throw new Exception("OpenClipboard"); 5918 scope(exit) 5919 CloseClipboard(); 5920 EmptyClipboard(); 5921 5922 5923 import arsd.bmp; 5924 ubyte[] mdata; 5925 mdata.reserve(img.width * img.height); 5926 void sink(ubyte b) { 5927 mdata ~= b; 5928 } 5929 writeBmpIndirect(img, &sink, false); 5930 5931 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 5932 if(handle is null) throw new Exception("GlobalAlloc"); 5933 if(auto data = cast(ubyte*) GlobalLock(handle)) { 5934 auto slice = data[0 .. mdata.length]; 5935 scope(failure) 5936 GlobalUnlock(handle); 5937 5938 slice[] = mdata[]; 5939 5940 GlobalUnlock(handle); 5941 SetClipboardData(CF_DIB, handle); 5942 } 5943 } else version(X11) { 5944 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 5945 mixin X11SetSelectionHandler_Basics; 5946 private const(ubyte)[] mdata; 5947 private const(ubyte)[] mdata_original; 5948 this(MemoryImage img) { 5949 import arsd.bmp; 5950 5951 mdata.reserve(img.width * img.height); 5952 void sink(ubyte b) { 5953 mdata ~= b; 5954 } 5955 writeBmpIndirect(img, &sink, true); 5956 5957 mdata_original = mdata; 5958 } 5959 5960 Atom[] availableFormats() { 5961 auto display = XDisplayConnection.get; 5962 return [ 5963 GetAtom!"image/bmp"(display), 5964 GetAtom!"TARGETS"(display) 5965 ]; 5966 } 5967 5968 ubyte[] getData(Atom format, return scope ubyte[] data) { 5969 if(mdata.length < data.length) { 5970 data[0 .. mdata.length] = mdata[]; 5971 auto ret = data[0 .. mdata.length]; 5972 mdata = mdata[$..$]; 5973 return ret; 5974 } else { 5975 data[] = mdata[0 .. data.length]; 5976 mdata = mdata[data.length .. $]; 5977 return data[]; 5978 } 5979 } 5980 5981 void done() { 5982 mdata = mdata_original; 5983 } 5984 } 5985 5986 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 5987 } else version(OSXCocoa) { 5988 throw new NotYetImplementedException(); 5989 } else static assert(0); 5990 } 5991 5992 5993 version(X11) { 5994 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 5995 5996 private Atom*[] interredAtoms; // for discardAndRecreate 5997 5998 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 5999 /// Platform-specific for X11. 6000 /// History: On February 21, 2021, I changed the default value of `create` to be true. 6001 @property Atom GetAtom(string name, bool create = true)(Display* display) { 6002 static Atom a; 6003 if(!a) { 6004 a = XInternAtom(display, name, !create); 6005 interredAtoms ~= &a; 6006 } 6007 if(a == None) 6008 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 6009 return a; 6010 } 6011 6012 /// Platform-specific for X11 - gets atom names as a string. 6013 string getAtomName(Atom atom, Display* display) { 6014 auto got = XGetAtomName(display, atom); 6015 scope(exit) XFree(got); 6016 import core.stdc.string; 6017 string s = got[0 .. strlen(got)].idup; 6018 return s; 6019 } 6020 6021 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later. 6022 void setPrimarySelection(SimpleWindow window, string text) { 6023 setX11Selection!"PRIMARY"(window, text); 6024 } 6025 6026 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later. 6027 void setSecondarySelection(SimpleWindow window, string text) { 6028 setX11Selection!"SECONDARY"(window, text); 6029 } 6030 6031 interface X11SetSelectionHandler { 6032 // should include TARGETS right now 6033 Atom[] availableFormats(); 6034 // Return the slice of data you filled, empty slice if done. 6035 // this is to support the incremental thing 6036 ubyte[] getData(Atom format, return scope ubyte[] data); 6037 6038 void done(); 6039 6040 void handleRequest(XEvent); 6041 6042 bool matchesIncr(Window, Atom); 6043 void sendMoreIncr(XPropertyEvent*); 6044 } 6045 6046 mixin template X11SetSelectionHandler_Basics() { 6047 Window incrWindow; 6048 Atom incrAtom; 6049 Atom selectionAtom; 6050 Atom formatAtom; 6051 ubyte[] toSend; 6052 bool matchesIncr(Window w, Atom a) { 6053 return incrAtom && incrAtom == a && w == incrWindow; 6054 } 6055 void sendMoreIncr(XPropertyEvent* event) { 6056 auto display = XDisplayConnection.get; 6057 6058 XChangeProperty (display, 6059 incrWindow, 6060 incrAtom, 6061 formatAtom, 6062 8 /* bits */, PropModeReplace, 6063 toSend.ptr, cast(int) toSend.length); 6064 6065 if(toSend.length != 0) { 6066 toSend = this.getData(formatAtom, toSend[]); 6067 } else { 6068 this.done(); 6069 incrWindow = None; 6070 incrAtom = None; 6071 selectionAtom = None; 6072 formatAtom = None; 6073 toSend = null; 6074 } 6075 } 6076 void handleRequest(XEvent ev) { 6077 6078 auto display = XDisplayConnection.get; 6079 6080 XSelectionRequestEvent* event = &ev.xselectionrequest; 6081 XSelectionEvent selectionEvent; 6082 selectionEvent.type = EventType.SelectionNotify; 6083 selectionEvent.display = event.display; 6084 selectionEvent.requestor = event.requestor; 6085 selectionEvent.selection = event.selection; 6086 selectionEvent.time = event.time; 6087 selectionEvent.target = event.target; 6088 6089 bool supportedType() { 6090 foreach(t; this.availableFormats()) 6091 if(t == event.target) 6092 return true; 6093 return false; 6094 } 6095 6096 if(event.property == None) { 6097 selectionEvent.property = event.target; 6098 6099 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6100 XFlush(display); 6101 } if(event.target == GetAtom!"TARGETS"(display)) { 6102 /* respond with the supported types */ 6103 auto tlist = this.availableFormats(); 6104 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 6105 selectionEvent.property = event.property; 6106 6107 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6108 XFlush(display); 6109 } else if(supportedType()) { 6110 auto buffer = new ubyte[](1024 * 64); 6111 auto toSend = this.getData(event.target, buffer[]); 6112 6113 if(toSend.length < 32 * 1024) { 6114 // small enough to send directly... 6115 selectionEvent.property = event.property; 6116 XChangeProperty (display, 6117 selectionEvent.requestor, 6118 selectionEvent.property, 6119 event.target, 6120 8 /* bits */, 0 /* PropModeReplace */, 6121 toSend.ptr, cast(int) toSend.length); 6122 6123 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6124 XFlush(display); 6125 } else { 6126 // large, let's send incrementally 6127 arch_ulong l = toSend.length; 6128 6129 // if I wanted other events from this window don't want to clear that out.... 6130 XWindowAttributes xwa; 6131 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 6132 6133 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 6134 6135 incrWindow = event.requestor; 6136 incrAtom = event.property; 6137 formatAtom = event.target; 6138 selectionAtom = event.selection; 6139 this.toSend = toSend; 6140 6141 selectionEvent.property = event.property; 6142 XChangeProperty (display, 6143 selectionEvent.requestor, 6144 selectionEvent.property, 6145 GetAtom!"INCR"(display), 6146 32 /* bits */, PropModeReplace, 6147 &l, 1); 6148 6149 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 6150 XFlush(display); 6151 } 6152 //if(after) 6153 //after(); 6154 } else { 6155 debug(sdpy_clip) { 6156 import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display)); 6157 } 6158 selectionEvent.property = None; // I don't know how to handle this type... 6159 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 6160 XFlush(display); 6161 } 6162 } 6163 } 6164 6165 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 6166 mixin X11SetSelectionHandler_Basics; 6167 private const(ubyte)[] text; 6168 private const(ubyte)[] text_original; 6169 this(string text) { 6170 this.text = cast(const ubyte[]) text; 6171 this.text_original = this.text; 6172 } 6173 Atom[] availableFormats() { 6174 auto display = XDisplayConnection.get; 6175 return [ 6176 GetAtom!"UTF8_STRING"(display), 6177 GetAtom!"text/plain"(display), 6178 XA_STRING, 6179 GetAtom!"TARGETS"(display) 6180 ]; 6181 } 6182 6183 ubyte[] getData(Atom format, return scope ubyte[] data) { 6184 if(text.length < data.length) { 6185 data[0 .. text.length] = text[]; 6186 return data[0 .. text.length]; 6187 } else { 6188 data[] = text[0 .. data.length]; 6189 text = text[data.length .. $]; 6190 return data[]; 6191 } 6192 } 6193 6194 void done() { 6195 text = text_original; 6196 } 6197 } 6198 6199 /// 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?!) 6200 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 6201 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 6202 } 6203 6204 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 6205 assert(window !is null); 6206 6207 auto display = XDisplayConnection.get(); 6208 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 6209 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 6210 else Atom a = GetAtom!atomName(display); 6211 6212 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 6213 6214 window.impl.setSelectionHandlers[a] = data; 6215 } 6216 6217 /// 6218 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 6219 getX11Selection!"PRIMARY"(window, handler); 6220 } 6221 6222 // added July 28, 2020 6223 // undocumented as experimental tho 6224 interface X11GetSelectionHandler { 6225 void handleData(Atom target, in ubyte[] data); 6226 Atom findBestFormat(Atom[] answer); 6227 6228 void prepareIncremental(Window, Atom); 6229 bool matchesIncr(Window, Atom); 6230 void handleIncrData(Atom, in ubyte[] data); 6231 } 6232 6233 mixin template X11GetSelectionHandler_Basics() { 6234 Window incrWindow; 6235 Atom incrAtom; 6236 6237 void prepareIncremental(Window w, Atom a) { 6238 incrWindow = w; 6239 incrAtom = a; 6240 } 6241 bool matchesIncr(Window w, Atom a) { 6242 return incrWindow == w && incrAtom == a; 6243 } 6244 6245 Atom incrFormatAtom; 6246 ubyte[] incrData; 6247 void handleIncrData(Atom format, in ubyte[] data) { 6248 incrFormatAtom = format; 6249 6250 if(data.length) 6251 incrData ~= data; 6252 else 6253 handleData(incrFormatAtom, incrData); 6254 6255 } 6256 } 6257 6258 /// 6259 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 6260 assert(window !is null); 6261 6262 auto display = XDisplayConnection.get(); 6263 auto atom = GetAtom!atomName(display); 6264 6265 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 6266 this(void delegate(in char[]) handler) { 6267 this.handler = handler; 6268 } 6269 6270 mixin X11GetSelectionHandler_Basics; 6271 6272 void delegate(in char[]) handler; 6273 6274 void handleData(Atom target, in ubyte[] data) { 6275 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 6276 handler(cast(const char[]) data); 6277 } 6278 6279 Atom findBestFormat(Atom[] answer) { 6280 Atom best = None; 6281 foreach(option; answer) { 6282 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 6283 best = option; 6284 break; 6285 } else if(option == XA_STRING) { 6286 best = option; 6287 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 6288 best = option; 6289 } 6290 } 6291 return best; 6292 } 6293 } 6294 6295 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 6296 6297 auto target = GetAtom!"TARGETS"(display); 6298 6299 // SDD_DATA is "simpledisplay.d data" 6300 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 6301 } 6302 6303 /// Gets the image on the clipboard, if there is one. Added July 2020. 6304 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 6305 assert(window !is null); 6306 6307 auto display = XDisplayConnection.get(); 6308 auto atom = GetAtom!atomName(display); 6309 6310 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 6311 this(void delegate(MemoryImage) handler) { 6312 this.handler = handler; 6313 } 6314 6315 mixin X11GetSelectionHandler_Basics; 6316 6317 void delegate(MemoryImage) handler; 6318 6319 void handleData(Atom target, in ubyte[] data) { 6320 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6321 import arsd.bmp; 6322 handler(readBmp(data)); 6323 } 6324 } 6325 6326 Atom findBestFormat(Atom[] answer) { 6327 Atom best = None; 6328 foreach(option; answer) { 6329 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 6330 best = option; 6331 } 6332 } 6333 return best; 6334 } 6335 6336 } 6337 6338 6339 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 6340 6341 auto target = GetAtom!"TARGETS"(display); 6342 6343 // SDD_DATA is "simpledisplay.d data" 6344 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 6345 } 6346 6347 6348 /// 6349 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 6350 Atom actualType; 6351 int actualFormat; 6352 arch_ulong actualItems; 6353 arch_ulong bytesRemaining; 6354 void* data; 6355 6356 auto display = XDisplayConnection.get(); 6357 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 6358 if(actualFormat == 0) 6359 return null; 6360 else { 6361 int byteLength; 6362 if(actualFormat == 32) { 6363 // 32 means it is a C long... which is variable length 6364 actualFormat = cast(int) arch_long.sizeof * 8; 6365 } 6366 6367 // then it is just a bit count 6368 byteLength = cast(int) (actualItems * actualFormat / 8); 6369 6370 auto d = new ubyte[](byteLength); 6371 d[] = cast(ubyte[]) data[0 .. byteLength]; 6372 XFree(data); 6373 return d; 6374 } 6375 } 6376 return null; 6377 } 6378 6379 /* defined in the systray spec */ 6380 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 6381 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 6382 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 6383 6384 6385 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 6386 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 6387 public class GlobalHotkey { 6388 KeyEvent key; 6389 void delegate () handler; 6390 6391 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 6392 6393 /// Create from initialzed KeyEvent object 6394 this (KeyEvent akey, void delegate () ahandler=null) { 6395 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 6396 key = akey; 6397 handler = ahandler; 6398 } 6399 6400 /// Create from emacs-like key name ("C-M-Y", etc.) 6401 this (const(char)[] akey, void delegate () ahandler=null) { 6402 key = KeyEvent.parse(akey); 6403 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 6404 handler = ahandler; 6405 } 6406 6407 } 6408 6409 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6410 //conwriteln("failed to grab key"); 6411 GlobalHotkeyManager.ghfailed = true; 6412 return 0; 6413 } 6414 6415 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6416 Image.impl.xshmfailed = true; 6417 return 0; 6418 } 6419 6420 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 6421 import core.stdc.stdio; 6422 char[265] buffer; 6423 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 6424 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); 6425 return 0; 6426 } 6427 6428 /++ 6429 Global hotkey manager. It contains static methods to manage global hotkeys. 6430 6431 --- 6432 try { 6433 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 6434 } catch (Exception e) { 6435 conwriteln("ERROR registering hotkey!"); 6436 } 6437 EventLoop.get.run(); 6438 --- 6439 6440 The key strings are based on Emacs. In practical terms, 6441 `M` means `alt` and `H` means the Windows logo key. `C` 6442 is `ctrl`. 6443 6444 $(WARNING 6445 This is X-specific right now. If you are on 6446 Windows, try [registerHotKey] instead. 6447 6448 We will probably merge these into a single 6449 interface later. 6450 ) 6451 +/ 6452 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 6453 version(X11) { 6454 void recreateAfterDisconnect() { 6455 throw new Exception("NOT IMPLEMENTED"); 6456 } 6457 void discardConnectionState() { 6458 throw new Exception("NOT IMPLEMENTED"); 6459 } 6460 } 6461 6462 private static immutable uint[8] masklist = [ 0, 6463 KeyOrButtonMask.LockMask, 6464 KeyOrButtonMask.Mod2Mask, 6465 KeyOrButtonMask.Mod3Mask, 6466 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 6467 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 6468 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6469 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 6470 ]; 6471 private __gshared GlobalHotkeyManager ghmanager; 6472 private __gshared bool ghfailed = false; 6473 6474 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 6475 if (modmask == 0) return false; 6476 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 6477 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 6478 return true; 6479 } 6480 6481 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 6482 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 6483 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 6484 return modmask; 6485 } 6486 6487 private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) { 6488 uint keycode = cast(uint)ke.key; 6489 auto dpy = XDisplayConnection.get; 6490 return XKeysymToKeycode(dpy, keycode); 6491 } 6492 6493 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 6494 6495 private __gshared GlobalHotkey[ulong] globalHotkeyList; 6496 6497 NativeEventHandler getNativeEventHandler () { 6498 return delegate int (XEvent e) { 6499 if (e.type != EventType.KeyPress) return 1; 6500 auto kev = cast(const(XKeyEvent)*)&e; 6501 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 6502 if (auto ghkp = hash in globalHotkeyList) { 6503 try { 6504 ghkp.doHandle(); 6505 } catch (Exception e) { 6506 import core.stdc.stdio : stderr, fprintf; 6507 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 6508 } 6509 } 6510 return 1; 6511 }; 6512 } 6513 6514 private this () { 6515 auto dpy = XDisplayConnection.get; 6516 auto root = RootWindow(dpy, DefaultScreen(dpy)); 6517 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 6518 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 6519 } 6520 6521 /// Register new global hotkey with initialized `GlobalHotkey` object. 6522 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 6523 static void register (GlobalHotkey gh) { 6524 if (gh is null) return; 6525 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 6526 6527 auto dpy = XDisplayConnection.get; 6528 immutable keycode = keyEvent2KeyCode(gh.key); 6529 6530 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 6531 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 6532 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 6533 XSync(dpy, 0/*False*/); 6534 6535 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6536 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6537 ghfailed = false; 6538 foreach (immutable uint ormask; masklist[]) { 6539 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 6540 } 6541 XSync(dpy, 0/*False*/); 6542 XSetErrorHandler(savedErrorHandler); 6543 6544 if (ghfailed) { 6545 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6546 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 6547 XSync(dpy, 0/*False*/); 6548 XSetErrorHandler(savedErrorHandler); 6549 throw new Exception("cannot register global hotkey"); 6550 } 6551 6552 globalHotkeyList[hash] = gh; 6553 } 6554 6555 /// Ditto 6556 static void register (const(char)[] akey, void delegate () ahandler) { 6557 register(new GlobalHotkey(akey, ahandler)); 6558 } 6559 6560 private static void removeByHash (ulong hash) { 6561 if (auto ghp = hash in globalHotkeyList) { 6562 auto dpy = XDisplayConnection.get; 6563 immutable keycode = keyEvent2KeyCode(ghp.key); 6564 Window root = RootWindow(dpy, DefaultScreen(dpy)); 6565 XSync(dpy, 0/*False*/); 6566 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 6567 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 6568 XSync(dpy, 0/*False*/); 6569 XSetErrorHandler(savedErrorHandler); 6570 globalHotkeyList.remove(hash); 6571 } 6572 } 6573 6574 /// Register new global hotkey with previously used `GlobalHotkey` object. 6575 /// It is safe to unregister unknown or invalid hotkey. 6576 static void unregister (GlobalHotkey gh) { 6577 //TODO: add second AA for faster search? prolly doesn't worth it. 6578 if (gh is null) return; 6579 foreach (const ref kv; globalHotkeyList.byKeyValue) { 6580 if (kv.value is gh) { 6581 removeByHash(kv.key); 6582 return; 6583 } 6584 } 6585 } 6586 6587 /// Ditto. 6588 static void unregister (const(char)[] key) { 6589 auto kev = KeyEvent.parse(key); 6590 immutable keycode = keyEvent2KeyCode(kev); 6591 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 6592 } 6593 } 6594 } 6595 6596 version(Windows) { 6597 /++ 6598 See [SyntheticInput.sendSyntheticInput] instead for cross-platform applications. 6599 6600 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). 6601 +/ 6602 void sendSyntheticInput(wstring s) { 6603 INPUT[] inputs; 6604 inputs.reserve(s.length * 2); 6605 6606 foreach(wchar c; s) { 6607 INPUT input; 6608 input.type = INPUT_KEYBOARD; 6609 input.ki.wScan = c; 6610 input.ki.dwFlags = KEYEVENTF_UNICODE; 6611 inputs ~= input; 6612 6613 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6614 inputs ~= input; 6615 } 6616 6617 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6618 throw new Exception("SendInput failed"); 6619 } 6620 6621 } 6622 6623 6624 // global hotkey helper function 6625 6626 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. See [GlobalHotkeyManager] for Linux. Maybe some day I will merge these. 6627 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6628 __gshared int hotkeyId = 0; 6629 int id = ++hotkeyId; 6630 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6631 throw new Exception("RegisterHotKey failed"); 6632 6633 __gshared void delegate()[WPARAM][HWND] handlers; 6634 6635 handlers[window.impl.hwnd][id] = handler; 6636 6637 int delegate(HWND, UINT, WPARAM, LPARAM, out int) oldHandler; 6638 6639 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) { 6640 switch(msg) { 6641 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6642 case WM_HOTKEY: 6643 if(auto list = hwnd in handlers) { 6644 if(auto h = wParam in *list) { 6645 (*h)(); 6646 return 0; 6647 } 6648 } 6649 goto default; 6650 default: 6651 } 6652 if(oldHandler) 6653 return oldHandler(hwnd, msg, wParam, lParam, mustReturn); 6654 return 1; // pass it on 6655 }; 6656 6657 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6658 oldHandler = window.handleNativeEvent; 6659 window.handleNativeEvent = nativeEventHandler; 6660 } 6661 6662 return id; 6663 } 6664 6665 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by [registerHotKey]. 6666 void unregisterHotKey(SimpleWindow window, int id) { 6667 if(!UnregisterHotKey(window.impl.hwnd, id)) 6668 throw new Exception("UnregisterHotKey"); 6669 } 6670 } 6671 6672 version (X11) { 6673 pragma(lib, "dl"); 6674 import core.sys.posix.dlfcn; 6675 } 6676 6677 /++ 6678 Allows for sending synthetic input to the X server via the Xtst 6679 extension or on Windows using SendInput. 6680 6681 Please remember user input is meant to be user - don't use this 6682 if you have some other alternative! 6683 6684 History: 6685 Added May 17, 2020 with the X implementation. 6686 6687 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.) 6688 Bugs: 6689 All methods on OSX Cocoa will throw not yet implemented exceptions. 6690 +/ 6691 struct SyntheticInput { 6692 @disable this(); 6693 6694 private int* refcount; 6695 6696 version(X11) { 6697 private void* lib; 6698 6699 private extern(C) { 6700 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6701 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6702 } 6703 } 6704 6705 /// The dummy param must be 0. 6706 this(int dummy) { 6707 version(X11) { 6708 lib = dlopen("libXtst.so", RTLD_NOW); 6709 if(lib is null) 6710 throw new Exception("cannot load xtest lib extension"); 6711 scope(failure) 6712 dlclose(lib); 6713 6714 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6715 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6716 6717 if(XTestFakeKeyEvent is null) 6718 throw new Exception("No XTestFakeKeyEvent"); 6719 if(XTestFakeButtonEvent is null) 6720 throw new Exception("No XTestFakeButtonEvent"); 6721 } 6722 6723 refcount = new int; 6724 *refcount = 1; 6725 } 6726 6727 this(this) { 6728 if(refcount) 6729 *refcount += 1; 6730 } 6731 6732 ~this() { 6733 if(refcount) { 6734 *refcount -= 1; 6735 if(*refcount == 0) 6736 // I commented this because if I close the lib before 6737 // XCloseDisplay, it is liable to segfault... so just 6738 // gonna keep it loaded if it is loaded, no big deal 6739 // anyway. 6740 {} // dlclose(lib); 6741 } 6742 } 6743 6744 /++ 6745 Simulates typing a string into the keyboard. 6746 6747 Bugs: 6748 On X11, this ONLY works with basic ascii! On Windows, it can handle more. 6749 6750 Not implemented except on Windows and X11. 6751 +/ 6752 void sendSyntheticInput(string s) { 6753 version(Windows) { 6754 INPUT[] inputs; 6755 inputs.reserve(s.length * 2); 6756 6757 auto ei = GetMessageExtraInfo(); 6758 6759 foreach(wchar c; s) { 6760 INPUT input; 6761 input.type = INPUT_KEYBOARD; 6762 input.ki.wScan = c; 6763 input.ki.dwFlags = KEYEVENTF_UNICODE; 6764 input.ki.dwExtraInfo = ei; 6765 inputs ~= input; 6766 6767 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6768 inputs ~= input; 6769 } 6770 6771 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6772 throw new Exception("SendInput failed"); 6773 } 6774 } else version(X11) { 6775 int delay = 0; 6776 foreach(ch; s) { 6777 pressKey(cast(Key) ch, true, delay); 6778 pressKey(cast(Key) ch, false, delay); 6779 delay += 5; 6780 } 6781 } else throw new NotYetImplementedException(); 6782 } 6783 6784 /++ 6785 Sends a fake press or release key event. 6786 6787 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6788 6789 Bugs: 6790 The `delay` parameter is not implemented yet on Windows. 6791 6792 Not implemented except on Windows and X11. 6793 +/ 6794 void pressKey(Key key, bool pressed, int delay = 0) { 6795 version(Windows) { 6796 INPUT input; 6797 input.type = INPUT_KEYBOARD; 6798 input.ki.wVk = cast(ushort) key; 6799 6800 input.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP; 6801 input.ki.dwExtraInfo = GetMessageExtraInfo(); 6802 6803 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6804 throw new Exception("SendInput failed"); 6805 } 6806 } else version(X11) { 6807 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6808 } else throw new NotYetImplementedException(); 6809 } 6810 6811 /++ 6812 Sends a fake mouse button press or release event. 6813 6814 Please note you need to call [flushGui] or return to the event loop for this to actually be sent on X11. 6815 6816 `pressed` param must be `true` if button is `wheelUp` or `wheelDown`. 6817 6818 Bugs: 6819 The `delay` parameter is not implemented yet on Windows. 6820 6821 The backButton and forwardButton will throw NotYetImplementedException on Windows. 6822 6823 All arguments will throw NotYetImplementedException on OSX Cocoa. 6824 +/ 6825 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6826 version(Windows) { 6827 INPUT input; 6828 input.type = INPUT_MOUSE; 6829 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6830 6831 // input.mi.mouseData for a wheel event 6832 6833 switch(button) { 6834 case MouseButton.left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break; 6835 case MouseButton.middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break; 6836 case MouseButton.right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break; 6837 case MouseButton.wheelUp: 6838 case MouseButton.wheelDown: 6839 input.mi.dwFlags = MOUSEEVENTF_WHEEL; 6840 input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; 6841 break; 6842 case MouseButton.backButton: throw new NotYetImplementedException(); 6843 case MouseButton.forwardButton: throw new NotYetImplementedException(); 6844 default: 6845 } 6846 6847 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6848 throw new Exception("SendInput failed"); 6849 } 6850 } else version(X11) { 6851 int btn; 6852 6853 switch(button) { 6854 case MouseButton.left: btn = 1; break; 6855 case MouseButton.middle: btn = 2; break; 6856 case MouseButton.right: btn = 3; break; 6857 case MouseButton.wheelUp: btn = 4; break; 6858 case MouseButton.wheelDown: btn = 5; break; 6859 case MouseButton.backButton: btn = 8; break; 6860 case MouseButton.forwardButton: btn = 9; break; 6861 default: 6862 } 6863 6864 assert(btn); 6865 6866 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 6867 } else throw new NotYetImplementedException(); 6868 } 6869 6870 /// 6871 static void moveMouseArrowBy(int dx, int dy) { 6872 version(Windows) { 6873 INPUT input; 6874 input.type = INPUT_MOUSE; 6875 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6876 input.mi.dx = dx; 6877 input.mi.dy = dy; 6878 input.mi.dwFlags = MOUSEEVENTF_MOVE; 6879 6880 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6881 throw new Exception("SendInput failed"); 6882 } 6883 } else version(X11) { 6884 auto disp = XDisplayConnection.get(); 6885 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 6886 XFlush(disp); 6887 } else throw new NotYetImplementedException(); 6888 } 6889 6890 /// 6891 static void moveMouseArrowTo(int x, int y) { 6892 version(Windows) { 6893 INPUT input; 6894 input.type = INPUT_MOUSE; 6895 input.mi.dwExtraInfo = GetMessageExtraInfo(); 6896 input.mi.dx = x; 6897 input.mi.dy = y; 6898 input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; 6899 6900 if(SendInput(1, &input, INPUT.sizeof) != 1) { 6901 throw new Exception("SendInput failed"); 6902 } 6903 } else version(X11) { 6904 auto disp = XDisplayConnection.get(); 6905 auto root = RootWindow(disp, DefaultScreen(disp)); 6906 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 6907 XFlush(disp); 6908 } else throw new NotYetImplementedException(); 6909 } 6910 } 6911 6912 6913 6914 /++ 6915 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 6916 6917 See_Also: 6918 $(LIST 6919 *[ScreenPainter] 6920 *[ScreenPainter.rasterOp] 6921 ) 6922 +/ 6923 enum RasterOp { 6924 normal, /// Replaces the pixel. 6925 xor, /// Uses bitwise xor to draw. 6926 } 6927 6928 // being phobos-free keeps the size WAY down 6929 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 6930 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 6931 package(arsd) const(wchar)* toWStringz(string s) { 6932 wstring r; 6933 foreach(dchar c; s) 6934 r ~= c; 6935 r ~= '\0'; 6936 return r.ptr; 6937 } 6938 private string[] split(in void[] a, char c) { 6939 string[] ret; 6940 size_t previous = 0; 6941 foreach(i, char ch; cast(ubyte[]) a) { 6942 if(ch == c) { 6943 ret ~= cast(string) a[previous .. i]; 6944 previous = i + 1; 6945 } 6946 } 6947 if(previous != a.length) 6948 ret ~= cast(string) a[previous .. $]; 6949 return ret; 6950 } 6951 6952 version(without_opengl) { 6953 enum OpenGlOptions { 6954 no, 6955 } 6956 } else { 6957 /++ 6958 Determines if you want an OpenGL context created on the new window. 6959 6960 6961 See more: [#topics-3d|in the 3d topic]. 6962 6963 --- 6964 import arsd.simpledisplay; 6965 void main() { 6966 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 6967 6968 // Set up the matrix 6969 window.setAsCurrentOpenGlContext(); // make this window active 6970 6971 // This is called on each frame, we will draw our scene 6972 window.redrawOpenGlScene = delegate() { 6973 6974 }; 6975 6976 window.eventLoop(0); 6977 } 6978 --- 6979 +/ 6980 enum OpenGlOptions { 6981 no, /// No OpenGL context is created 6982 yes, /// Yes, create an OpenGL context 6983 } 6984 6985 version(X11) { 6986 static if (!SdpyIsUsingIVGLBinds) { 6987 6988 6989 struct __GLXFBConfigRec {} 6990 alias GLXFBConfig = __GLXFBConfigRec*; 6991 6992 //pragma(lib, "GL"); 6993 //pragma(lib, "GLU"); 6994 interface GLX { 6995 extern(C) nothrow @nogc { 6996 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 6997 const int *attrib_list); 6998 6999 void glXCopyContext(Display *dpy, GLXContext src, 7000 GLXContext dst, arch_ulong mask); 7001 7002 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 7003 GLXContext share_list, Bool direct); 7004 7005 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 7006 Pixmap pixmap); 7007 7008 void glXDestroyContext(Display *dpy, GLXContext ctx); 7009 7010 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 7011 7012 int glXGetConfig(Display *dpy, XVisualInfo *vis, 7013 int attrib, int *value); 7014 7015 GLXContext glXGetCurrentContext(); 7016 7017 GLXDrawable glXGetCurrentDrawable(); 7018 7019 Bool glXIsDirect(Display *dpy, GLXContext ctx); 7020 7021 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 7022 GLXContext ctx); 7023 7024 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 7025 7026 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 7027 7028 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 7029 7030 void glXUseXFont(Font font, int first, int count, int list_base); 7031 7032 void glXWaitGL(); 7033 7034 void glXWaitX(); 7035 7036 7037 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 7038 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 7039 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 7040 7041 char* glXQueryExtensionsString (Display*, int); 7042 void* glXGetProcAddress (const(char)*); 7043 7044 } 7045 } 7046 7047 version(OSX) 7048 mixin DynamicLoad!(GLX, "GL", 0, openGlLibrariesSuccessfullyLoaded) glx; 7049 else 7050 mixin DynamicLoad!(GLX, "GLX", 0, openGlLibrariesSuccessfullyLoaded) glx; 7051 shared static this() { 7052 glx.loadDynamicLibrary(); 7053 } 7054 7055 alias glbindGetProcAddress = glXGetProcAddress; 7056 } 7057 } else version(Windows) { 7058 /* it is done below by interface GL */ 7059 } else 7060 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."); 7061 } 7062 7063 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 7064 alias Resizablity = Resizability; 7065 7066 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 7067 enum Resizability { 7068 fixedSize, /// the window cannot be resized 7069 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. 7070 automaticallyScaleIfPossible, /// if possible, your drawing buffer will remain the same size and simply be automatically scaled to the new window size. If this is impossible, it will not allow the user to resize the window at all. Note: window.width and window.height WILL be adjusted, which might throw you off if you draw based on them, so keep track of your expected width and height separately. That way, when it is scaled, things won't be thrown off. 7071 7072 // FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events 7073 } 7074 7075 7076 /++ 7077 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 7078 +/ 7079 enum TextAlignment : uint { 7080 Left = 0, /// 7081 Center = 1, /// 7082 Right = 2, /// 7083 7084 VerticalTop = 0, /// 7085 VerticalCenter = 4, /// 7086 VerticalBottom = 8, /// 7087 } 7088 7089 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 7090 alias Rectangle = arsd.color.Rectangle; 7091 7092 7093 /++ 7094 Keyboard press and release events. 7095 +/ 7096 struct KeyEvent { 7097 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 7098 Key key; 7099 ubyte hardwareCode; /// A platform and hardware specific code for the key 7100 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 7101 7102 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 7103 7104 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 7105 7106 SimpleWindow window; /// associated Window 7107 7108 /++ 7109 A view into the upcoming buffer holding coming character events that are sent if and only if neither 7110 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 7111 to predict if char events are actually coming.. 7112 7113 Only available on X systems since this information is not given ahead of time elsewhere. 7114 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 7115 7116 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 7117 and potential quirks I'd recommend avoiding it. 7118 7119 History: 7120 Added April 26, 2021 (dub v9.5) 7121 +/ 7122 version(X11) 7123 dchar[] charsPossible; 7124 7125 // convert key event to simplified string representation a-la emacs 7126 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 7127 uint dpos = 0; 7128 void put (const(char)[] s...) nothrow @trusted { 7129 static if (growdest) { 7130 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 7131 } else { 7132 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 7133 } 7134 } 7135 7136 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 7137 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 7138 } 7139 7140 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 7141 7142 // put modifiers 7143 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 7144 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 7145 putMod(ModifierState.alt, Key.Alt, "Alt+"); 7146 putMod(ModifierState.windows, Key.Shift, "Windows+"); 7147 putMod(ModifierState.shift, Key.Shift, "Shift+"); 7148 7149 if (this.key) { 7150 foreach (string kn; __traits(allMembers, Key)) { 7151 if (this.key == __traits(getMember, Key, kn)) { 7152 // HACK! 7153 static if (kn == "N0") put("0"); 7154 else static if (kn == "N1") put("1"); 7155 else static if (kn == "N2") put("2"); 7156 else static if (kn == "N3") put("3"); 7157 else static if (kn == "N4") put("4"); 7158 else static if (kn == "N5") put("5"); 7159 else static if (kn == "N6") put("6"); 7160 else static if (kn == "N7") put("7"); 7161 else static if (kn == "N8") put("8"); 7162 else static if (kn == "N9") put("9"); 7163 else put(kn); 7164 return dest[0..dpos]; 7165 } 7166 } 7167 put("Unknown"); 7168 } else { 7169 if (dpos && dest[dpos-1] == '+') --dpos; 7170 } 7171 return dest[0..dpos]; 7172 } 7173 7174 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 7175 7176 /** Parse string into key name with modifiers. It accepts things like: 7177 * 7178 * C-H-1 -- emacs style (ctrl, and windows, and 1) 7179 * 7180 * Ctrl+Win+1 -- windows style 7181 * 7182 * Ctrl-Win-1 -- '-' is a valid delimiter too 7183 * 7184 * Ctrl Win 1 -- and space 7185 * 7186 * and even "Win + 1 + Ctrl". 7187 */ 7188 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 7189 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 7190 7191 // remove trailing spaces 7192 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 7193 7194 // tokens delimited by blank, '+', or '-' 7195 // null on eol 7196 const(char)[] getToken () nothrow @trusted @nogc { 7197 // remove leading spaces and delimiters 7198 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 7199 if (name.length == 0) return null; // oops, no more tokens 7200 // get token 7201 size_t epos = 0; 7202 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 7203 assert(epos > 0 && epos <= name.length); 7204 auto res = name[0..epos]; 7205 name = name[epos..$]; 7206 return res; 7207 } 7208 7209 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 7210 if (s0.length != s1.length) return false; 7211 foreach (immutable ci, char c0; s0) { 7212 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 7213 char c1 = s1[ci]; 7214 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 7215 if (c0 != c1) return false; 7216 } 7217 return true; 7218 } 7219 7220 if (ignoreModsOut !is null) *ignoreModsOut = false; 7221 if (updown !is null) *updown = -1; 7222 KeyEvent res; 7223 res.key = cast(Key)0; // just in case 7224 const(char)[] tk, tkn; // last token 7225 bool allowEmascStyle = true; 7226 bool ignoreModifiers = false; 7227 tokenloop: for (;;) { 7228 tk = tkn; 7229 tkn = getToken(); 7230 //k8: yay, i took "Bloody Mess" trait from Fallout! 7231 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 7232 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 7233 if (allowEmascStyle && tkn.length != 0) { 7234 if (tk.length == 1) { 7235 char mdc = tk[0]; 7236 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 7237 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7238 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7239 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7240 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7241 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 7242 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 7243 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 7244 } 7245 } 7246 allowEmascStyle = false; 7247 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 7248 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 7249 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 7250 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 7251 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 7252 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 7253 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 7254 if (tk.length == 0) continue; 7255 // try key name 7256 if (res.key == 0) { 7257 // little hack 7258 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 7259 final switch (tk[0]) { 7260 case '0': tk = "N0"; break; 7261 case '1': tk = "N1"; break; 7262 case '2': tk = "N2"; break; 7263 case '3': tk = "N3"; break; 7264 case '4': tk = "N4"; break; 7265 case '5': tk = "N5"; break; 7266 case '6': tk = "N6"; break; 7267 case '7': tk = "N7"; break; 7268 case '8': tk = "N8"; break; 7269 case '9': tk = "N9"; break; 7270 } 7271 } 7272 foreach (string kn; __traits(allMembers, Key)) { 7273 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 7274 } 7275 } 7276 // unknown or duplicate key name, get out of here 7277 break; 7278 } 7279 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 7280 return res; // something 7281 } 7282 7283 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 7284 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 7285 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 7286 if (kk == k) { mask |= mst; kk = cast(Key)0; } 7287 } 7288 bool ignoreMods; 7289 int updown; 7290 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 7291 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 7292 if (this.key != ke.key) { 7293 // things like "ctrl+alt" are complicated 7294 uint tkm = this.modifierState&modmask; 7295 uint kkm = ke.modifierState&modmask; 7296 Key tk = this.key; 7297 // ke 7298 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 7299 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 7300 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 7301 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 7302 // this 7303 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 7304 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 7305 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 7306 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 7307 return (tk == ke.key && tkm == kkm); 7308 } 7309 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 7310 } 7311 } 7312 7313 /// Sets the application name. 7314 @property string ApplicationName(string name) { 7315 return _applicationName = name; 7316 } 7317 7318 string _applicationName; 7319 7320 /// ditto 7321 @property string ApplicationName() { 7322 if(_applicationName is null) { 7323 import core.runtime; 7324 return Runtime.args[0]; 7325 } 7326 return _applicationName; 7327 } 7328 7329 7330 /// Type of a [MouseEvent]. 7331 enum MouseEventType : int { 7332 motion = 0, /// The mouse moved inside the window 7333 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 7334 buttonReleased = 2, /// A mouse button was released 7335 } 7336 7337 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 7338 /++ 7339 Listen for this on your event listeners if you are interested in mouse action. 7340 7341 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. 7342 7343 Examples: 7344 7345 This will draw boxes on the window with the mouse as you hold the left button. 7346 --- 7347 import arsd.simpledisplay; 7348 7349 void main() { 7350 auto window = new SimpleWindow(); 7351 7352 window.eventLoop(0, 7353 (MouseEvent ev) { 7354 if(ev.modifierState & ModifierState.leftButtonDown) { 7355 auto painter = window.draw(); 7356 painter.fillColor = Color.red; 7357 painter.outlineColor = Color.black; 7358 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 7359 } 7360 } 7361 ); 7362 } 7363 --- 7364 +/ 7365 struct MouseEvent { 7366 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 7367 7368 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. 7369 int y; /// Current Y position of the cursor when the event fired. 7370 7371 int dx; /// Change in X position since last report 7372 int dy; /// Change in Y position since last report 7373 7374 MouseButton button; /// See [MouseButton] 7375 int modifierState; /// See [ModifierState] 7376 7377 version(X11) 7378 private Time timestamp; 7379 7380 /// Returns a linear representation of mouse button, 7381 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 7382 /// 7383 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 7384 @property ubyte buttonLinear() const { 7385 import core.bitop; 7386 if(button == 0) 7387 return 0; 7388 return (bsf(button) + 1) & 0b1111; 7389 } 7390 7391 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 7392 7393 SimpleWindow window; /// The window in which the event happened. 7394 7395 Point globalCoordinates() { 7396 Point p; 7397 if(window is null) 7398 throw new Exception("wtf"); 7399 static if(UsingSimpledisplayX11) { 7400 Window child; 7401 XTranslateCoordinates( 7402 XDisplayConnection.get, 7403 window.impl.window, 7404 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 7405 x, y, &p.x, &p.y, &child); 7406 return p; 7407 } else version(Windows) { 7408 POINT[1] points; 7409 points[0].x = x; 7410 points[0].y = y; 7411 MapWindowPoints( 7412 window.impl.hwnd, 7413 null, 7414 points.ptr, 7415 points.length 7416 ); 7417 p.x = points[0].x; 7418 p.y = points[0].y; 7419 7420 return p; 7421 } else version(OSXCocoa) { 7422 throw new NotYetImplementedException(); 7423 } else static assert(0); 7424 } 7425 7426 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 7427 7428 /** 7429 can contain emacs-like modifier prefix 7430 case-insensitive names: 7431 lmbX/leftX 7432 rmbX/rightX 7433 mmbX/middleX 7434 wheelX 7435 motion (no prefix allowed) 7436 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 7437 */ 7438 static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 7439 if (str.length == 0) return false; // just in case 7440 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 7441 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 7442 auto anchor = str; 7443 uint mods = 0; // uint.max == any 7444 // interesting bits in kmod 7445 uint kmodmask = 7446 ModifierState.shift| 7447 ModifierState.ctrl| 7448 ModifierState.alt| 7449 ModifierState.windows| 7450 ModifierState.leftButtonDown| 7451 ModifierState.middleButtonDown| 7452 ModifierState.rightButtonDown| 7453 0; 7454 uint lastButt = uint.max; // otherwise, bit 31 means "down" 7455 bool wasButtons = false; 7456 while (str.length) { 7457 if (str.ptr[0] <= ' ') { 7458 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 7459 continue; 7460 } 7461 // one-letter modifier? 7462 if (str.length >= 2 && str.ptr[1] == '-') { 7463 switch (str.ptr[0]) { 7464 case '*': // "any" modifier (cannot be undone) 7465 mods = mods.max; 7466 break; 7467 case 'C': case 'c': // emacs "ctrl" 7468 if (mods != mods.max) mods |= ModifierState.ctrl; 7469 break; 7470 case 'M': case 'm': // emacs "meta" 7471 if (mods != mods.max) mods |= ModifierState.alt; 7472 break; 7473 case 'S': case 's': // emacs "shift" 7474 if (mods != mods.max) mods |= ModifierState.shift; 7475 break; 7476 case 'H': case 'h': // emacs "hyper" (aka winkey) 7477 if (mods != mods.max) mods |= ModifierState.windows; 7478 break; 7479 default: 7480 return false; // unknown modifier 7481 } 7482 str = str[2..$]; 7483 continue; 7484 } 7485 // word 7486 char[16] buf = void; // locased 7487 auto wep = 0; 7488 while (str.length) { 7489 immutable char ch = str.ptr[0]; 7490 if (ch <= ' ' || ch == '-') break; 7491 str = str[1..$]; 7492 if (wep > buf.length) return false; // too long 7493 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7494 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7495 else return false; // invalid char 7496 } 7497 if (wep == 0) return false; // just in case 7498 uint bnum; 7499 enum UpDown { None = -1, Up, Down, Any } 7500 auto updown = UpDown.None; // 0: up; 1: down 7501 switch (buf[0..wep]) { 7502 // left button 7503 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 7504 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 7505 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 7506 case "lmb": case "left": bnum = 0; break; 7507 // middle button 7508 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 7509 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 7510 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 7511 case "mmb": case "middle": bnum = 1; break; 7512 // right button 7513 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 7514 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 7515 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 7516 case "rmb": case "right": bnum = 2; break; 7517 // wheel 7518 case "wheelup": updown = UpDown.Up; goto case "wheel"; 7519 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 7520 case "wheelany": updown = UpDown.Any; goto case "wheel"; 7521 case "wheel": bnum = 3; break; 7522 // motion 7523 case "motion": bnum = 7; break; 7524 // unknown 7525 default: return false; 7526 } 7527 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7528 // parse possible "-up" or "-down" 7529 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 7530 wep = 0; 7531 foreach (immutable idx, immutable char ch; str[1..$]) { 7532 if (ch <= ' ' || ch == '-') break; 7533 assert(idx == wep); // for now; trick 7534 if (wep > buf.length) { wep = 0; break; } // too long 7535 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 7536 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 7537 else { wep = 0; break; } // invalid char 7538 } 7539 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 7540 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 7541 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 7542 // remove parsed part 7543 if (updown != UpDown.None) str = str[wep+1..$]; 7544 } 7545 if (updown == UpDown.None) { 7546 updown = UpDown.Down; 7547 } 7548 wasButtons = wasButtons || (bnum <= 2); 7549 //assert(updown != UpDown.None); 7550 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 7551 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 7552 if (lastButt != lastButt.max) { 7553 if ((lastButt&0xff) >= 3) return false; // wheel or motion 7554 if (mods != mods.max) { 7555 uint butbit = 0; 7556 final switch (lastButt&0x03) { 7557 case 0: butbit = ModifierState.leftButtonDown; break; 7558 case 1: butbit = ModifierState.middleButtonDown; break; 7559 case 2: butbit = ModifierState.rightButtonDown; break; 7560 } 7561 if (lastButt&Flag.Down) mods |= butbit; 7562 else if (lastButt&Flag.Up) mods &= ~butbit; 7563 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 7564 } 7565 } 7566 // remember last button 7567 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 7568 } 7569 // no button -- nothing to do 7570 if (lastButt == lastButt.max) return false; 7571 // done parsing, check if something's left 7572 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 7573 // remove action button from mask 7574 if ((lastButt&0xff) < 3) { 7575 final switch (lastButt&0x03) { 7576 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 7577 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 7578 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 7579 } 7580 } 7581 // special case: "Motion" means "ignore buttons" 7582 if ((lastButt&0xff) == 7 && !wasButtons) { 7583 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 7584 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 7585 } 7586 uint kmod = event.modifierState&kmodmask; 7587 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 7588 // check modifier state 7589 if (mods != mods.max) { 7590 if (kmod != mods) return false; 7591 } 7592 // now check type 7593 if ((lastButt&0xff) == 7) { 7594 // motion 7595 if (event.type != MouseEventType.motion) return false; 7596 } else if ((lastButt&0xff) == 3) { 7597 // wheel 7598 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 7599 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 7600 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 7601 return false; 7602 } else { 7603 // buttons 7604 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 7605 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 7606 { 7607 return false; 7608 } 7609 // button number 7610 switch (lastButt&0x03) { 7611 case 0: if (event.button != MouseButton.left) return false; break; 7612 case 1: if (event.button != MouseButton.middle) return false; break; 7613 case 2: if (event.button != MouseButton.right) return false; break; 7614 default: return false; 7615 } 7616 } 7617 return true; 7618 } 7619 } 7620 7621 version(arsd_mevent_strcmp_test) unittest { 7622 MouseEvent event; 7623 event.type = MouseEventType.buttonPressed; 7624 event.button = MouseButton.left; 7625 event.modifierState = ModifierState.ctrl; 7626 assert(event == "C-LMB"); 7627 assert(event != "C-LMBUP"); 7628 assert(event != "C-LMB-UP"); 7629 assert(event != "C-S-LMB"); 7630 assert(event == "*-LMB"); 7631 assert(event != "*-LMB-UP"); 7632 7633 event.type = MouseEventType.buttonReleased; 7634 assert(event != "C-LMB"); 7635 assert(event == "C-LMBUP"); 7636 assert(event == "C-LMB-UP"); 7637 assert(event != "C-S-LMB"); 7638 assert(event != "*-LMB"); 7639 assert(event == "*-LMB-UP"); 7640 7641 event.button = MouseButton.right; 7642 event.modifierState |= ModifierState.shift; 7643 event.type = MouseEventType.buttonPressed; 7644 assert(event != "C-LMB"); 7645 assert(event != "C-LMBUP"); 7646 assert(event != "C-LMB-UP"); 7647 assert(event != "C-S-LMB"); 7648 assert(event != "*-LMB"); 7649 assert(event != "*-LMB-UP"); 7650 7651 assert(event != "C-RMB"); 7652 assert(event != "C-RMBUP"); 7653 assert(event != "C-RMB-UP"); 7654 assert(event == "C-S-RMB"); 7655 assert(event == "*-RMB"); 7656 assert(event != "*-RMB-UP"); 7657 } 7658 7659 /// This gives a few more options to drawing lines and such 7660 struct Pen { 7661 Color color; /// the foreground color 7662 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. 7663 Style style; /// See [Style] 7664 /+ 7665 // From X.h 7666 7667 #define LineSolid 0 7668 #define LineOnOffDash 1 7669 #define LineDoubleDash 2 7670 LineDou- The full path of the line is drawn, but the 7671 bleDash even dashes are filled differently from the 7672 odd dashes (see fill-style) with CapButt 7673 style used where even and odd dashes meet. 7674 7675 7676 7677 /* capStyle */ 7678 7679 #define CapNotLast 0 7680 #define CapButt 1 7681 #define CapRound 2 7682 #define CapProjecting 3 7683 7684 /* joinStyle */ 7685 7686 #define JoinMiter 0 7687 #define JoinRound 1 7688 #define JoinBevel 2 7689 7690 /* fillStyle */ 7691 7692 #define FillSolid 0 7693 #define FillTiled 1 7694 #define FillStippled 2 7695 #define FillOpaqueStippled 3 7696 7697 7698 +/ 7699 /// Style of lines drawn 7700 enum Style { 7701 Solid, /// a solid line 7702 Dashed, /// a dashed line 7703 Dotted, /// a dotted line 7704 } 7705 } 7706 7707 7708 /++ 7709 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 7710 7711 7712 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 7713 7714 $(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.) 7715 7716 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. 7717 7718 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 7719 7720 $(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. 7721 7722 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! 7723 7724 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!) 7725 7726 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 7727 7728 --- 7729 auto image = new Image(256, 256); 7730 scope(exit) destroy(image); 7731 --- 7732 7733 As long as you don't hold on to it outside the scope. 7734 7735 I might change it to be an owned pointer at some point in the future. 7736 7737 ) 7738 7739 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7740 you can also often get a fair amount of speedup by getting the raw data format and 7741 writing some custom code. 7742 7743 FIXME INSERT EXAMPLES HERE 7744 7745 7746 +/ 7747 final class Image { 7748 /// 7749 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7750 this.width = width; 7751 this.height = height; 7752 this.enableAlpha = enableAlpha; 7753 7754 impl.createImage(width, height, forcexshm, enableAlpha); 7755 } 7756 7757 /// 7758 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7759 this(size.width, size.height, forcexshm, enableAlpha); 7760 } 7761 7762 private bool suppressDestruction; 7763 7764 version(X11) 7765 this(XImage* handle) { 7766 this.handle = handle; 7767 this.rawData = cast(ubyte*) handle.data; 7768 this.width = handle.width; 7769 this.height = handle.height; 7770 this.enableAlpha = handle.depth == 32; 7771 suppressDestruction = true; 7772 } 7773 7774 ~this() { 7775 if(suppressDestruction) return; 7776 impl.dispose(); 7777 } 7778 7779 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7780 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7781 pure const @system nothrow { 7782 /* 7783 To use these to draw a blue rectangle with size WxH at position X,Y... 7784 7785 // make certain that it will fit before we proceed 7786 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! 7787 7788 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7789 // (though calculating them isn't really that expensive). 7790 auto nextLineAdjustment = img.adjustmentForNextLine(); 7791 auto offR = img.redByteOffset(); 7792 auto offB = img.blueByteOffset(); 7793 auto offG = img.greenByteOffset(); 7794 auto bpp = img.bytesPerPixel(); 7795 7796 auto data = img.getDataPointer(); 7797 7798 // figure out the starting byte offset 7799 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7800 7801 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7802 7803 // and now our drawing loop for the rectangle 7804 foreach(y; 0 .. H) { 7805 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7806 foreach(x; 0 .. W) { 7807 // write our color 7808 data[offR] = 0; 7809 data[offG] = 0; 7810 data[offB] = 255; 7811 7812 data += bpp; // moving to the next pixel is just an addition... 7813 } 7814 startOfLine += nextLineAdjustment; 7815 } 7816 7817 7818 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7819 7820 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7821 can be made into a bitmask or something so we can write them as *uint... 7822 */ 7823 7824 /// 7825 int offsetForTopLeftPixel() { 7826 version(X11) { 7827 return 0; 7828 } else version(Windows) { 7829 if(enableAlpha) { 7830 return (width * 4) * (height - 1); 7831 } else { 7832 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7833 } 7834 } else version(OSXCocoa) { 7835 return 0 ; //throw new NotYetImplementedException(); 7836 } else static assert(0, "fill in this info for other OSes"); 7837 } 7838 7839 /// 7840 int offsetForPixel(int x, int y) { 7841 version(X11) { 7842 auto offset = (y * width + x) * 4; 7843 return offset; 7844 } else version(Windows) { 7845 if(enableAlpha) { 7846 auto itemsPerLine = width * 4; 7847 // remember, bmps are upside down 7848 auto offset = itemsPerLine * (height - y - 1) + x * 4; 7849 return offset; 7850 } else { 7851 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 7852 // remember, bmps are upside down 7853 auto offset = itemsPerLine * (height - y - 1) + x * 3; 7854 return offset; 7855 } 7856 } else version(OSXCocoa) { 7857 return 0 ; //throw new NotYetImplementedException(); 7858 } else static assert(0, "fill in this info for other OSes"); 7859 } 7860 7861 /// 7862 int adjustmentForNextLine() { 7863 version(X11) { 7864 return width * 4; 7865 } else version(Windows) { 7866 // windows bmps are upside down, so the adjustment is actually negative 7867 if(enableAlpha) 7868 return - (cast(int) width * 4); 7869 else 7870 return -((cast(int) width * 3 + 3) / 4) * 4; 7871 } else version(OSXCocoa) { 7872 return 0 ; //throw new NotYetImplementedException(); 7873 } else static assert(0, "fill in this info for other OSes"); 7874 } 7875 7876 /// once you have the position of a pixel, use these to get to the proper color 7877 int redByteOffset() { 7878 version(X11) { 7879 return 2; 7880 } else version(Windows) { 7881 return 2; 7882 } else version(OSXCocoa) { 7883 return 0 ; //throw new NotYetImplementedException(); 7884 } else static assert(0, "fill in this info for other OSes"); 7885 } 7886 7887 /// 7888 int greenByteOffset() { 7889 version(X11) { 7890 return 1; 7891 } else version(Windows) { 7892 return 1; 7893 } else version(OSXCocoa) { 7894 return 0 ; //throw new NotYetImplementedException(); 7895 } else static assert(0, "fill in this info for other OSes"); 7896 } 7897 7898 /// 7899 int blueByteOffset() { 7900 version(X11) { 7901 return 0; 7902 } else version(Windows) { 7903 return 0; 7904 } else version(OSXCocoa) { 7905 return 0 ; //throw new NotYetImplementedException(); 7906 } else static assert(0, "fill in this info for other OSes"); 7907 } 7908 7909 /// Only valid if [enableAlpha] is true 7910 int alphaByteOffset() { 7911 version(X11) { 7912 return 3; 7913 } else version(Windows) { 7914 return 3; 7915 } else version(OSXCocoa) { 7916 return 3; //throw new NotYetImplementedException(); 7917 } else static assert(0, "fill in this info for other OSes"); 7918 } 7919 } 7920 7921 /// 7922 final void putPixel(int x, int y, Color c) { 7923 if(x < 0 || x >= width) 7924 return; 7925 if(y < 0 || y >= height) 7926 return; 7927 7928 impl.setPixel(x, y, c); 7929 } 7930 7931 /// 7932 final Color getPixel(int x, int y) { 7933 if(x < 0 || x >= width) 7934 return Color.transparent; 7935 if(y < 0 || y >= height) 7936 return Color.transparent; 7937 7938 version(OSXCocoa) throw new NotYetImplementedException(); else 7939 return impl.getPixel(x, y); 7940 } 7941 7942 /// 7943 final void opIndexAssign(Color c, int x, int y) { 7944 putPixel(x, y, c); 7945 } 7946 7947 /// 7948 TrueColorImage toTrueColorImage() { 7949 auto tci = new TrueColorImage(width, height); 7950 convertToRgbaBytes(tci.imageData.bytes); 7951 return tci; 7952 } 7953 7954 /// 7955 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) { 7956 auto tci = i.getAsTrueColorImage(); 7957 auto img = new Image(tci.width, tci.height, false, enableAlpha); 7958 img.setRgbaBytes(tci.imageData.bytes); 7959 return img; 7960 } 7961 7962 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 7963 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 7964 /// if you pass null, it will allocate a new one. 7965 ubyte[] getRgbaBytes(ubyte[] where = null) { 7966 if(where is null) 7967 where = new ubyte[this.width*this.height*4]; 7968 convertToRgbaBytes(where); 7969 return where; 7970 } 7971 7972 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 7973 void setRgbaBytes(in ubyte[] from ) { 7974 assert(from.length == this.width * this.height * 4); 7975 setFromRgbaBytes(from); 7976 } 7977 7978 // FIXME: make properly cross platform by getting rgba right 7979 7980 /// warning: this is not portable across platforms because the data format can change 7981 ubyte* getDataPointer() { 7982 return impl.rawData; 7983 } 7984 7985 /// for use with getDataPointer 7986 final int bytesPerLine() const pure @safe nothrow { 7987 version(Windows) 7988 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 7989 else version(X11) 7990 return 4 * width; 7991 else version(OSXCocoa) 7992 return 4 * width; 7993 else static assert(0); 7994 } 7995 7996 /// for use with getDataPointer 7997 final int bytesPerPixel() const pure @safe nothrow { 7998 version(Windows) 7999 return enableAlpha ? 4 : 3; 8000 else version(X11) 8001 return 4; 8002 else version(OSXCocoa) 8003 return 4; 8004 else static assert(0); 8005 } 8006 8007 /// 8008 immutable int width; 8009 8010 /// 8011 immutable int height; 8012 8013 /// 8014 immutable bool enableAlpha; 8015 //private: 8016 mixin NativeImageImplementation!() impl; 8017 } 8018 8019 /++ 8020 A convenience function to pop up a window displaying the image. 8021 If you pass a win, it will draw the image in it. Otherwise, it will 8022 create a window with the size of the image and run its event loop, closing 8023 when a key is pressed. 8024 8025 History: 8026 `BlockingMode` parameter added on December 8, 2021. Previously, it would 8027 always block until the application quit which could cause bizarre behavior 8028 inside a more complex application. Now, the default is to block until 8029 this window closes if it is the only event loop running, and otherwise, 8030 not to block at all and just pop up the display window asynchronously. 8031 +/ 8032 void displayImage(Image image, SimpleWindow win = null, BlockingMode bm = BlockingMode.untilWindowCloses | BlockingMode.onlyIfNotNested) { 8033 if(win is null) { 8034 win = new SimpleWindow(image); 8035 { 8036 auto p = win.draw; 8037 p.drawImage(Point(0, 0), image); 8038 } 8039 win.eventLoopWithBlockingMode( 8040 bm, 0, 8041 (KeyEvent ev) { 8042 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 8043 } ); 8044 } else { 8045 win.image = image; 8046 } 8047 } 8048 8049 enum FontWeight : int { 8050 dontcare = 0, 8051 thin = 100, 8052 extralight = 200, 8053 light = 300, 8054 regular = 400, 8055 medium = 500, 8056 semibold = 600, 8057 bold = 700, 8058 extrabold = 800, 8059 heavy = 900 8060 } 8061 8062 /++ 8063 Interface with the common functionality for font measurements between [OperatingSystemFont] and [DrawableFont]. 8064 8065 History: 8066 Added October 24, 2022. The methods were already on [OperatingSystemFont] before that. 8067 +/ 8068 interface MeasurableFont { 8069 /++ 8070 Returns true if it is a monospace font, meaning each of the 8071 glyphs (at least the ascii characters) have matching width 8072 and no kerning, so you can determine the display width of some 8073 strings by simply multiplying the string width by [averageWidth]. 8074 8075 (Please note that multiply doesn't $(I actually) work in general, 8076 consider characters like tab and newline, but it does sometimes.) 8077 +/ 8078 bool isMonospace(); 8079 8080 /++ 8081 The average width of glyphs in the font, traditionally equal to the 8082 width of the lowercase x. Can be used to estimate bounding boxes, 8083 especially if the font [isMonospace]. 8084 8085 Given in pixels. 8086 +/ 8087 int averageWidth(); 8088 /++ 8089 The height of the bounding box of a line. 8090 +/ 8091 int height(); 8092 /++ 8093 The maximum ascent of a glyph above the baseline. 8094 8095 Given in pixels. 8096 +/ 8097 int ascent(); 8098 /++ 8099 The maximum descent of a glyph below the baseline. For example, how low the g might go. 8100 8101 Given in pixels. 8102 +/ 8103 int descent(); 8104 /++ 8105 The display width of the given string, and if you provide a window, it will use it to 8106 make the pixel count on screen more accurate too, but this shouldn't generally be necessary. 8107 8108 Given in pixels. 8109 +/ 8110 int stringWidth(scope const(char)[] s, SimpleWindow window = null); 8111 8112 } 8113 8114 // FIXME: i need a font cache and it needs to handle disconnects. 8115 8116 /++ 8117 Represents a font loaded off the operating system or the X server. 8118 8119 8120 While the api here is unified cross platform, the fonts are not necessarily 8121 available, even across machines of the same platform, so be sure to always check 8122 for null (using [isNull]) and have a fallback plan. 8123 8124 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 8125 8126 Worst case, a null font will automatically fall back to the default font loaded 8127 for your system. 8128 +/ 8129 class OperatingSystemFont : MeasurableFont { 8130 // FIXME: when the X Connection is lost, these need to be invalidated! 8131 // that means I need to store the original stuff again to reconstruct it too. 8132 8133 version(X11) { 8134 XFontStruct* font; 8135 XFontSet fontset; 8136 8137 version(with_xft) { 8138 XftFont* xftFont; 8139 bool isXft; 8140 } 8141 } else version(Windows) { 8142 HFONT font; 8143 int width_; 8144 int height_; 8145 } else version(OSXCocoa) { 8146 // FIXME 8147 } else static assert(0); 8148 8149 /++ 8150 Constructs the class and immediately calls [load]. 8151 +/ 8152 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8153 load(name, size, weight, italic); 8154 } 8155 8156 /++ 8157 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 8158 8159 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 8160 8161 History: 8162 Added January 24, 2021. 8163 +/ 8164 this() { 8165 // this space intentionally left blank 8166 } 8167 8168 /++ 8169 Loads specifically with the Xft library - a freetype font from a fontconfig string. 8170 8171 History: 8172 Added November 13, 2020. 8173 +/ 8174 version(with_xft) 8175 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8176 unload(); 8177 8178 if(!XftLibrary.attempted) { 8179 XftLibrary.loadDynamicLibrary(); 8180 } 8181 8182 if(!XftLibrary.loadSuccessful) 8183 return false; 8184 8185 auto display = XDisplayConnection.get; 8186 8187 char[256] nameBuffer = void; 8188 int nbp = 0; 8189 8190 void add(in char[] a) { 8191 nameBuffer[nbp .. nbp + a.length] = a[]; 8192 nbp += a.length; 8193 } 8194 add(name); 8195 8196 if(size) { 8197 add(":size="); 8198 add(toInternal!string(size)); 8199 } 8200 if(weight != FontWeight.dontcare) { 8201 add(":weight="); 8202 add(weightToString(weight)); 8203 } 8204 if(italic) 8205 add(":slant=100"); 8206 8207 nameBuffer[nbp] = 0; 8208 8209 this.xftFont = XftFontOpenName( 8210 display, 8211 DefaultScreen(display), 8212 nameBuffer.ptr 8213 ); 8214 8215 this.isXft = true; 8216 8217 if(xftFont !is null) { 8218 isMonospace_ = stringWidth("x") == stringWidth("M"); 8219 ascent_ = xftFont.ascent; 8220 descent_ = xftFont.descent; 8221 } 8222 8223 return !isNull(); 8224 } 8225 8226 /++ 8227 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 8228 8229 8230 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. 8231 8232 If `pattern` is null, it returns all available font families. 8233 8234 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. 8235 8236 The format of the pattern is platform-specific. 8237 8238 History: 8239 Added May 1, 2021 (dub v9.5) 8240 +/ 8241 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 8242 version(Windows) { 8243 auto hdc = GetDC(null); 8244 scope(exit) ReleaseDC(null, hdc); 8245 LOGFONT logfont; 8246 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 8247 auto localHandler = *(cast(typeof(handler)*) p); 8248 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 8249 } 8250 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 8251 } else version(X11) { 8252 //import core.stdc.stdio; 8253 bool done = false; 8254 version(with_xft) { 8255 if(!XftLibrary.attempted) { 8256 XftLibrary.loadDynamicLibrary(); 8257 } 8258 8259 if(!XftLibrary.loadSuccessful) 8260 goto skipXft; 8261 8262 if(!FontConfigLibrary.attempted) 8263 FontConfigLibrary.loadDynamicLibrary(); 8264 if(!FontConfigLibrary.loadSuccessful) 8265 goto skipXft; 8266 8267 { 8268 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 8269 if(got is null) 8270 goto skipXft; 8271 scope(exit) FcFontSetDestroy(got); 8272 8273 auto fontPatterns = got.fonts[0 .. got.nfont]; 8274 foreach(candidate; fontPatterns) { 8275 char* where, whereStyle; 8276 8277 char* pmg = FcNameUnparse(candidate); 8278 8279 //FcPatternGetString(candidate, "family", 0, &where); 8280 //FcPatternGetString(candidate, "style", 0, &whereStyle); 8281 //if(where && whereStyle) { 8282 if(pmg) { 8283 if(!handler(pmg.sliceCString)) 8284 return; 8285 //printf("%s || %s %s\n", pmg, where, whereStyle); 8286 } 8287 } 8288 } 8289 } 8290 8291 skipXft: 8292 8293 if(pattern is null) 8294 pattern = "*"; 8295 8296 int count; 8297 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 8298 scope(exit) XFreeFontNames(coreFontsRaw); 8299 8300 auto coreFonts = coreFontsRaw[0 .. count]; 8301 8302 foreach(font; coreFonts) { 8303 char[128] tmp; 8304 tmp[0 ..5] = "core:"; 8305 auto cf = font.sliceCString; 8306 if(5 + cf.length > tmp.length) 8307 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 8308 tmp[5 .. 5 + cf.length] = cf; 8309 if(!handler(tmp[0 .. 5 + cf.length])) 8310 return; 8311 } 8312 } 8313 } 8314 8315 /++ 8316 Returns the raw content of the ttf file, if possible. This allows you to use OperatingSystemFont 8317 to look up fonts that you then pass to things like [arsd.ttf.OpenGlLimitedFont] or [arsd.nanovega]. 8318 8319 Returns null if impossible. It is impossible if the loaded font is not a local TTF file or if the 8320 underlying system doesn't support returning the raw bytes. 8321 8322 History: 8323 Added September 10, 2021 (dub v10.3) 8324 +/ 8325 ubyte[] getTtfBytes() { 8326 if(isNull) 8327 return null; 8328 8329 version(Windows) { 8330 auto dc = GetDC(null); 8331 auto orig = SelectObject(dc, font); 8332 8333 scope(exit) { 8334 SelectObject(dc, orig); 8335 ReleaseDC(null, dc); 8336 } 8337 8338 auto res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, null, 0); 8339 if(res == GDI_ERROR) 8340 return null; 8341 8342 ubyte[] buffer = new ubyte[](res); 8343 res = GetFontData(dc, 0 /* whole file */, 0 /* offset */, buffer.ptr, cast(DWORD) buffer.length); 8344 if(res == GDI_ERROR) 8345 return null; // wtf really tbh 8346 8347 return buffer; 8348 } else version(with_xft) { 8349 if(isXft && xftFont) { 8350 if(!FontConfigLibrary.attempted) 8351 FontConfigLibrary.loadDynamicLibrary(); 8352 if(!FontConfigLibrary.loadSuccessful) 8353 return null; 8354 8355 char* file; 8356 if (FcPatternGetString(xftFont.pattern, "file", 0, &file) == 0 /*FcResultMatch*/) { 8357 if (file !is null && file[0]) { 8358 import core.stdc.stdio; 8359 auto fp = fopen(file, "rb"); 8360 if(fp is null) 8361 return null; 8362 scope(exit) 8363 fclose(fp); 8364 fseek(fp, 0, SEEK_END); 8365 ubyte[] buffer = new ubyte[](ftell(fp)); 8366 fseek(fp, 0, SEEK_SET); 8367 8368 auto got = fread(buffer.ptr, 1, buffer.length, fp); 8369 if(got != buffer.length) 8370 return null; 8371 8372 return buffer; 8373 } 8374 } 8375 } 8376 return null; 8377 } 8378 } 8379 8380 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 8381 8382 private string weightToString(FontWeight weight) { 8383 with(FontWeight) 8384 final switch(weight) { 8385 case dontcare: return "*"; 8386 case thin: return "extralight"; 8387 case extralight: return "extralight"; 8388 case light: return "light"; 8389 case regular: return "regular"; 8390 case medium: return "medium"; 8391 case semibold: return "demibold"; 8392 case bold: return "bold"; 8393 case extrabold: return "demibold"; 8394 case heavy: return "black"; 8395 } 8396 } 8397 8398 /++ 8399 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 8400 8401 History: 8402 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8403 +/ 8404 version(X11) 8405 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8406 unload(); 8407 8408 string xfontstr; 8409 8410 if(name.length > 3 && name[0 .. 3] == "-*-") { 8411 // this is kinda a disgusting hack but if the user sends an exact 8412 // string I'd like to honor it... 8413 xfontstr = name; 8414 } else { 8415 string weightstr = weightToString(weight); 8416 string sizestr; 8417 if(size == 0) 8418 sizestr = "*"; 8419 else 8420 sizestr = toInternal!string(size); 8421 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 8422 } 8423 8424 //import std.stdio; writeln(xfontstr); 8425 8426 auto display = XDisplayConnection.get; 8427 8428 font = XLoadQueryFont(display, xfontstr.ptr); 8429 if(font is null) 8430 return false; 8431 8432 char** lol; 8433 int lol2; 8434 char* lol3; 8435 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8436 8437 prepareFontInfo(); 8438 8439 return !isNull(); 8440 } 8441 8442 version(X11) 8443 private void prepareFontInfo() { 8444 if(font !is null) { 8445 isMonospace_ = stringWidth("l") == stringWidth("M"); 8446 ascent_ = font.max_bounds.ascent; 8447 descent_ = font.max_bounds.descent; 8448 } 8449 } 8450 8451 /++ 8452 Loads a Windows font. You probably want to use [load] instead to be more generic. 8453 8454 History: 8455 Added November 13, 2020. Before then, this code was integrated in the [load] function. 8456 +/ 8457 version(Windows) 8458 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 8459 unload(); 8460 8461 WCharzBuffer buffer = WCharzBuffer(name); 8462 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 8463 8464 prepareFontInfo(hdc); 8465 8466 return !isNull(); 8467 } 8468 8469 version(Windows) 8470 void prepareFontInfo(HDC hdc = null) { 8471 if(font is null) 8472 return; 8473 8474 TEXTMETRIC tm; 8475 auto dc = hdc ? hdc : GetDC(null); 8476 auto orig = SelectObject(dc, font); 8477 GetTextMetrics(dc, &tm); 8478 SelectObject(dc, orig); 8479 if(hdc is null) 8480 ReleaseDC(null, dc); 8481 8482 width_ = tm.tmAveCharWidth; 8483 height_ = tm.tmHeight; 8484 ascent_ = tm.tmAscent; 8485 descent_ = tm.tmDescent; 8486 // 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. 8487 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 8488 } 8489 8490 8491 /++ 8492 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 8493 8494 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 8495 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 8496 8497 On Windows, it forwards directly to [loadWin32]. 8498 8499 Params: 8500 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 8501 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. 8502 weight = approximate boldness, results may vary. 8503 italic = try to get a slanted version of the given font. 8504 8505 History: 8506 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. 8507 +/ 8508 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 8509 version(X11) { 8510 version(with_xft) { 8511 if(name.length > 5 && name[0 .. 5] == "core:") { 8512 goto core; 8513 } 8514 8515 if(loadXft(name, size, weight, italic)) 8516 return true; 8517 // if xft fails, fallback to core to avoid breaking 8518 // code that already depended on this. 8519 } 8520 8521 core: 8522 8523 if(name.length > 5 && name[0 .. 5] == "core:") { 8524 name = name[5 .. $]; 8525 } 8526 8527 return loadCoreX(name, size, weight, italic); 8528 } else version(Windows) { 8529 return loadWin32(name, size, weight, italic); 8530 } else version(OSXCocoa) { 8531 // FIXME 8532 return false; 8533 } else static assert(0); 8534 } 8535 8536 /// 8537 void unload() { 8538 if(isNull()) 8539 return; 8540 8541 version(X11) { 8542 auto display = XDisplayConnection.display; 8543 8544 if(display is null) 8545 return; 8546 8547 version(with_xft) { 8548 if(isXft) { 8549 if(xftFont) 8550 XftFontClose(display, xftFont); 8551 isXft = false; 8552 xftFont = null; 8553 return; 8554 } 8555 } 8556 8557 if(font && font !is ScreenPainterImplementation.defaultfont) 8558 XFreeFont(display, font); 8559 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 8560 XFreeFontSet(display, fontset); 8561 8562 font = null; 8563 fontset = null; 8564 } else version(Windows) { 8565 DeleteObject(font); 8566 font = null; 8567 } else version(OSXCocoa) { 8568 // FIXME 8569 } else static assert(0); 8570 } 8571 8572 private bool isMonospace_; 8573 8574 /++ 8575 History: 8576 Added January 16, 2021 8577 +/ 8578 bool isMonospace() { 8579 return isMonospace_; 8580 } 8581 8582 /++ 8583 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 8584 8585 History: 8586 Added March 26, 2020 8587 Documented January 16, 2021 8588 +/ 8589 int averageWidth() { 8590 version(X11) { 8591 return stringWidth("x"); 8592 } else version(Windows) 8593 return width_; 8594 else assert(0); 8595 } 8596 8597 /++ 8598 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 8599 8600 History: 8601 Added January 16, 2021 8602 +/ 8603 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 8604 // FIXME: what about tab? 8605 if(isNull) 8606 return 0; 8607 8608 version(X11) { 8609 version(with_xft) 8610 if(isXft && xftFont !is null) { 8611 //return xftFont.max_advance_width; 8612 XGlyphInfo extents; 8613 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 8614 //import std.stdio; writeln(extents); 8615 return extents.xOff; 8616 } 8617 if(font is null) 8618 return 0; 8619 else if(fontset) { 8620 XRectangle rect; 8621 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 8622 8623 return rect.width; 8624 } else { 8625 return XTextWidth(font, s.ptr, cast(int) s.length); 8626 } 8627 } else version(Windows) { 8628 WCharzBuffer buffer = WCharzBuffer(s); 8629 8630 return stringWidth(buffer.slice, window); 8631 } 8632 else assert(0); 8633 } 8634 8635 version(Windows) 8636 /// ditto 8637 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 8638 if(isNull) 8639 return 0; 8640 version(Windows) { 8641 SIZE size; 8642 8643 prepareContext(window); 8644 scope(exit) releaseContext(); 8645 8646 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 8647 8648 return size.cx; 8649 } else { 8650 // std.conv can do this easily but it is slow to import and i don't think it is worth it 8651 static assert(0, "not implemented yet"); 8652 //return stringWidth(s, window); 8653 } 8654 } 8655 8656 private { 8657 int prepRefcount; 8658 8659 version(Windows) { 8660 HDC dc; 8661 HANDLE orig; 8662 HWND hwnd; 8663 } 8664 } 8665 /++ 8666 [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. 8667 8668 History: 8669 Added January 23, 2021 8670 +/ 8671 void prepareContext(SimpleWindow window = null) { 8672 prepRefcount++; 8673 if(prepRefcount == 1) { 8674 version(Windows) { 8675 hwnd = window is null ? null : window.impl.hwnd; 8676 dc = GetDC(hwnd); 8677 orig = SelectObject(dc, font); 8678 } 8679 } 8680 } 8681 /// ditto 8682 void releaseContext() { 8683 prepRefcount--; 8684 if(prepRefcount == 0) { 8685 version(Windows) { 8686 SelectObject(dc, orig); 8687 ReleaseDC(hwnd, dc); 8688 hwnd = null; 8689 dc = null; 8690 orig = null; 8691 } 8692 } 8693 } 8694 8695 /+ 8696 FIXME: I think I need advance and kerning pair 8697 8698 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 8699 +/ 8700 8701 /++ 8702 Returns the height of the font. 8703 8704 History: 8705 Added March 26, 2020 8706 Documented January 16, 2021 8707 +/ 8708 int height() { 8709 version(X11) { 8710 version(with_xft) 8711 if(isXft && xftFont !is null) { 8712 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 8713 } 8714 if(font is null) 8715 return 0; 8716 return font.max_bounds.ascent + font.max_bounds.descent; 8717 } else version(Windows) 8718 return height_; 8719 else assert(0); 8720 } 8721 8722 private int ascent_; 8723 private int descent_; 8724 8725 /++ 8726 Max ascent above the baseline. 8727 8728 History: 8729 Added January 22, 2021 8730 +/ 8731 int ascent() { 8732 return ascent_; 8733 } 8734 8735 /++ 8736 Max descent below the baseline. 8737 8738 History: 8739 Added January 22, 2021 8740 +/ 8741 int descent() { 8742 return descent_; 8743 } 8744 8745 /++ 8746 Loads the default font used by [ScreenPainter] if none others are loaded. 8747 8748 Returns: 8749 This method mutates the `this` object, but then returns `this` for 8750 easy chaining like: 8751 8752 --- 8753 auto font = foo.isNull ? foo : foo.loadDefault 8754 --- 8755 8756 History: 8757 Added previously, but left unimplemented until January 24, 2021. 8758 +/ 8759 OperatingSystemFont loadDefault() { 8760 unload(); 8761 8762 version(X11) { 8763 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 8764 // but meh since sdpy does its own thing, this should be ok too 8765 8766 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8767 this.font = ScreenPainterImplementation.defaultfont; 8768 this.fontset = ScreenPainterImplementation.defaultfontset; 8769 8770 prepareFontInfo(); 8771 } else version(Windows) { 8772 ScreenPainterImplementation.ensureDefaultFontLoaded(); 8773 this.font = ScreenPainterImplementation.defaultGuiFont; 8774 8775 prepareFontInfo(); 8776 } else throw new NotYetImplementedException(); 8777 8778 return this; 8779 } 8780 8781 /// 8782 bool isNull() { 8783 version(OSXCocoa) throw new NotYetImplementedException(); else { 8784 version(with_xft) 8785 if(isXft) 8786 return xftFont is null; 8787 return font is null; 8788 } 8789 } 8790 8791 /* Metrics */ 8792 /+ 8793 GetABCWidth 8794 GetKerningPairs 8795 8796 if I do it right, I can size it all here, and match 8797 what happens when I draw the full string with the OS functions. 8798 8799 subclasses might do the same thing while getting the glyphs on images 8800 struct GlyphInfo { 8801 int glyph; 8802 8803 size_t stringIdxStart; 8804 size_t stringIdxEnd; 8805 8806 Rectangle boundingBox; 8807 } 8808 GlyphInfo[] getCharBoxes() { 8809 // XftTextExtentsUtf8 8810 return null; 8811 8812 } 8813 +/ 8814 8815 ~this() { 8816 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 8817 unload(); 8818 } 8819 } 8820 8821 version(Windows) 8822 private string sliceCString(const(wchar)[] w) { 8823 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 8824 } 8825 8826 private inout(char)[] sliceCString(inout(char)* s) { 8827 import core.stdc.string; 8828 auto len = strlen(s); 8829 return s[0 .. len]; 8830 } 8831 8832 /** 8833 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 8834 than constructing it directly. Then, it is reference counted so you can pass it 8835 at around and when the last ref goes out of scope, the buffered drawing activities 8836 are all carried out. 8837 8838 8839 Most functions use the outlineColor instead of taking a color themselves. 8840 ScreenPainter is reference counted and draws its buffer to the screen when its 8841 final reference goes out of scope. 8842 */ 8843 struct ScreenPainter { 8844 CapableOfBeingDrawnUpon window; 8845 this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle, bool manualInvalidations) { 8846 this.window = window; 8847 if(window.closed) 8848 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 8849 //currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 8850 currentClipRectangle = arsd.color.Rectangle(short.min, short.min, short.max, short.max); 8851 if(window.activeScreenPainter !is null) { 8852 impl = window.activeScreenPainter; 8853 if(impl.referenceCount == 0) { 8854 impl.window = window; 8855 impl.create(handle); 8856 } 8857 impl.manualInvalidations = manualInvalidations; 8858 impl.referenceCount++; 8859 // writeln("refcount ++ ", impl.referenceCount); 8860 } else { 8861 impl = new ScreenPainterImplementation; 8862 impl.window = window; 8863 impl.create(handle); 8864 impl.referenceCount = 1; 8865 impl.manualInvalidations = manualInvalidations; 8866 window.activeScreenPainter = impl; 8867 //import std.stdio; writeln("constructed"); 8868 } 8869 8870 copyActiveOriginals(); 8871 } 8872 8873 /++ 8874 If you are using manual invalidations, this informs the 8875 window system that a section needs to be redrawn. 8876 8877 If you didn't opt into manual invalidation, you don't 8878 have to call this. 8879 8880 History: 8881 Added December 30, 2021 (dub v10.5) 8882 +/ 8883 void invalidateRect(Rectangle rect) { 8884 if(impl is null) return; 8885 8886 // transform(rect) 8887 rect.left += _originX; 8888 rect.right += _originX; 8889 rect.top += _originY; 8890 rect.bottom += _originY; 8891 8892 impl.invalidateRect(rect); 8893 } 8894 8895 private Pen originalPen; 8896 private Color originalFillColor; 8897 private arsd.color.Rectangle originalClipRectangle; 8898 void copyActiveOriginals() { 8899 if(impl is null) return; 8900 originalPen = impl._activePen; 8901 originalFillColor = impl._fillColor; 8902 originalClipRectangle = impl._clipRectangle; 8903 } 8904 8905 ~this() { 8906 if(impl is null) return; 8907 impl.referenceCount--; 8908 //writeln("refcount -- ", impl.referenceCount); 8909 if(impl.referenceCount == 0) { 8910 //import std.stdio; writeln("destructed"); 8911 impl.dispose(); 8912 *window.activeScreenPainter = ScreenPainterImplementation.init; 8913 //import std.stdio; writeln("paint finished"); 8914 } else { 8915 // there is still an active reference, reset stuff so the 8916 // next user doesn't get weirdness via the reference 8917 this.rasterOp = RasterOp.normal; 8918 pen = originalPen; 8919 fillColor = originalFillColor; 8920 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 8921 } 8922 } 8923 8924 this(this) { 8925 if(impl is null) return; 8926 impl.referenceCount++; 8927 //writeln("refcount ++ ", impl.referenceCount); 8928 8929 copyActiveOriginals(); 8930 } 8931 8932 private int _originX; 8933 private int _originY; 8934 @property int originX() { return _originX; } 8935 @property int originY() { return _originY; } 8936 @property int originX(int a) { 8937 _originX = a; 8938 return _originX; 8939 } 8940 @property int originY(int a) { 8941 _originY = a; 8942 return _originY; 8943 } 8944 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 8945 private void transform(ref Point p) { 8946 if(impl is null) return; 8947 p.x += _originX; 8948 p.y += _originY; 8949 } 8950 8951 // this needs to be checked BEFORE the originX/Y transformation 8952 private bool isClipped(Point p) { 8953 return !currentClipRectangle.contains(p); 8954 } 8955 private bool isClipped(Point p, int width, int height) { 8956 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 8957 } 8958 private bool isClipped(Point p, Size s) { 8959 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 8960 } 8961 private bool isClipped(Point p, Point p2) { 8962 // need to ensure the end points are actually included inside, so the +1 does that 8963 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 8964 } 8965 8966 8967 /++ 8968 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 8969 8970 Returns: 8971 The old clip rectangle. 8972 8973 History: 8974 Return value was `void` prior to May 10, 2021. 8975 8976 +/ 8977 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 8978 if(impl is null) return currentClipRectangle; 8979 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 8980 return currentClipRectangle; // no need to do anything 8981 auto old = currentClipRectangle; 8982 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 8983 transform(pt); 8984 8985 impl.setClipRectangle(pt.x, pt.y, width, height); 8986 8987 return old; 8988 } 8989 8990 /// ditto 8991 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 8992 if(impl is null) return currentClipRectangle; 8993 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 8994 } 8995 8996 /// 8997 void setFont(OperatingSystemFont font) { 8998 if(impl is null) return; 8999 impl.setFont(font); 9000 } 9001 9002 /// 9003 int fontHeight() { 9004 if(impl is null) return 0; 9005 return impl.fontHeight(); 9006 } 9007 9008 private Pen activePen; 9009 9010 /// 9011 @property void pen(Pen p) { 9012 if(impl is null) return; 9013 activePen = p; 9014 impl.pen(p); 9015 } 9016 9017 /// 9018 @scriptable 9019 @property void outlineColor(Color c) { 9020 if(impl is null) return; 9021 if(activePen.color == c) 9022 return; 9023 activePen.color = c; 9024 impl.pen(activePen); 9025 } 9026 9027 /// 9028 @scriptable 9029 @property void fillColor(Color c) { 9030 if(impl is null) return; 9031 impl.fillColor(c); 9032 } 9033 9034 /// 9035 @property void rasterOp(RasterOp op) { 9036 if(impl is null) return; 9037 impl.rasterOp(op); 9038 } 9039 9040 9041 void updateDisplay() { 9042 // FIXME this should do what the dtor does 9043 } 9044 9045 /// 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) 9046 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 9047 if(impl is null) return; 9048 if(isClipped(upperLeft, width, height)) return; 9049 transform(upperLeft); 9050 version(Windows) { 9051 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 9052 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 9053 RECT clip = scroll; 9054 RECT uncovered; 9055 HRGN hrgn; 9056 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 9057 throw new Exception("ScrollDC"); 9058 9059 } else version(X11) { 9060 // FIXME: clip stuff outside this rectangle 9061 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 9062 } else version(OSXCocoa) { 9063 throw new NotYetImplementedException(); 9064 } else static assert(0); 9065 } 9066 9067 /// 9068 void clear(Color color = Color.white()) { 9069 if(impl is null) return; 9070 fillColor = color; 9071 outlineColor = color; 9072 drawRectangle(Point(0, 0), window.width, window.height); 9073 } 9074 9075 /++ 9076 Draws a pixmap (represented by the [Sprite] class) on the drawable. 9077 9078 Params: 9079 upperLeft = point on the window where the upper left corner of the image will be drawn 9080 imageUpperLeft = point on the image to start the slice to draw 9081 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. 9082 History: 9083 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9084 +/ 9085 version(OSXCocoa) {} else // NotYetImplementedException 9086 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9087 if(impl is null) return; 9088 if(isClipped(upperLeft, s.width, s.height)) return; 9089 transform(upperLeft); 9090 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 9091 } 9092 9093 /// 9094 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 9095 if(impl is null) return; 9096 //if(isClipped(upperLeft, w, h)) return; // FIXME 9097 transform(upperLeft); 9098 if(w == 0 || w > i.width) 9099 w = i.width; 9100 if(h == 0 || h > i.height) 9101 h = i.height; 9102 if(upperLeftOfImage.x < 0) 9103 upperLeftOfImage.x = 0; 9104 if(upperLeftOfImage.y < 0) 9105 upperLeftOfImage.y = 0; 9106 9107 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 9108 } 9109 9110 /// 9111 Size textSize(in char[] text) { 9112 if(impl is null) return Size(0, 0); 9113 return impl.textSize(text); 9114 } 9115 9116 /++ 9117 Draws a string in the window with the set font (see [setFont] to change it). 9118 9119 Params: 9120 upperLeft = the upper left point of the bounding box of the text 9121 text = the string to draw 9122 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 9123 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 9124 +/ 9125 @scriptable 9126 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 9127 if(impl is null) return; 9128 if(lowerRight.x != 0 || lowerRight.y != 0) { 9129 if(isClipped(upperLeft, lowerRight)) return; 9130 transform(lowerRight); 9131 } else { 9132 if(isClipped(upperLeft, textSize(text))) return; 9133 } 9134 transform(upperLeft); 9135 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 9136 } 9137 9138 /++ 9139 Draws text using a custom font. 9140 9141 This is still MAJOR work in progress. 9142 9143 Creating a [DrawableFont] can be tricky and require additional dependencies. 9144 +/ 9145 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 9146 if(impl is null) return; 9147 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9148 transform(upperLeft); 9149 font.drawString(this, upperLeft, text); 9150 } 9151 9152 version(Windows) 9153 void drawText(Point upperLeft, scope const(wchar)[] text) { 9154 if(impl is null) return; 9155 if(isClipped(upperLeft, Point(int.max, int.max))) return; 9156 transform(upperLeft); 9157 9158 if(text.length && text[$-1] == '\n') 9159 text = text[0 .. $-1]; // tailing newlines are weird on windows... 9160 9161 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 9162 } 9163 9164 static struct TextDrawingContext { 9165 Point boundingBoxUpperLeft; 9166 Point boundingBoxLowerRight; 9167 9168 Point currentLocation; 9169 9170 Point lastDrewUpperLeft; 9171 Point lastDrewLowerRight; 9172 9173 // how do i do right aligned rich text? 9174 // i kinda want to do a pre-made drawing then right align 9175 // draw the whole block. 9176 // 9177 // That's exactly the diff: inline vs block stuff. 9178 9179 // I need to get coordinates of an inline section out too, 9180 // not just a bounding box, but a series of bounding boxes 9181 // should be ok. Consider what's needed to detect a click 9182 // on a link in the middle of a paragraph breaking a line. 9183 // 9184 // Generally, we should be able to get the rectangles of 9185 // any portion we draw. 9186 // 9187 // It also needs to tell what text is left if it overflows 9188 // out of the box, so we can do stuff like float images around 9189 // it. It should not attempt to draw a letter that would be 9190 // clipped. 9191 // 9192 // I might also turn off word wrap stuff. 9193 } 9194 9195 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 9196 if(impl is null) return; 9197 // FIXME 9198 } 9199 9200 /// Drawing an individual pixel is slow. Avoid it if possible. 9201 void drawPixel(Point where) { 9202 if(impl is null) return; 9203 if(isClipped(where)) return; 9204 transform(where); 9205 impl.drawPixel(where.x, where.y); 9206 } 9207 9208 9209 /// Draws a pen using the current pen / outlineColor 9210 @scriptable 9211 void drawLine(Point starting, Point ending) { 9212 if(impl is null) return; 9213 if(isClipped(starting, ending)) return; 9214 transform(starting); 9215 transform(ending); 9216 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 9217 } 9218 9219 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 9220 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 9221 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 9222 @scriptable 9223 void drawRectangle(Point upperLeft, int width, int height) { 9224 if(impl is null) return; 9225 if(isClipped(upperLeft, width, height)) return; 9226 transform(upperLeft); 9227 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 9228 } 9229 9230 /// ditto 9231 void drawRectangle(Point upperLeft, Size size) { 9232 if(impl is null) return; 9233 if(isClipped(upperLeft, size.width, size.height)) return; 9234 transform(upperLeft); 9235 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 9236 } 9237 9238 /// ditto 9239 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 9240 if(impl is null) return; 9241 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 9242 transform(upperLeft); 9243 transform(lowerRightInclusive); 9244 impl.drawRectangle(upperLeft.x, upperLeft.y, 9245 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 9246 } 9247 9248 // overload added on May 12, 2021 9249 /// ditto 9250 void drawRectangle(Rectangle rect) { 9251 drawRectangle(rect.upperLeft, rect.size); 9252 } 9253 9254 /// Arguments are the points of the bounding rectangle 9255 void drawEllipse(Point upperLeft, Point lowerRight) { 9256 if(impl is null) return; 9257 if(isClipped(upperLeft, lowerRight)) return; 9258 transform(upperLeft); 9259 transform(lowerRight); 9260 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 9261 } 9262 9263 /++ 9264 start and finish are units of degrees * 64 9265 9266 History: 9267 The Windows implementation didn't match the Linux implementation until September 24, 2021. 9268 9269 They still don't exactly match in outlining the arc with straight lines (Windows does, Linux doesn't for now). 9270 +/ 9271 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 9272 if(impl is null) return; 9273 // FIXME: not actually implemented 9274 if(isClipped(upperLeft, width, height)) return; 9275 transform(upperLeft); 9276 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 9277 } 9278 9279 /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 9280 void drawCircle(Point upperLeft, int diameter) { 9281 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 9282 } 9283 9284 /// . 9285 void drawPolygon(Point[] vertexes) { 9286 if(impl is null) return; 9287 assert(vertexes.length); 9288 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 9289 foreach(ref vertex; vertexes) { 9290 if(vertex.x < minX) 9291 minX = vertex.x; 9292 if(vertex.y < minY) 9293 minY = vertex.y; 9294 if(vertex.x > maxX) 9295 maxX = vertex.x; 9296 if(vertex.y > maxY) 9297 maxY = vertex.y; 9298 transform(vertex); 9299 } 9300 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 9301 impl.drawPolygon(vertexes); 9302 } 9303 9304 /// ditto 9305 void drawPolygon(Point[] vertexes...) { 9306 if(impl is null) return; 9307 drawPolygon(vertexes); 9308 } 9309 9310 9311 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 9312 9313 //mixin NativeScreenPainterImplementation!() impl; 9314 9315 9316 // HACK: if I mixin the impl directly, it won't let me override the copy 9317 // constructor! The linker complains about there being multiple definitions. 9318 // I'll make the best of it and reference count it though. 9319 ScreenPainterImplementation* impl; 9320 } 9321 9322 // HACK: I need a pointer to the implementation so it's separate 9323 struct ScreenPainterImplementation { 9324 CapableOfBeingDrawnUpon window; 9325 int referenceCount; 9326 mixin NativeScreenPainterImplementation!(); 9327 } 9328 9329 // FIXME: i haven't actually tested the sprite class on MS Windows 9330 9331 /** 9332 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 9333 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 9334 9335 9336 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 9337 though I'm not sure that's ideal and the implementation might change. 9338 9339 You create one by giving a window and an image. It optimizes for that window, 9340 and copies the image into it to use as the initial picture. Creating a sprite 9341 can be quite slow (especially over a network connection) so you should do it 9342 as little as possible and just hold on to your sprite handles after making them. 9343 simpledisplay does try to do its best though, using the XSHM extension if available, 9344 but you should still write your code as if it will always be slow. 9345 9346 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 9347 a fast operation - much faster than drawing the Image itself every time. 9348 9349 `Sprite` represents a scarce resource which should be freed when you 9350 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 9351 after it has been disposed. If you are unsure about this, don't take chances, 9352 just let the garbage collector do it for you. But ideally, you can manage its 9353 lifetime more efficiently. 9354 9355 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 9356 support alpha blending in its drawing at this time. That might change in the 9357 future, but if you need alpha blending right now, use OpenGL instead. See 9358 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 9359 9360 Update: on April 23, 2021, I finally added alpha blending support. You must opt 9361 in by setting the enableAlpha = true in the constructor. 9362 */ 9363 version(OSXCocoa) {} else // NotYetImplementedException 9364 class Sprite : CapableOfBeingDrawnUpon { 9365 9366 /// 9367 ScreenPainter draw() { 9368 return ScreenPainter(this, handle, false); 9369 } 9370 9371 /++ 9372 Copies the sprite's current state into a [TrueColorImage]. 9373 9374 Be warned: this can be a very slow operation 9375 9376 History: 9377 Actually implemented on March 14, 2021 9378 +/ 9379 TrueColorImage takeScreenshot() { 9380 return trueColorImageFromNativeHandle(handle, width, height); 9381 } 9382 9383 void delegate() paintingFinishedDg() { return null; } 9384 bool closed() { return false; } 9385 ScreenPainterImplementation* activeScreenPainter_; 9386 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 9387 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 9388 9389 version(Windows) 9390 private ubyte* rawData; 9391 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 9392 // ditto on the XPicture stuff 9393 9394 version(X11) { 9395 private static XRenderPictFormat* RGB24; 9396 private static XRenderPictFormat* ARGB32; 9397 9398 private Picture xrenderPicture; 9399 } 9400 9401 version(X11) 9402 private static void requireXRender() { 9403 if(!XRenderLibrary.loadAttempted) { 9404 XRenderLibrary.loadDynamicLibrary(); 9405 } 9406 9407 if(!XRenderLibrary.loadSuccessful) 9408 throw new Exception("XRender library load failure"); 9409 9410 auto display = XDisplayConnection.get; 9411 9412 // FIXME: if we migrate X displays, these need to be changed 9413 if(RGB24 is null) 9414 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 9415 if(ARGB32 is null) 9416 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 9417 } 9418 9419 protected this() {} 9420 9421 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 9422 this._width = width; 9423 this._height = height; 9424 this.enableAlpha = enableAlpha; 9425 9426 version(X11) { 9427 auto display = XDisplayConnection.get(); 9428 9429 if(enableAlpha) { 9430 requireXRender(); 9431 } 9432 9433 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 9434 9435 if(enableAlpha) { 9436 XRenderPictureAttributes attrs; 9437 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 9438 } 9439 } else version(Windows) { 9440 version(CRuntime_DigitalMars) { 9441 //if(enableAlpha) 9442 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 9443 } 9444 9445 BITMAPINFO infoheader; 9446 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 9447 infoheader.bmiHeader.biWidth = width; 9448 infoheader.bmiHeader.biHeight = height; 9449 infoheader.bmiHeader.biPlanes = 1; 9450 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 9451 infoheader.bmiHeader.biCompression = BI_RGB; 9452 9453 // FIXME: this should prolly be a device dependent bitmap... 9454 handle = CreateDIBSection( 9455 null, 9456 &infoheader, 9457 DIB_RGB_COLORS, 9458 cast(void**) &rawData, 9459 null, 9460 0); 9461 9462 if(handle is null) 9463 throw new Exception("couldn't create pixmap"); 9464 } 9465 } 9466 9467 /// Makes a sprite based on the image with the initial contents from the Image 9468 this(SimpleWindow win, Image i) { 9469 this(win, i.width, i.height, i.enableAlpha); 9470 9471 version(X11) { 9472 auto display = XDisplayConnection.get(); 9473 auto gc = XCreateGC(display, this.handle, 0, null); 9474 scope(exit) XFreeGC(display, gc); 9475 if(i.usingXshm) 9476 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 9477 else 9478 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 9479 } else version(Windows) { 9480 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 9481 auto arrLength = itemsPerLine * height; 9482 rawData[0..arrLength] = i.rawData[0..arrLength]; 9483 } else version(OSXCocoa) { 9484 // FIXME: I have no idea if this is even any good 9485 ubyte* rawData; 9486 9487 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 9488 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 9489 colorSpace, 9490 kCGImageAlphaPremultipliedLast 9491 |kCGBitmapByteOrder32Big); 9492 CGColorSpaceRelease(colorSpace); 9493 rawData = CGBitmapContextGetData(context); 9494 9495 auto rdl = (width * height * 4); 9496 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 9497 } else static assert(0); 9498 } 9499 9500 /++ 9501 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 9502 9503 Params: 9504 where = point on the window where the upper left corner of the image will be drawn 9505 imageUpperLeft = point on the image to start the slice to draw 9506 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. 9507 History: 9508 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 9509 +/ 9510 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 9511 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 9512 } 9513 9514 /// Call this when you're ready to get rid of it 9515 void dispose() { 9516 version(X11) { 9517 staticDispose(xrenderPicture, handle); 9518 xrenderPicture = None; 9519 handle = None; 9520 } else version(Windows) { 9521 staticDispose(handle); 9522 handle = null; 9523 } else version(OSXCocoa) { 9524 staticDispose(context); 9525 context = null; 9526 } else static assert(0); 9527 9528 } 9529 9530 version(X11) 9531 static void staticDispose(Picture xrenderPicture, Pixmap handle) { 9532 if(xrenderPicture) 9533 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 9534 if(handle) 9535 XFreePixmap(XDisplayConnection.get(), handle); 9536 } 9537 else version(Windows) 9538 static void staticDispose(HBITMAP handle) { 9539 if(handle) 9540 DeleteObject(handle); 9541 } 9542 else version(OSXCocoa) 9543 static void staticDispose(CGContextRef context) { 9544 if(context) 9545 CGContextRelease(context); 9546 } 9547 9548 ~this() { 9549 version(X11) { if(xrenderPicture || handle) 9550 cleanupQueue.queue!staticDispose(xrenderPicture, handle); 9551 } else version(Windows) { if(handle) 9552 cleanupQueue.queue!staticDispose(handle); 9553 } else version(OSXCocoa) { if(context) 9554 cleanupQueue.queue!staticDispose(context); 9555 } else static assert(0); 9556 } 9557 9558 /// 9559 final @property int width() { return _width; } 9560 9561 /// 9562 final @property int height() { return _height; } 9563 9564 /// 9565 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 9566 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 9567 } 9568 9569 auto nativeHandle() { 9570 return handle; 9571 } 9572 9573 private: 9574 9575 int _width; 9576 int _height; 9577 bool enableAlpha; 9578 version(X11) 9579 Pixmap handle; 9580 else version(Windows) 9581 HBITMAP handle; 9582 else version(OSXCocoa) 9583 CGContextRef context; 9584 else static assert(0); 9585 } 9586 9587 /++ 9588 Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. 9589 9590 History: 9591 Added November 20, 2021 (dub v10.4) 9592 +/ 9593 abstract class Gradient : Sprite { 9594 protected this(int w, int h) { 9595 version(X11) { 9596 Sprite.requireXRender(); 9597 9598 super(); 9599 enableAlpha = true; 9600 _width = w; 9601 _height = h; 9602 } else version(Windows) { 9603 super(null, w, h, true); // on Windows i'm just making a bitmap myself 9604 } 9605 } 9606 9607 version(Windows) 9608 final void forEachPixel(scope Color delegate(int x, int y) dg) { 9609 auto ptr = rawData; 9610 foreach(j; 0 .. _height) 9611 foreach(i; 0 .. _width) { 9612 auto color = dg(i, _height - j - 1); // cuz of upside down bitmap 9613 *rawData = (color.a * color.b) / 255; rawData++; 9614 *rawData = (color.a * color.g) / 255; rawData++; 9615 *rawData = (color.a * color.r) / 255; rawData++; 9616 *rawData = color.a; rawData++; 9617 } 9618 } 9619 9620 version(X11) 9621 protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { 9622 assert(stops.length > 0); 9623 assert(stops.length <= 16, "I got lazy with buffers"); 9624 9625 XFixed[16] stopsPositions = void; 9626 XRenderColor[16] colors = void; 9627 9628 foreach(idx, stop; stops) { 9629 stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); 9630 auto c = stop.c; 9631 colors[idx] = XRenderColor( 9632 cast(ushort)(c.r * ushort.max / 255), 9633 cast(ushort)(c.g * ushort.max / 255), 9634 cast(ushort)(c.b * ushort.max / 255), 9635 cast(ushort)(c.a * ubyte.max) // max value here is fractional 9636 ); 9637 } 9638 9639 xrenderPicture = dg(stopsPositions, colors); 9640 } 9641 9642 /// 9643 static struct Stop { 9644 float percentage; /// between 0 and 1.0 9645 Color c; 9646 } 9647 } 9648 9649 /++ 9650 Creates a linear gradient between p1 and p2. 9651 9652 X ONLY RIGHT NOW 9653 9654 History: 9655 Added November 20, 2021 (dub v10.4) 9656 9657 Bugs: 9658 Not yet implemented on Windows. 9659 +/ 9660 class LinearGradient : Gradient { 9661 /++ 9662 9663 +/ 9664 this(Point p1, Point p2, Stop[] stops...) { 9665 super(p2.x, p2.y); 9666 9667 version(X11) { 9668 XLinearGradient gradient; 9669 gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); 9670 gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); 9671 9672 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9673 return XRenderCreateLinearGradient( 9674 XDisplayConnection.get, 9675 &gradient, 9676 stopsPositions.ptr, 9677 colors.ptr, 9678 cast(int) stops.length); 9679 }); 9680 } else version(Windows) { 9681 // FIXME 9682 forEachPixel((int x, int y) { 9683 import core.stdc.math; 9684 9685 //sqrtf( 9686 9687 return Color.transparent; 9688 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9689 }); 9690 } 9691 } 9692 } 9693 9694 /++ 9695 A conical gradient goes from color to color around a circumference from a center point. 9696 9697 X ONLY RIGHT NOW 9698 9699 History: 9700 Added November 20, 2021 (dub v10.4) 9701 9702 Bugs: 9703 Not yet implemented on Windows. 9704 +/ 9705 class ConicalGradient : Gradient { 9706 /++ 9707 9708 +/ 9709 this(Point center, float angleInDegrees, Stop[] stops...) { 9710 super(center.x * 2, center.y * 2); 9711 9712 version(X11) { 9713 XConicalGradient gradient; 9714 gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); 9715 gradient.angle = cast(int)(angleInDegrees * ushort.max); 9716 9717 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9718 return XRenderCreateConicalGradient( 9719 XDisplayConnection.get, 9720 &gradient, 9721 stopsPositions.ptr, 9722 colors.ptr, 9723 cast(int) stops.length); 9724 }); 9725 } else version(Windows) { 9726 // FIXME 9727 forEachPixel((int x, int y) { 9728 import core.stdc.math; 9729 9730 //sqrtf( 9731 9732 return Color.transparent; 9733 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9734 }); 9735 9736 } 9737 } 9738 } 9739 9740 /++ 9741 A radial gradient goes from color to color based on distance from the center. 9742 It is like rings of color. 9743 9744 X ONLY RIGHT NOW 9745 9746 9747 More specifically, you create two circles: an inner circle and an outer circle. 9748 The gradient is only drawn in the area outside the inner circle but inside the outer 9749 circle. The closest line between those two circles forms the line for the gradient 9750 and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. 9751 9752 History: 9753 Added November 20, 2021 (dub v10.4) 9754 9755 Bugs: 9756 Not yet implemented on Windows. 9757 +/ 9758 class RadialGradient : Gradient { 9759 /++ 9760 9761 +/ 9762 this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { 9763 super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); 9764 9765 version(X11) { 9766 XRadialGradient gradient; 9767 gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); 9768 gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); 9769 9770 helper(stops, (scope XFixed[] stopsPositions, scope XRenderColor[] colors) { 9771 return XRenderCreateRadialGradient( 9772 XDisplayConnection.get, 9773 &gradient, 9774 stopsPositions.ptr, 9775 colors.ptr, 9776 cast(int) stops.length); 9777 }); 9778 } else version(Windows) { 9779 // FIXME 9780 forEachPixel((int x, int y) { 9781 import core.stdc.math; 9782 9783 //sqrtf( 9784 9785 return Color.transparent; 9786 // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful 9787 }); 9788 } 9789 } 9790 } 9791 9792 9793 9794 /+ 9795 NOT IMPLEMENTED 9796 9797 A display-stored image optimized for relatively quick drawing, like 9798 [Sprite], but this one supports alpha channel blending and does NOT 9799 support direct drawing upon it with a [ScreenPainter]. 9800 9801 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 9802 plain [ScreenPainter]... sort of. 9803 9804 On X11, it requires the Xrender extension and library. This is available 9805 almost everywhere though. 9806 9807 History: 9808 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 9809 +/ 9810 version(none) 9811 class AlphaSprite { 9812 /++ 9813 Copies the given image into it. 9814 +/ 9815 this(MemoryImage img) { 9816 9817 if(!XRenderLibrary.loadAttempted) { 9818 XRenderLibrary.loadDynamicLibrary(); 9819 9820 // FIXME: this needs to be reconstructed when the X server changes 9821 repopulateX(); 9822 } 9823 if(!XRenderLibrary.loadSuccessful) 9824 throw new Exception("XRender library load failure"); 9825 9826 // I probably need to put the alpha mask in a separate Picture 9827 // ugh 9828 // maybe the Sprite itself can have an alpha bitmask anyway 9829 9830 9831 auto display = XDisplayConnection.get(); 9832 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 9833 9834 9835 XRenderPictureAttributes attrs; 9836 9837 handle = XRenderCreatePicture( 9838 XDisplayConnection.get, 9839 pixmap, 9840 RGBA, 9841 0, 9842 &attrs 9843 ); 9844 9845 } 9846 9847 // maybe i'll use the create gradient functions too with static factories.. 9848 9849 void drawAt(ScreenPainter painter, Point where) { 9850 //painter.drawPixmap(this, where); 9851 9852 XRenderPictureAttributes attrs; 9853 9854 auto pic = XRenderCreatePicture( 9855 XDisplayConnection.get, 9856 painter.impl.d, 9857 RGB, 9858 0, 9859 &attrs 9860 ); 9861 9862 XRenderComposite( 9863 XDisplayConnection.get, 9864 3, // PictOpOver 9865 handle, 9866 None, 9867 pic, 9868 0, // src 9869 0, 9870 0, // mask 9871 0, 9872 10, // dest 9873 10, 9874 100, // width 9875 100 9876 ); 9877 9878 /+ 9879 XRenderFreePicture( 9880 XDisplayConnection.get, 9881 pic 9882 ); 9883 9884 XRenderFreePicture( 9885 XDisplayConnection.get, 9886 fill 9887 ); 9888 +/ 9889 // on Windows you can stretch but Xrender still can't :( 9890 } 9891 9892 static XRenderPictFormat* RGB; 9893 static XRenderPictFormat* RGBA; 9894 static void repopulateX() { 9895 auto display = XDisplayConnection.get; 9896 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 9897 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 9898 } 9899 9900 XPixmap pixmap; 9901 Picture handle; 9902 } 9903 9904 /// 9905 interface CapableOfBeingDrawnUpon { 9906 /// 9907 ScreenPainter draw(); 9908 /// 9909 int width(); 9910 /// 9911 int height(); 9912 protected ScreenPainterImplementation* activeScreenPainter(); 9913 protected void activeScreenPainter(ScreenPainterImplementation*); 9914 bool closed(); 9915 9916 void delegate() paintingFinishedDg(); 9917 9918 /// Be warned: this can be a very slow operation 9919 TrueColorImage takeScreenshot(); 9920 } 9921 9922 /// 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]. 9923 void flushGui() { 9924 version(X11) { 9925 auto dpy = XDisplayConnection.get(); 9926 XLockDisplay(dpy); 9927 scope(exit) XUnlockDisplay(dpy); 9928 XFlush(dpy); 9929 } 9930 } 9931 9932 /++ 9933 Runs the given code in the GUI thread when its event loop 9934 is available, blocking until it completes. This allows you 9935 to create and manipulate windows from another thread without 9936 invoking undefined behavior. 9937 9938 If this is the gui thread, it runs the code immediately. 9939 9940 If no gui thread exists yet, the current thread is assumed 9941 to be it. Attempting to create windows or run the event loop 9942 in any other thread will cause an assertion failure. 9943 9944 9945 $(TIP 9946 Did you know you can use UFCS on delegate literals? 9947 9948 () { 9949 // code here 9950 }.runInGuiThread; 9951 ) 9952 9953 Returns: 9954 `true` if the function was called, `false` if it was not. 9955 The function may not be called because the gui thread had 9956 already terminated by the time you called this. 9957 9958 History: 9959 Added April 10, 2020 (v7.2.0) 9960 9961 Return value added and implementation tweaked to avoid locking 9962 at program termination on February 24, 2021 (v9.2.1). 9963 +/ 9964 bool runInGuiThread(scope void delegate() dg) @trusted { 9965 claimGuiThread(); 9966 9967 if(thisIsGuiThread) { 9968 dg(); 9969 return true; 9970 } 9971 9972 if(guiThreadTerminating) 9973 return false; 9974 9975 import core.sync.semaphore; 9976 static Semaphore sc; 9977 if(sc is null) 9978 sc = new Semaphore(); 9979 9980 static RunQueueMember* rqm; 9981 if(rqm is null) 9982 rqm = new RunQueueMember; 9983 rqm.dg = cast(typeof(rqm.dg)) dg; 9984 rqm.signal = sc; 9985 rqm.thrown = null; 9986 9987 synchronized(runInGuiThreadLock) { 9988 runInGuiThreadQueue ~= rqm; 9989 } 9990 9991 if(!SimpleWindow.eventWakeUp()) 9992 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 9993 9994 rqm.signal.wait(); 9995 auto t = rqm.thrown; 9996 9997 if(t) 9998 throw t; 9999 10000 return true; 10001 } 10002 10003 // note it runs sync if this is the gui thread.... 10004 void runInGuiThreadAsync(void delegate() dg, void delegate(Exception) nothrow handleError = null) nothrow { 10005 claimGuiThread(); 10006 10007 try { 10008 10009 if(thisIsGuiThread) { 10010 dg(); 10011 return; 10012 } 10013 10014 if(guiThreadTerminating) 10015 return; 10016 10017 RunQueueMember* rqm = new RunQueueMember; 10018 rqm.dg = cast(typeof(rqm.dg)) dg; 10019 rqm.signal = null; 10020 rqm.thrown = null; 10021 10022 synchronized(runInGuiThreadLock) { 10023 runInGuiThreadQueue ~= rqm; 10024 } 10025 10026 if(!SimpleWindow.eventWakeUp()) 10027 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 10028 } catch(Exception e) { 10029 if(handleError) 10030 handleError(e); 10031 } 10032 } 10033 10034 private void runPendingRunInGuiThreadDelegates() { 10035 more: 10036 RunQueueMember* next; 10037 synchronized(runInGuiThreadLock) { 10038 if(runInGuiThreadQueue.length) { 10039 next = runInGuiThreadQueue[0]; 10040 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 10041 } else { 10042 next = null; 10043 } 10044 } 10045 10046 if(next) { 10047 try { 10048 next.dg(); 10049 next.thrown = null; 10050 } catch(Throwable t) { 10051 next.thrown = t; 10052 } 10053 10054 if(next.signal) 10055 next.signal.notify(); 10056 10057 goto more; 10058 } 10059 } 10060 10061 private void claimGuiThread() nothrow { 10062 import core.atomic; 10063 if(cas(&guiThreadExists_, false, true)) 10064 thisIsGuiThread = true; 10065 } 10066 10067 private struct RunQueueMember { 10068 void delegate() dg; 10069 import core.sync.semaphore; 10070 Semaphore signal; 10071 Throwable thrown; 10072 } 10073 10074 private __gshared RunQueueMember*[] runInGuiThreadQueue; 10075 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 10076 private bool thisIsGuiThread = false; 10077 private shared bool guiThreadExists_ = false; 10078 private shared bool guiThreadTerminating = false; 10079 10080 /++ 10081 Returns `true` if a gui thread exists, that is, a thread running the simpledisplay.d 10082 event loop. All windows must be exclusively created and managed by a single thread. 10083 10084 If no gui thread exists, simpledisplay.d will automatically adopt the current thread 10085 when you call one of its constructors. 10086 10087 If a gui thread exists, you should check [thisThreadRunningGui] to see if it is this 10088 one. If so, you can run gui functions on it. If not, don't. The helper functions 10089 [runInGuiThread] and [runInGuiThreadAsync] can be used to help you with this automatically. 10090 10091 The reason this function is available is in case you want to message pass between a gui 10092 thread and your current thread. If no gui thread exists or if this is the gui thread, 10093 you're liable to deadlock when trying to communicate since you'd end up talking to yourself. 10094 10095 History: 10096 Added December 3, 2021 (dub v10.5) 10097 +/ 10098 public bool guiThreadExists() { 10099 return guiThreadExists_; 10100 } 10101 10102 /++ 10103 Returns `true` if this thread is either running or set to be running the 10104 simpledisplay.d gui core event loop because it owns windows. 10105 10106 It is important to keep gui-related functionality in the right thread, so you will 10107 want to `runInGuiThread` when you call them (with some specific exceptions called 10108 out in those specific functions' documentation). Notably, all windows must be 10109 created and managed only from the gui thread. 10110 10111 Will return false if simpledisplay's other functions haven't been called 10112 yet; check [guiThreadExists] in addition to this. 10113 10114 History: 10115 Added December 3, 2021 (dub v10.5) 10116 +/ 10117 public bool thisThreadRunningGui() { 10118 return thisIsGuiThread; 10119 } 10120 10121 /++ 10122 Function to help temporarily print debugging info. It will bypass any stdout/err redirection 10123 and go to the controlling tty or console (attaching to the parent and/or allocating one as 10124 needed on Windows. Please note it may overwrite output from other programs in the parent and the 10125 allocated one will not survive if your program crashes. Use the `fileOverride` to print to a log 10126 file instead if you are in one of those situations). 10127 10128 It does not support outputting very many types; just strings and ints are likely to actually work. 10129 10130 It will perform very slowly and swallows any errors that may occur. Moreover, the specific output 10131 is unspecified meaning I can change it at any time. The only point of this function is to help 10132 in temporary use for printf-style debugging. It is NOT nogc, but you can use the `debug` keyword 10133 and the compiler will cheat for you. It is, however, formally nothrow and trusted to ease its use 10134 in those contexts. 10135 10136 $(WARNING 10137 I reserve the right to change this function at any time. You can use it if it helps you 10138 but do not rely on it for anything permanent. 10139 ) 10140 10141 History: 10142 Added December 3, 2021. Not formally supported under any stable tag. 10143 +/ 10144 void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted { 10145 try { 10146 version(Windows) { 10147 import core.sys.windows.wincon; 10148 if(!AttachConsole(ATTACH_PARENT_PROCESS)) 10149 AllocConsole(); 10150 const(char)* fn = "CONOUT$"; 10151 } else version(Posix) { 10152 const(char)* fn = "/dev/tty"; 10153 } else static assert(0, "Function not implemented for your system"); 10154 10155 if(fileOverride.length) 10156 fn = fileOverride.ptr; 10157 10158 import core.stdc.stdio; 10159 auto fp = fopen(fn, "wt"); 10160 if(fp is null) return; 10161 scope(exit) fclose(fp); 10162 10163 string str; 10164 foreach(item; t) { 10165 static if(is(typeof(item) : const(char)[])) 10166 str ~= item; 10167 else 10168 str ~= toInternal!string(item); 10169 str ~= " "; 10170 } 10171 str ~= "\n"; 10172 10173 fwrite(str.ptr, 1, str.length, fp); 10174 fflush(fp); 10175 } catch(Exception e) { 10176 // sorry no hope 10177 } 10178 } 10179 10180 private void guiThreadFinalize() { 10181 assert(thisIsGuiThread); 10182 10183 guiThreadTerminating = true; // don't add any more from this point on 10184 runPendingRunInGuiThreadDelegates(); 10185 } 10186 10187 /+ 10188 interface IPromise { 10189 void reportProgress(int current, int max, string message); 10190 10191 /+ // not formally in cuz of templates but still 10192 IPromise Then(); 10193 IPromise Catch(); 10194 IPromise Finally(); 10195 +/ 10196 } 10197 10198 /+ 10199 auto promise = async({ ... }); 10200 promise.Then(whatever). 10201 Then(whateverelse). 10202 Catch((exception) { }); 10203 10204 10205 A promise is run inside a fiber and it looks something like: 10206 10207 try { 10208 auto res = whatever(); 10209 auto res2 = whateverelse(res); 10210 } catch(Exception e) { 10211 { }(e); 10212 } 10213 10214 When a thing succeeds, it is passed as an arg to the next 10215 +/ 10216 class Promise(T) : IPromise { 10217 auto Then() { return null; } 10218 auto Catch() { return null; } 10219 auto Finally() { return null; } 10220 10221 // wait for it to resolve and return the value, or rethrow the error if that occurred. 10222 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 10223 T await(); 10224 } 10225 10226 interface Task { 10227 } 10228 10229 interface Resolvable(T) : Task { 10230 void run(); 10231 10232 void resolve(T); 10233 10234 Resolvable!T then(void delegate(T)); // returns a new promise 10235 Resolvable!T error(Throwable); // js catch 10236 Resolvable!T completed(); // js finally 10237 10238 } 10239 10240 /++ 10241 Runs `work` in a helper thread and sends its return value back to the main gui 10242 thread as the argument to `uponCompletion`. If `work` throws, the exception is 10243 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 10244 kill the program. 10245 10246 You can call reportProgress(position, max, message) to update your parent window 10247 on your progress. 10248 10249 I should also use `shared` methods. FIXME 10250 10251 History: 10252 Added March 6, 2021 (dub version 9.3). 10253 +/ 10254 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 10255 uponCompletion(work(null)); 10256 } 10257 10258 +/ 10259 10260 /// Used internal to dispatch events to various classes. 10261 interface CapableOfHandlingNativeEvent { 10262 NativeEventHandler getNativeEventHandler(); 10263 10264 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 10265 10266 version(X11) { 10267 // if this is impossible, you are allowed to just throw from it 10268 // Note: if you call it from another object, set a flag cuz the manger will call you again 10269 void recreateAfterDisconnect(); 10270 // discard any *connection specific* state, but keep enough that you 10271 // can be recreated if possible. discardConnectionState() is always called immediately 10272 // before recreateAfterDisconnect(), so you can set a flag there to decide if 10273 // you need initialization order 10274 void discardConnectionState(); 10275 } 10276 } 10277 10278 version(X11) 10279 /++ 10280 State of keys on mouse events, especially motion. 10281 10282 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 10283 +/ 10284 enum ModifierState : uint { 10285 shift = 1, /// 10286 capsLock = 2, /// 10287 ctrl = 4, /// 10288 alt = 8, /// Not always available on Windows 10289 windows = 64, /// ditto 10290 numLock = 16, /// 10291 10292 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10293 middleButtonDown = 512, /// ditto 10294 rightButtonDown = 1024, /// ditto 10295 } 10296 else version(Windows) 10297 /// ditto 10298 enum ModifierState : uint { 10299 shift = 4, /// 10300 ctrl = 8, /// 10301 10302 // i'm not sure if the next two are available 10303 alt = 256, /// not always available on Windows 10304 windows = 512, /// ditto 10305 10306 capsLock = 1024, /// 10307 numLock = 2048, /// 10308 10309 leftButtonDown = 1, /// not available on key events 10310 middleButtonDown = 16, /// ditto 10311 rightButtonDown = 2, /// ditto 10312 10313 backButtonDown = 0x20, /// not available on X 10314 forwardButtonDown = 0x40, /// ditto 10315 } 10316 else version(OSXCocoa) 10317 // FIXME FIXME NotYetImplementedException 10318 enum ModifierState : uint { 10319 shift = 1, /// 10320 capsLock = 2, /// 10321 ctrl = 4, /// 10322 alt = 8, /// Not always available on Windows 10323 windows = 64, /// ditto 10324 numLock = 16, /// 10325 10326 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 10327 middleButtonDown = 512, /// ditto 10328 rightButtonDown = 1024, /// ditto 10329 } 10330 10331 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them. 10332 enum MouseButton : int { 10333 none = 0, 10334 left = 1, /// 10335 right = 2, /// 10336 middle = 4, /// 10337 wheelUp = 8, /// 10338 wheelDown = 16, /// 10339 backButton = 32, /// often found on the thumb and used for back in browsers 10340 forwardButton = 64, /// often found on the thumb and used for forward in browsers 10341 } 10342 10343 version(X11) { 10344 // FIXME: match ASCII whenever we can. Most of it is already there, 10345 // but there's a few exceptions and mismatches with Windows 10346 10347 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 10348 enum Key { 10349 Escape = 0xff1b, /// 10350 F1 = 0xffbe, /// 10351 F2 = 0xffbf, /// 10352 F3 = 0xffc0, /// 10353 F4 = 0xffc1, /// 10354 F5 = 0xffc2, /// 10355 F6 = 0xffc3, /// 10356 F7 = 0xffc4, /// 10357 F8 = 0xffc5, /// 10358 F9 = 0xffc6, /// 10359 F10 = 0xffc7, /// 10360 F11 = 0xffc8, /// 10361 F12 = 0xffc9, /// 10362 PrintScreen = 0xff61, /// 10363 ScrollLock = 0xff14, /// 10364 Pause = 0xff13, /// 10365 Grave = 0x60, /// The $(BACKTICK) ~ key 10366 // number keys across the top of the keyboard 10367 N1 = 0x31, /// Number key atop the keyboard 10368 N2 = 0x32, /// 10369 N3 = 0x33, /// 10370 N4 = 0x34, /// 10371 N5 = 0x35, /// 10372 N6 = 0x36, /// 10373 N7 = 0x37, /// 10374 N8 = 0x38, /// 10375 N9 = 0x39, /// 10376 N0 = 0x30, /// 10377 Dash = 0x2d, /// 10378 Equals = 0x3d, /// 10379 Backslash = 0x5c, /// The \ | key 10380 Backspace = 0xff08, /// 10381 Insert = 0xff63, /// 10382 Home = 0xff50, /// 10383 PageUp = 0xff55, /// 10384 Delete = 0xffff, /// 10385 End = 0xff57, /// 10386 PageDown = 0xff56, /// 10387 Up = 0xff52, /// 10388 Down = 0xff54, /// 10389 Left = 0xff51, /// 10390 Right = 0xff53, /// 10391 10392 Tab = 0xff09, /// 10393 Q = 0x71, /// 10394 W = 0x77, /// 10395 E = 0x65, /// 10396 R = 0x72, /// 10397 T = 0x74, /// 10398 Y = 0x79, /// 10399 U = 0x75, /// 10400 I = 0x69, /// 10401 O = 0x6f, /// 10402 P = 0x70, /// 10403 LeftBracket = 0x5b, /// the [ { key 10404 RightBracket = 0x5d, /// the ] } key 10405 CapsLock = 0xffe5, /// 10406 A = 0x61, /// 10407 S = 0x73, /// 10408 D = 0x64, /// 10409 F = 0x66, /// 10410 G = 0x67, /// 10411 H = 0x68, /// 10412 J = 0x6a, /// 10413 K = 0x6b, /// 10414 L = 0x6c, /// 10415 Semicolon = 0x3b, /// 10416 Apostrophe = 0x27, /// 10417 Enter = 0xff0d, /// 10418 Shift = 0xffe1, /// 10419 Z = 0x7a, /// 10420 X = 0x78, /// 10421 C = 0x63, /// 10422 V = 0x76, /// 10423 B = 0x62, /// 10424 N = 0x6e, /// 10425 M = 0x6d, /// 10426 Comma = 0x2c, /// 10427 Period = 0x2e, /// 10428 Slash = 0x2f, /// the / ? key 10429 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 10430 Ctrl = 0xffe3, /// 10431 Windows = 0xffeb, /// 10432 Alt = 0xffe9, /// 10433 Space = 0x20, /// 10434 Alt_r = 0xffea, /// ditto of shift_r 10435 Windows_r = 0xffec, /// 10436 Menu = 0xff67, /// 10437 Ctrl_r = 0xffe4, /// 10438 10439 NumLock = 0xff7f, /// 10440 Divide = 0xffaf, /// The / key on the number pad 10441 Multiply = 0xffaa, /// The * key on the number pad 10442 Minus = 0xffad, /// The - key on the number pad 10443 Plus = 0xffab, /// The + key on the number pad 10444 PadEnter = 0xff8d, /// Numberpad enter key 10445 Pad1 = 0xff9c, /// Numberpad keys 10446 Pad2 = 0xff99, /// 10447 Pad3 = 0xff9b, /// 10448 Pad4 = 0xff96, /// 10449 Pad5 = 0xff9d, /// 10450 Pad6 = 0xff98, /// 10451 Pad7 = 0xff95, /// 10452 Pad8 = 0xff97, /// 10453 Pad9 = 0xff9a, /// 10454 Pad0 = 0xff9e, /// 10455 PadDot = 0xff9f, /// 10456 } 10457 } else version(Windows) { 10458 // the character here is for en-us layouts and for illustration only 10459 // if you actually want to get characters, wait for character events 10460 // (the argument to your event handler is simply a dchar) 10461 // those will be converted by the OS for the right locale. 10462 10463 enum Key { 10464 Escape = 0x1b, 10465 F1 = 0x70, 10466 F2 = 0x71, 10467 F3 = 0x72, 10468 F4 = 0x73, 10469 F5 = 0x74, 10470 F6 = 0x75, 10471 F7 = 0x76, 10472 F8 = 0x77, 10473 F9 = 0x78, 10474 F10 = 0x79, 10475 F11 = 0x7a, 10476 F12 = 0x7b, 10477 PrintScreen = 0x2c, 10478 ScrollLock = 0x91, 10479 Pause = 0x13, 10480 Grave = 0xc0, 10481 // number keys across the top of the keyboard 10482 N1 = 0x31, 10483 N2 = 0x32, 10484 N3 = 0x33, 10485 N4 = 0x34, 10486 N5 = 0x35, 10487 N6 = 0x36, 10488 N7 = 0x37, 10489 N8 = 0x38, 10490 N9 = 0x39, 10491 N0 = 0x30, 10492 Dash = 0xbd, 10493 Equals = 0xbb, 10494 Backslash = 0xdc, 10495 Backspace = 0x08, 10496 Insert = 0x2d, 10497 Home = 0x24, 10498 PageUp = 0x21, 10499 Delete = 0x2e, 10500 End = 0x23, 10501 PageDown = 0x22, 10502 Up = 0x26, 10503 Down = 0x28, 10504 Left = 0x25, 10505 Right = 0x27, 10506 10507 Tab = 0x09, 10508 Q = 0x51, 10509 W = 0x57, 10510 E = 0x45, 10511 R = 0x52, 10512 T = 0x54, 10513 Y = 0x59, 10514 U = 0x55, 10515 I = 0x49, 10516 O = 0x4f, 10517 P = 0x50, 10518 LeftBracket = 0xdb, 10519 RightBracket = 0xdd, 10520 CapsLock = 0x14, 10521 A = 0x41, 10522 S = 0x53, 10523 D = 0x44, 10524 F = 0x46, 10525 G = 0x47, 10526 H = 0x48, 10527 J = 0x4a, 10528 K = 0x4b, 10529 L = 0x4c, 10530 Semicolon = 0xba, 10531 Apostrophe = 0xde, 10532 Enter = 0x0d, 10533 Shift = 0x10, 10534 Z = 0x5a, 10535 X = 0x58, 10536 C = 0x43, 10537 V = 0x56, 10538 B = 0x42, 10539 N = 0x4e, 10540 M = 0x4d, 10541 Comma = 0xbc, 10542 Period = 0xbe, 10543 Slash = 0xbf, 10544 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10545 Ctrl = 0x11, 10546 Windows = 0x5b, 10547 Alt = -5, // FIXME 10548 Space = 0x20, 10549 Alt_r = 0xffea, // ditto of shift_r 10550 Windows_r = 0x5c, // ditto of shift_r 10551 Menu = 0x5d, 10552 Ctrl_r = 0xa3, // ditto of shift_r 10553 10554 NumLock = 0x90, 10555 Divide = 0x6f, 10556 Multiply = 0x6a, 10557 Minus = 0x6d, 10558 Plus = 0x6b, 10559 PadEnter = -8, // FIXME 10560 Pad1 = 0x61, 10561 Pad2 = 0x62, 10562 Pad3 = 0x63, 10563 Pad4 = 0x64, 10564 Pad5 = 0x65, 10565 Pad6 = 0x66, 10566 Pad7 = 0x67, 10567 Pad8 = 0x68, 10568 Pad9 = 0x69, 10569 Pad0 = 0x60, 10570 PadDot = 0x6e, 10571 } 10572 10573 // I'm keeping this around for reference purposes 10574 // ideally all these buttons will be listed for all platforms, 10575 // but now now I'm just focusing on my US keyboard 10576 version(none) 10577 enum Key { 10578 LBUTTON = 0x01, 10579 RBUTTON = 0x02, 10580 CANCEL = 0x03, 10581 MBUTTON = 0x04, 10582 //static if (_WIN32_WINNT > = 0x500) { 10583 XBUTTON1 = 0x05, 10584 XBUTTON2 = 0x06, 10585 //} 10586 BACK = 0x08, 10587 TAB = 0x09, 10588 CLEAR = 0x0C, 10589 RETURN = 0x0D, 10590 SHIFT = 0x10, 10591 CONTROL = 0x11, 10592 MENU = 0x12, 10593 PAUSE = 0x13, 10594 CAPITAL = 0x14, 10595 KANA = 0x15, 10596 HANGEUL = 0x15, 10597 HANGUL = 0x15, 10598 JUNJA = 0x17, 10599 FINAL = 0x18, 10600 HANJA = 0x19, 10601 KANJI = 0x19, 10602 ESCAPE = 0x1B, 10603 CONVERT = 0x1C, 10604 NONCONVERT = 0x1D, 10605 ACCEPT = 0x1E, 10606 MODECHANGE = 0x1F, 10607 SPACE = 0x20, 10608 PRIOR = 0x21, 10609 NEXT = 0x22, 10610 END = 0x23, 10611 HOME = 0x24, 10612 LEFT = 0x25, 10613 UP = 0x26, 10614 RIGHT = 0x27, 10615 DOWN = 0x28, 10616 SELECT = 0x29, 10617 PRINT = 0x2A, 10618 EXECUTE = 0x2B, 10619 SNAPSHOT = 0x2C, 10620 INSERT = 0x2D, 10621 DELETE = 0x2E, 10622 HELP = 0x2F, 10623 LWIN = 0x5B, 10624 RWIN = 0x5C, 10625 APPS = 0x5D, 10626 SLEEP = 0x5F, 10627 NUMPAD0 = 0x60, 10628 NUMPAD1 = 0x61, 10629 NUMPAD2 = 0x62, 10630 NUMPAD3 = 0x63, 10631 NUMPAD4 = 0x64, 10632 NUMPAD5 = 0x65, 10633 NUMPAD6 = 0x66, 10634 NUMPAD7 = 0x67, 10635 NUMPAD8 = 0x68, 10636 NUMPAD9 = 0x69, 10637 MULTIPLY = 0x6A, 10638 ADD = 0x6B, 10639 SEPARATOR = 0x6C, 10640 SUBTRACT = 0x6D, 10641 DECIMAL = 0x6E, 10642 DIVIDE = 0x6F, 10643 F1 = 0x70, 10644 F2 = 0x71, 10645 F3 = 0x72, 10646 F4 = 0x73, 10647 F5 = 0x74, 10648 F6 = 0x75, 10649 F7 = 0x76, 10650 F8 = 0x77, 10651 F9 = 0x78, 10652 F10 = 0x79, 10653 F11 = 0x7A, 10654 F12 = 0x7B, 10655 F13 = 0x7C, 10656 F14 = 0x7D, 10657 F15 = 0x7E, 10658 F16 = 0x7F, 10659 F17 = 0x80, 10660 F18 = 0x81, 10661 F19 = 0x82, 10662 F20 = 0x83, 10663 F21 = 0x84, 10664 F22 = 0x85, 10665 F23 = 0x86, 10666 F24 = 0x87, 10667 NUMLOCK = 0x90, 10668 SCROLL = 0x91, 10669 LSHIFT = 0xA0, 10670 RSHIFT = 0xA1, 10671 LCONTROL = 0xA2, 10672 RCONTROL = 0xA3, 10673 LMENU = 0xA4, 10674 RMENU = 0xA5, 10675 //static if (_WIN32_WINNT > = 0x500) { 10676 BROWSER_BACK = 0xA6, 10677 BROWSER_FORWARD = 0xA7, 10678 BROWSER_REFRESH = 0xA8, 10679 BROWSER_STOP = 0xA9, 10680 BROWSER_SEARCH = 0xAA, 10681 BROWSER_FAVORITES = 0xAB, 10682 BROWSER_HOME = 0xAC, 10683 VOLUME_MUTE = 0xAD, 10684 VOLUME_DOWN = 0xAE, 10685 VOLUME_UP = 0xAF, 10686 MEDIA_NEXT_TRACK = 0xB0, 10687 MEDIA_PREV_TRACK = 0xB1, 10688 MEDIA_STOP = 0xB2, 10689 MEDIA_PLAY_PAUSE = 0xB3, 10690 LAUNCH_MAIL = 0xB4, 10691 LAUNCH_MEDIA_SELECT = 0xB5, 10692 LAUNCH_APP1 = 0xB6, 10693 LAUNCH_APP2 = 0xB7, 10694 //} 10695 OEM_1 = 0xBA, 10696 //static if (_WIN32_WINNT > = 0x500) { 10697 OEM_PLUS = 0xBB, 10698 OEM_COMMA = 0xBC, 10699 OEM_MINUS = 0xBD, 10700 OEM_PERIOD = 0xBE, 10701 //} 10702 OEM_2 = 0xBF, 10703 OEM_3 = 0xC0, 10704 OEM_4 = 0xDB, 10705 OEM_5 = 0xDC, 10706 OEM_6 = 0xDD, 10707 OEM_7 = 0xDE, 10708 OEM_8 = 0xDF, 10709 //static if (_WIN32_WINNT > = 0x500) { 10710 OEM_102 = 0xE2, 10711 //} 10712 PROCESSKEY = 0xE5, 10713 //static if (_WIN32_WINNT > = 0x500) { 10714 PACKET = 0xE7, 10715 //} 10716 ATTN = 0xF6, 10717 CRSEL = 0xF7, 10718 EXSEL = 0xF8, 10719 EREOF = 0xF9, 10720 PLAY = 0xFA, 10721 ZOOM = 0xFB, 10722 NONAME = 0xFC, 10723 PA1 = 0xFD, 10724 OEM_CLEAR = 0xFE, 10725 } 10726 10727 } else version(OSXCocoa) { 10728 // FIXME 10729 enum Key { 10730 Escape = 0x1b, 10731 F1 = 0x70, 10732 F2 = 0x71, 10733 F3 = 0x72, 10734 F4 = 0x73, 10735 F5 = 0x74, 10736 F6 = 0x75, 10737 F7 = 0x76, 10738 F8 = 0x77, 10739 F9 = 0x78, 10740 F10 = 0x79, 10741 F11 = 0x7a, 10742 F12 = 0x7b, 10743 PrintScreen = 0x2c, 10744 ScrollLock = -2, // FIXME 10745 Pause = -3, // FIXME 10746 Grave = 0xc0, 10747 // number keys across the top of the keyboard 10748 N1 = 0x31, 10749 N2 = 0x32, 10750 N3 = 0x33, 10751 N4 = 0x34, 10752 N5 = 0x35, 10753 N6 = 0x36, 10754 N7 = 0x37, 10755 N8 = 0x38, 10756 N9 = 0x39, 10757 N0 = 0x30, 10758 Dash = 0xbd, 10759 Equals = 0xbb, 10760 Backslash = 0xdc, 10761 Backspace = 0x08, 10762 Insert = 0x2d, 10763 Home = 0x24, 10764 PageUp = 0x21, 10765 Delete = 0x2e, 10766 End = 0x23, 10767 PageDown = 0x22, 10768 Up = 0x26, 10769 Down = 0x28, 10770 Left = 0x25, 10771 Right = 0x27, 10772 10773 Tab = 0x09, 10774 Q = 0x51, 10775 W = 0x57, 10776 E = 0x45, 10777 R = 0x52, 10778 T = 0x54, 10779 Y = 0x59, 10780 U = 0x55, 10781 I = 0x49, 10782 O = 0x4f, 10783 P = 0x50, 10784 LeftBracket = 0xdb, 10785 RightBracket = 0xdd, 10786 CapsLock = 0x14, 10787 A = 0x41, 10788 S = 0x53, 10789 D = 0x44, 10790 F = 0x46, 10791 G = 0x47, 10792 H = 0x48, 10793 J = 0x4a, 10794 K = 0x4b, 10795 L = 0x4c, 10796 Semicolon = 0xba, 10797 Apostrophe = 0xde, 10798 Enter = 0x0d, 10799 Shift = 0x10, 10800 Z = 0x5a, 10801 X = 0x58, 10802 C = 0x43, 10803 V = 0x56, 10804 B = 0x42, 10805 N = 0x4e, 10806 M = 0x4d, 10807 Comma = 0xbc, 10808 Period = 0xbe, 10809 Slash = 0xbf, 10810 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 10811 Ctrl = 0x11, 10812 Windows = 0x5b, 10813 Alt = -5, // FIXME 10814 Space = 0x20, 10815 Alt_r = 0xffea, // ditto of shift_r 10816 Windows_r = -6, // FIXME 10817 Menu = 0x5d, 10818 Ctrl_r = -7, // FIXME 10819 10820 NumLock = 0x90, 10821 Divide = 0x6f, 10822 Multiply = 0x6a, 10823 Minus = 0x6d, 10824 Plus = 0x6b, 10825 PadEnter = -8, // FIXME 10826 // FIXME for the rest of these: 10827 Pad1 = 0xff9c, 10828 Pad2 = 0xff99, 10829 Pad3 = 0xff9b, 10830 Pad4 = 0xff96, 10831 Pad5 = 0xff9d, 10832 Pad6 = 0xff98, 10833 Pad7 = 0xff95, 10834 Pad8 = 0xff97, 10835 Pad9 = 0xff9a, 10836 Pad0 = 0xff9e, 10837 PadDot = 0xff9f, 10838 } 10839 10840 } 10841 10842 /* Additional utilities */ 10843 10844 10845 Color fromHsl(real h, real s, real l) { 10846 return arsd.color.fromHsl([h,s,l]); 10847 } 10848 10849 10850 10851 /* ********** What follows is the system-specific implementations *********/ 10852 version(Windows) { 10853 10854 10855 // helpers for making HICONs from MemoryImages 10856 class WindowsIcon { 10857 struct Win32Icon(int colorCount) { 10858 align(1): 10859 uint biSize; 10860 int biWidth; 10861 int biHeight; 10862 ushort biPlanes; 10863 ushort biBitCount; 10864 uint biCompression; 10865 uint biSizeImage; 10866 int biXPelsPerMeter; 10867 int biYPelsPerMeter; 10868 uint biClrUsed; 10869 uint biClrImportant; 10870 RGBQUAD[colorCount] biColors; 10871 /* Pixels: 10872 Uint8 pixels[] 10873 */ 10874 /* Mask: 10875 Uint8 mask[] 10876 */ 10877 10878 ubyte[4096] data; 10879 10880 void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 10881 width = mi.width; 10882 height = mi.height; 10883 10884 auto indexedImage = cast(IndexedImage) mi; 10885 if(indexedImage is null) 10886 indexedImage = quantize(mi.getAsTrueColorImage()); 10887 10888 assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy 10889 assert(height %4 == 0); 10890 10891 int icon_plen = height*((width+3)&~3); 10892 int icon_mlen = height*((((width+7)/8)+3)&~3); 10893 icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 10894 10895 biSize = 40; 10896 biWidth = width; 10897 biHeight = height*2; 10898 biPlanes = 1; 10899 biBitCount = 8; 10900 biSizeImage = icon_plen+icon_mlen; 10901 10902 int offset = 0; 10903 int andOff = icon_plen * 8; // the and offset is in bits 10904 for(int y = height - 1; y >= 0; y--) { 10905 int off2 = y * width; 10906 foreach(x; 0 .. width) { 10907 const b = indexedImage.data[off2 + x]; 10908 data[offset] = b; 10909 offset++; 10910 10911 const andBit = andOff % 8; 10912 const andIdx = andOff / 8; 10913 assert(b < indexedImage.palette.length); 10914 // this is anded to the destination, since and 0 means erase, 10915 // we want that to be opaque, and 1 for transparent 10916 auto transparent = (indexedImage.palette[b].a <= 127); 10917 data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0); 10918 10919 andOff++; 10920 } 10921 10922 andOff += andOff % 32; 10923 } 10924 10925 foreach(idx, entry; indexedImage.palette) { 10926 if(entry.a > 127) { 10927 biColors[idx].rgbBlue = entry.b; 10928 biColors[idx].rgbGreen = entry.g; 10929 biColors[idx].rgbRed = entry.r; 10930 } else { 10931 biColors[idx].rgbBlue = 255; 10932 biColors[idx].rgbGreen = 255; 10933 biColors[idx].rgbRed = 255; 10934 } 10935 } 10936 10937 /* 10938 data[0..icon_plen] = getFlippedUnfilteredDatastream(png); 10939 data[icon_plen..icon_plen+icon_mlen] = getANDMask(png); 10940 //icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0); 10941 auto pngMap = fetchPaletteWin32(png); 10942 biColors[0..pngMap.length] = pngMap[]; 10943 */ 10944 } 10945 } 10946 10947 10948 Win32Icon!(256) icon_win32; 10949 10950 10951 this(MemoryImage mi) { 10952 int icon_len, width, height; 10953 10954 icon_win32.fromMemoryImage(mi, icon_len, width, height); 10955 10956 /* 10957 PNG* png = readPnpngData); 10958 PNGHeader pngh = getHeader(png); 10959 void* icon_win32; 10960 if(pngh.depth == 4) { 10961 auto i = new Win32Icon!(16); 10962 i.fromPNG(png, pngh, icon_len, width, height); 10963 icon_win32 = i; 10964 } 10965 else if(pngh.depth == 8) { 10966 auto i = new Win32Icon!(256); 10967 i.fromPNG(png, pngh, icon_len, width, height); 10968 icon_win32 = i; 10969 } else assert(0); 10970 */ 10971 10972 hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0); 10973 10974 if(hIcon is null) throw new Exception("CreateIconFromResourceEx"); 10975 } 10976 10977 ~this() { 10978 if(!thisIsGuiThread) return; // FIXME: leaks if multithreaded gc 10979 DestroyIcon(hIcon); 10980 } 10981 10982 HICON hIcon; 10983 } 10984 10985 10986 10987 10988 10989 10990 alias int delegate(HWND, UINT, WPARAM, LPARAM, out int) NativeEventHandler; 10991 alias HWND NativeWindowHandle; 10992 10993 extern(Windows) 10994 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 10995 try { 10996 if(SimpleWindow.handleNativeGlobalEvent !is null) { 10997 // it returns zero if the message is handled, so we won't do anything more there 10998 // do I like that though? 10999 int mustReturn; 11000 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam, mustReturn); 11001 if(mustReturn) 11002 return ret; 11003 } 11004 11005 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 11006 if(window.getNativeEventHandler !is null) { 11007 int mustReturn; 11008 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam, mustReturn); 11009 if(mustReturn) 11010 return ret; 11011 } 11012 if(auto w = cast(SimpleWindow) (*window)) 11013 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 11014 else 11015 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11016 } else { 11017 return DefWindowProc(hWnd, iMessage, wParam, lParam); 11018 } 11019 } catch (Exception e) { 11020 try { 11021 sdpy_abort(e); 11022 return 0; 11023 } catch(Exception e) { assert(0); } 11024 } 11025 } 11026 11027 void sdpy_abort(Throwable e) nothrow { 11028 try 11029 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 11030 catch(Exception e) 11031 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 11032 ExitProcess(1); 11033 } 11034 11035 mixin template NativeScreenPainterImplementation() { 11036 HDC hdc; 11037 HWND hwnd; 11038 //HDC windowHdc; 11039 HBITMAP oldBmp; 11040 11041 void create(NativeWindowHandle window) { 11042 hwnd = window; 11043 11044 if(auto sw = cast(SimpleWindow) this.window) { 11045 // drawing on a window, double buffer 11046 auto windowHdc = GetDC(hwnd); 11047 11048 auto buffer = sw.impl.buffer; 11049 if(buffer is null) { 11050 hdc = windowHdc; 11051 windowDc = true; 11052 } else { 11053 hdc = CreateCompatibleDC(windowHdc); 11054 11055 ReleaseDC(hwnd, windowHdc); 11056 11057 oldBmp = SelectObject(hdc, buffer); 11058 } 11059 } else { 11060 // drawing on something else, draw directly 11061 hdc = CreateCompatibleDC(null); 11062 SelectObject(hdc, window); 11063 } 11064 11065 // X doesn't draw a text background, so neither should we 11066 SetBkMode(hdc, TRANSPARENT); 11067 11068 ensureDefaultFontLoaded(); 11069 11070 if(defaultGuiFont) { 11071 SelectObject(hdc, defaultGuiFont); 11072 // DeleteObject(defaultGuiFont); 11073 } 11074 } 11075 11076 static HFONT defaultGuiFont; 11077 static void ensureDefaultFontLoaded() { 11078 static bool triedDefaultGuiFont = false; 11079 if(!triedDefaultGuiFont) { 11080 NONCLIENTMETRICS params; 11081 params.cbSize = params.sizeof; 11082 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 11083 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 11084 } 11085 triedDefaultGuiFont = true; 11086 } 11087 } 11088 11089 void setFont(OperatingSystemFont font) { 11090 if(font && font.font) { 11091 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 11092 // error... how to handle tho? 11093 } 11094 } 11095 else if(defaultGuiFont) 11096 SelectObject(hdc, defaultGuiFont); 11097 } 11098 11099 arsd.color.Rectangle _clipRectangle; 11100 11101 void setClipRectangle(int x, int y, int width, int height) { 11102 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11103 11104 if(width == 0 || height == 0) { 11105 SelectClipRgn(hdc, null); 11106 } else { 11107 auto region = CreateRectRgn(x, y, x + width, y + height); 11108 SelectClipRgn(hdc, region); 11109 DeleteObject(region); 11110 } 11111 } 11112 11113 11114 // just because we can on Windows... 11115 //void create(Image image); 11116 11117 void invalidateRect(Rectangle invalidRect) { 11118 RECT rect; 11119 rect.left = invalidRect.left; 11120 rect.right = invalidRect.right; 11121 rect.top = invalidRect.top; 11122 rect.bottom = invalidRect.bottom; 11123 InvalidateRect(hwnd, &rect, false); 11124 } 11125 bool manualInvalidations; 11126 11127 void dispose() { 11128 // FIXME: this.window.width/height is probably wrong 11129 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 11130 // ReleaseDC(hwnd, windowHdc); 11131 11132 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 11133 if(cast(SimpleWindow) this.window) { 11134 if(!manualInvalidations) 11135 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 11136 } 11137 11138 if(originalPen !is null) 11139 SelectObject(hdc, originalPen); 11140 if(currentPen !is null) 11141 DeleteObject(currentPen); 11142 if(originalBrush !is null) 11143 SelectObject(hdc, originalBrush); 11144 if(currentBrush !is null) 11145 DeleteObject(currentBrush); 11146 11147 SelectObject(hdc, oldBmp); 11148 11149 if(windowDc) 11150 ReleaseDC(hwnd, hdc); 11151 else 11152 DeleteDC(hdc); 11153 11154 if(window.paintingFinishedDg !is null) 11155 window.paintingFinishedDg()(); 11156 } 11157 11158 bool windowDc; 11159 HPEN originalPen; 11160 HPEN currentPen; 11161 11162 Pen _activePen; 11163 11164 Color _outlineColor; 11165 11166 @property void pen(Pen p) { 11167 _activePen = p; 11168 _outlineColor = p.color; 11169 11170 HPEN pen; 11171 if(p.color.a == 0) { 11172 pen = GetStockObject(NULL_PEN); 11173 } else { 11174 int style = PS_SOLID; 11175 final switch(p.style) { 11176 case Pen.Style.Solid: 11177 style = PS_SOLID; 11178 break; 11179 case Pen.Style.Dashed: 11180 style = PS_DASH; 11181 break; 11182 case Pen.Style.Dotted: 11183 style = PS_DOT; 11184 break; 11185 } 11186 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 11187 } 11188 auto orig = SelectObject(hdc, pen); 11189 if(originalPen is null) 11190 originalPen = orig; 11191 11192 if(currentPen !is null) 11193 DeleteObject(currentPen); 11194 11195 currentPen = pen; 11196 11197 // the outline is like a foreground since it's done that way on X 11198 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 11199 11200 } 11201 11202 @property void rasterOp(RasterOp op) { 11203 int mode; 11204 final switch(op) { 11205 case RasterOp.normal: 11206 mode = R2_COPYPEN; 11207 break; 11208 case RasterOp.xor: 11209 mode = R2_XORPEN; 11210 break; 11211 } 11212 SetROP2(hdc, mode); 11213 } 11214 11215 HBRUSH originalBrush; 11216 HBRUSH currentBrush; 11217 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 11218 @property void fillColor(Color c) { 11219 if(c == _fillColor) 11220 return; 11221 _fillColor = c; 11222 HBRUSH brush; 11223 if(c.a == 0) { 11224 brush = GetStockObject(HOLLOW_BRUSH); 11225 } else { 11226 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 11227 } 11228 auto orig = SelectObject(hdc, brush); 11229 if(originalBrush is null) 11230 originalBrush = orig; 11231 11232 if(currentBrush !is null) 11233 DeleteObject(currentBrush); 11234 11235 currentBrush = brush; 11236 11237 // background color is NOT set because X doesn't draw text backgrounds 11238 // SetBkColor(hdc, RGB(255, 255, 255)); 11239 } 11240 11241 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11242 BITMAP bm; 11243 11244 HDC hdcMem = CreateCompatibleDC(hdc); 11245 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 11246 11247 GetObject(i.handle, bm.sizeof, &bm); 11248 11249 // or should I AlphaBlend!??!?! 11250 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 11251 11252 SelectObject(hdcMem, hbmOld); 11253 DeleteDC(hdcMem); 11254 } 11255 11256 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11257 BITMAP bm; 11258 11259 HDC hdcMem = CreateCompatibleDC(hdc); 11260 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 11261 11262 GetObject(s.handle, bm.sizeof, &bm); 11263 11264 version(CRuntime_DigitalMars) goto noalpha; 11265 11266 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 11267 if(s.enableAlpha) { 11268 auto dw = w ? w : bm.bmWidth; 11269 auto dh = h ? h : bm.bmHeight; 11270 BLENDFUNCTION bf; 11271 bf.BlendOp = AC_SRC_OVER; 11272 bf.SourceConstantAlpha = 255; 11273 bf.AlphaFormat = AC_SRC_ALPHA; 11274 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 11275 } else { 11276 noalpha: 11277 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 11278 } 11279 11280 SelectObject(hdcMem, hbmOld); 11281 DeleteDC(hdcMem); 11282 } 11283 11284 Size textSize(scope const(char)[] text) { 11285 bool dummyX; 11286 if(text.length == 0) { 11287 text = " "; 11288 dummyX = true; 11289 } 11290 RECT rect; 11291 WCharzBuffer buffer = WCharzBuffer(text); 11292 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT | DT_NOPREFIX); 11293 return Size(dummyX ? 0 : rect.right, rect.bottom); 11294 } 11295 11296 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 11297 if(text.length && text[$-1] == '\n') 11298 text = text[0 .. $-1]; // tailing newlines are weird on windows... 11299 if(text.length && text[$-1] == '\r') 11300 text = text[0 .. $-1]; 11301 11302 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 11303 if(x2 == 0 && y2 == 0) { 11304 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 11305 } else { 11306 RECT rect; 11307 rect.left = x; 11308 rect.top = y; 11309 rect.right = x2; 11310 rect.bottom = y2; 11311 11312 uint mode = DT_LEFT; 11313 if(alignment & TextAlignment.Right) 11314 mode = DT_RIGHT; 11315 else if(alignment & TextAlignment.Center) 11316 mode = DT_CENTER; 11317 11318 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 11319 if(alignment & TextAlignment.VerticalCenter) 11320 mode |= DT_VCENTER | DT_SINGLELINE; 11321 11322 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode | DT_NOPREFIX); 11323 } 11324 11325 /* 11326 uint mode; 11327 11328 if(alignment & TextAlignment.Center) 11329 mode = TA_CENTER; 11330 11331 SetTextAlign(hdc, mode); 11332 */ 11333 } 11334 11335 int fontHeight() { 11336 TEXTMETRIC metric; 11337 if(GetTextMetricsW(hdc, &metric)) { 11338 return metric.tmHeight; 11339 } 11340 11341 return 16; // idk just guessing here, maybe we should throw 11342 } 11343 11344 void drawPixel(int x, int y) { 11345 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 11346 } 11347 11348 // The basic shapes, outlined 11349 11350 void drawLine(int x1, int y1, int x2, int y2) { 11351 MoveToEx(hdc, x1, y1, null); 11352 LineTo(hdc, x2, y2); 11353 } 11354 11355 void drawRectangle(int x, int y, int width, int height) { 11356 // FIXME: with a wider pen this might not draw quite right. im not sure. 11357 gdi.Rectangle(hdc, x, y, x + width, y + height); 11358 } 11359 11360 /// Arguments are the points of the bounding rectangle 11361 void drawEllipse(int x1, int y1, int x2, int y2) { 11362 Ellipse(hdc, x1, y1, x2, y2); 11363 } 11364 11365 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11366 if((start % (360*64)) == (finish % (360*64))) 11367 drawEllipse(x1, y1, x1 + width, y1 + height); 11368 else { 11369 import core.stdc.math; 11370 float startAngle = cast(float) start / 64.0 / 180.0 * 3.14159265358979323; 11371 float endAngle = cast(float) finish / 64.0 / 180.0 * 3.14159265358979323; 11372 11373 auto c1 = cast(int) roundf(cos(startAngle) * width / 2 + x1 + width / 2); 11374 auto c2 = cast(int) roundf(-sin(startAngle) * height / 2 + y1 + height / 2); 11375 auto c3 = cast(int) roundf(cos(endAngle) * width / 2 + x1 + width / 2); 11376 auto c4 = cast(int) roundf(-sin(endAngle) * height / 2 + y1 + height / 2); 11377 11378 if(_activePen.color.a) 11379 Arc(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11380 if(_fillColor.a) 11381 Pie(hdc, x1, y1, x1 + width + 1, y1 + height + 1, c1, c2, c3, c4); 11382 } 11383 } 11384 11385 void drawPolygon(Point[] vertexes) { 11386 POINT[] points; 11387 points.length = vertexes.length; 11388 11389 foreach(i, p; vertexes) { 11390 points[i].x = p.x; 11391 points[i].y = p.y; 11392 } 11393 11394 Polygon(hdc, points.ptr, cast(int) points.length); 11395 } 11396 } 11397 11398 11399 // Mix this into the SimpleWindow class 11400 mixin template NativeSimpleWindowImplementation() { 11401 int curHidden = 0; // counter 11402 __gshared static bool[string] knownWinClasses; 11403 static bool altPressed = false; 11404 11405 HANDLE oldCursor; 11406 11407 void hideCursor () { 11408 if(curHidden == 0) 11409 oldCursor = SetCursor(null); 11410 ++curHidden; 11411 } 11412 11413 void showCursor () { 11414 --curHidden; 11415 if(curHidden == 0) { 11416 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 11417 } 11418 } 11419 11420 11421 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 11422 11423 void setMinSize (int minwidth, int minheight) { 11424 minWidth = minwidth; 11425 minHeight = minheight; 11426 } 11427 void setMaxSize (int maxwidth, int maxheight) { 11428 maxWidth = maxwidth; 11429 maxHeight = maxheight; 11430 } 11431 11432 // FIXME i'm not sure that Windows has this functionality 11433 // though it is nonessential anyway. 11434 void setResizeGranularity (int granx, int grany) {} 11435 11436 ScreenPainter getPainter(bool manualInvalidations) { 11437 return ScreenPainter(this, hwnd, manualInvalidations); 11438 } 11439 11440 HBITMAP buffer; 11441 11442 void setTitle(string title) { 11443 WCharzBuffer bfr = WCharzBuffer(title); 11444 SetWindowTextW(hwnd, bfr.ptr); 11445 } 11446 11447 string getTitle() { 11448 auto len = GetWindowTextLengthW(hwnd); 11449 if (!len) 11450 return null; 11451 wchar[256] tmpBuffer; 11452 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 11453 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 11454 auto str = buffer[0 .. len2]; 11455 return makeUtf8StringFromWindowsString(str); 11456 } 11457 11458 void move(int x, int y) { 11459 RECT rect; 11460 GetWindowRect(hwnd, &rect); 11461 // move it while maintaining the same size... 11462 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 11463 } 11464 11465 void resize(int w, int h) { 11466 RECT rect; 11467 GetWindowRect(hwnd, &rect); 11468 11469 RECT client; 11470 GetClientRect(hwnd, &client); 11471 11472 rect.right = rect.right - client.right + w; 11473 rect.bottom = rect.bottom - client.bottom + h; 11474 11475 // same position, new size for the client rectangle 11476 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 11477 11478 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 11479 } 11480 11481 void moveResize (int x, int y, int w, int h) { 11482 // what's given is the client rectangle, we need to adjust 11483 11484 RECT rect; 11485 rect.left = x; 11486 rect.top = y; 11487 rect.right = w + x; 11488 rect.bottom = h + y; 11489 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 11490 throw new Exception("AdjustWindowRect"); 11491 11492 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 11493 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 11494 if (windowResized !is null) windowResized(w, h); 11495 } 11496 11497 version(without_opengl) {} else { 11498 HGLRC ghRC; 11499 HDC ghDC; 11500 } 11501 11502 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 11503 string cnamec; 11504 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 11505 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 11506 cnamec = "DSimpleWindow"; 11507 } else { 11508 cnamec = sdpyWindowClass; 11509 } 11510 11511 WCharzBuffer cn = WCharzBuffer(cnamec); 11512 11513 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 11514 11515 if(cnamec !in knownWinClasses) { 11516 WNDCLASSEX wc; 11517 11518 // FIXME: I might be able to use cbWndExtra to hold the pointer back 11519 // to the object. Maybe. 11520 wc.cbSize = wc.sizeof; 11521 wc.cbClsExtra = 0; 11522 wc.cbWndExtra = 0; 11523 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 11524 wc.hCursor = LoadCursorW(null, IDC_ARROW); 11525 wc.hIcon = LoadIcon(hInstance, null); 11526 wc.hInstance = hInstance; 11527 wc.lpfnWndProc = &WndProc; 11528 wc.lpszClassName = cn.ptr; 11529 wc.hIconSm = null; 11530 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 11531 if(!RegisterClassExW(&wc)) 11532 throw new WindowsApiException("RegisterClassExW"); 11533 knownWinClasses[cnamec] = true; 11534 } 11535 11536 int style; 11537 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 11538 11539 // FIXME: windowType and customizationFlags 11540 final switch(windowType) { 11541 case WindowTypes.normal: 11542 style = WS_OVERLAPPEDWINDOW; 11543 break; 11544 case WindowTypes.undecorated: 11545 style = WS_POPUP | WS_SYSMENU; 11546 break; 11547 case WindowTypes.eventOnly: 11548 _hidden = true; 11549 break; 11550 case WindowTypes.dropdownMenu: 11551 case WindowTypes.popupMenu: 11552 case WindowTypes.notification: 11553 style = WS_POPUP; 11554 flags |= WS_EX_NOACTIVATE; 11555 break; 11556 case WindowTypes.nestedChild: 11557 style = WS_CHILD; 11558 break; 11559 } 11560 11561 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11562 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 11563 11564 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 11565 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 11566 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 11567 11568 if ((customizationFlags & WindowFlags.extraComposite) != 0) 11569 setOpacity(255); 11570 11571 SimpleWindow.nativeMapping[hwnd] = this; 11572 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 11573 11574 if(windowType == WindowTypes.eventOnly) 11575 return; 11576 11577 HDC hdc = GetDC(hwnd); 11578 11579 11580 version(without_opengl) {} 11581 else { 11582 if(opengl == OpenGlOptions.yes) { 11583 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 11584 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 11585 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 11586 ghDC = hdc; 11587 PIXELFORMATDESCRIPTOR pfd; 11588 11589 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 11590 pfd.nVersion = 1; 11591 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 11592 pfd.dwLayerMask = PFD_MAIN_PLANE; 11593 pfd.iPixelType = PFD_TYPE_RGBA; 11594 pfd.cColorBits = 24; 11595 pfd.cDepthBits = 24; 11596 pfd.cAccumBits = 0; 11597 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 11598 11599 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 11600 11601 if (pixelformat == 0) 11602 throw new WindowsApiException("ChoosePixelFormat"); 11603 11604 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 11605 throw new WindowsApiException("SetPixelFormat"); 11606 11607 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 11608 // windoze is idiotic: we have to have OpenGL context to get function addresses 11609 // so we will create fake context to get that stupid address 11610 auto tmpcc = wglCreateContext(ghDC); 11611 if (tmpcc !is null) { 11612 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 11613 wglMakeCurrent(ghDC, tmpcc); 11614 wglInitOtherFunctions(); 11615 } 11616 } 11617 11618 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 11619 int[9] contextAttribs = [ 11620 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 11621 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 11622 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 11623 // for modern context, set "forward compatibility" flag too 11624 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 11625 0/*None*/, 11626 ]; 11627 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 11628 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 11629 // activate fallback mode 11630 // 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; 11631 ghRC = wglCreateContext(ghDC); 11632 } 11633 if (ghRC is null) 11634 throw new WindowsApiException("wglCreateContextAttribsARB"); 11635 } else { 11636 // try to do at least something 11637 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 11638 sdpyOpenGLContextVersion = 0; 11639 ghRC = wglCreateContext(ghDC); 11640 } 11641 if (ghRC is null) 11642 throw new WindowsApiException("wglCreateContext"); 11643 } 11644 } 11645 } 11646 11647 if(opengl == OpenGlOptions.no) { 11648 buffer = CreateCompatibleBitmap(hdc, width, height); 11649 11650 auto hdcBmp = CreateCompatibleDC(hdc); 11651 // make sure it's filled with a blank slate 11652 auto oldBmp = SelectObject(hdcBmp, buffer); 11653 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 11654 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 11655 gdi.Rectangle(hdcBmp, 0, 0, width, height); 11656 SelectObject(hdcBmp, oldBmp); 11657 SelectObject(hdcBmp, oldBrush); 11658 SelectObject(hdcBmp, oldPen); 11659 DeleteDC(hdcBmp); 11660 11661 bmpWidth = width; 11662 bmpHeight = height; 11663 11664 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 11665 } 11666 11667 // We want the window's client area to match the image size 11668 RECT rcClient, rcWindow; 11669 POINT ptDiff; 11670 GetClientRect(hwnd, &rcClient); 11671 GetWindowRect(hwnd, &rcWindow); 11672 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 11673 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 11674 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 11675 11676 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 11677 ShowWindow(hwnd, SW_SHOWNORMAL); 11678 } else { 11679 _hidden = true; 11680 } 11681 this._visibleForTheFirstTimeCalled = false; // hack! 11682 } 11683 11684 11685 void dispose() { 11686 if(buffer) 11687 DeleteObject(buffer); 11688 } 11689 11690 void closeWindow() { 11691 DestroyWindow(hwnd); 11692 } 11693 11694 bool setOpacity(ubyte alpha) { 11695 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 11696 } 11697 11698 HANDLE currentCursor; 11699 11700 // returns zero if it recognized the event 11701 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 11702 MouseEvent mouse; 11703 11704 void mouseEvent(bool isScreen, ulong mods) { 11705 auto x = LOWORD(lParam); 11706 auto y = HIWORD(lParam); 11707 if(isScreen) { 11708 POINT p; 11709 p.x = x; 11710 p.y = y; 11711 ScreenToClient(hwnd, &p); 11712 x = cast(ushort) p.x; 11713 y = cast(ushort) p.y; 11714 } 11715 mouse.x = x + offsetX; 11716 mouse.y = y + offsetY; 11717 11718 wind.mdx(mouse); 11719 mouse.modifierState = cast(int) mods; 11720 mouse.window = wind; 11721 11722 if(wind.handleMouseEvent) 11723 wind.handleMouseEvent(mouse); 11724 } 11725 11726 switch(msg) { 11727 case WM_GETMINMAXINFO: 11728 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 11729 11730 if(wind.minWidth > 0) { 11731 RECT rect; 11732 rect.left = 100; 11733 rect.top = 100; 11734 rect.right = wind.minWidth + 100; 11735 rect.bottom = wind.minHeight + 100; 11736 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11737 throw new WindowsApiException("AdjustWindowRect"); 11738 11739 mmi.ptMinTrackSize.x = rect.right - rect.left; 11740 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 11741 } 11742 11743 if(wind.maxWidth < int.max) { 11744 RECT rect; 11745 rect.left = 100; 11746 rect.top = 100; 11747 rect.right = wind.maxWidth + 100; 11748 rect.bottom = wind.maxHeight + 100; 11749 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 11750 throw new WindowsApiException("AdjustWindowRect"); 11751 11752 mmi.ptMaxTrackSize.x = rect.right - rect.left; 11753 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 11754 } 11755 break; 11756 case WM_CHAR: 11757 wchar c = cast(wchar) wParam; 11758 if(wind.handleCharEvent) 11759 wind.handleCharEvent(cast(dchar) c); 11760 break; 11761 case WM_SETFOCUS: 11762 case WM_KILLFOCUS: 11763 wind._focused = (msg == WM_SETFOCUS); 11764 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 11765 if(wind.onFocusChange) 11766 wind.onFocusChange(msg == WM_SETFOCUS); 11767 break; 11768 11769 case WM_SYSKEYDOWN: 11770 goto case; 11771 case WM_SYSKEYUP: 11772 if(lParam & (1 << 29)) { 11773 goto case; 11774 } else { 11775 // no window has keyboard focus 11776 goto default; 11777 } 11778 case WM_KEYDOWN: 11779 case WM_KEYUP: 11780 KeyEvent ev; 11781 ev.key = cast(Key) wParam; 11782 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 11783 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 11784 11785 ev.hardwareCode = (lParam & 0xff0000) >> 16; 11786 11787 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 11788 ev.modifierState |= ModifierState.shift; 11789 //k8: this doesn't work; thanks for nothing, windows 11790 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 11791 ev.modifierState |= ModifierState.alt;*/ 11792 // this never seems to actually be set 11793 // if (lParam & 0x2000 /* KF_ALTDOWN */) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11794 11795 if (wParam == 0x12) { 11796 altPressed = (msg == WM_SYSKEYDOWN); 11797 } 11798 11799 if(msg == WM_KEYDOWN || msg == WM_KEYUP) { 11800 altPressed = false; 11801 } 11802 // sdpyPrintDebugString(altPressed ? "alt down" : " up "); 11803 11804 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 11805 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 11806 ev.modifierState |= ModifierState.ctrl; 11807 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 11808 ev.modifierState |= ModifierState.windows; 11809 if(GetKeyState(Key.NumLock)) 11810 ev.modifierState |= ModifierState.numLock; 11811 if(GetKeyState(Key.CapsLock)) 11812 ev.modifierState |= ModifierState.capsLock; 11813 11814 /+ 11815 // we always want to send the character too, so let's convert it 11816 ubyte[256] state; 11817 wchar[16] buffer; 11818 GetKeyboardState(state.ptr); 11819 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 11820 11821 foreach(dchar d; buffer) { 11822 ev.character = d; 11823 break; 11824 } 11825 +/ 11826 11827 ev.window = wind; 11828 if(wind.handleKeyEvent) 11829 wind.handleKeyEvent(ev); 11830 break; 11831 case 0x020a /*WM_MOUSEWHEEL*/: 11832 // send click 11833 mouse.type = cast(MouseEventType) 1; 11834 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 11835 mouseEvent(true, LOWORD(wParam)); 11836 11837 // also send release 11838 mouse.type = cast(MouseEventType) 2; 11839 mouse.button = ((GET_WHEEL_DELTA_WPARAM(wParam) > 0) ? MouseButton.wheelUp : MouseButton.wheelDown); 11840 mouseEvent(true, LOWORD(wParam)); 11841 break; 11842 case WM_MOUSEMOVE: 11843 mouse.type = cast(MouseEventType) 0; 11844 mouseEvent(false, wParam); 11845 break; 11846 case WM_LBUTTONDOWN: 11847 case WM_LBUTTONDBLCLK: 11848 mouse.type = cast(MouseEventType) 1; 11849 mouse.button = MouseButton.left; 11850 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 11851 mouseEvent(false, wParam); 11852 break; 11853 case WM_LBUTTONUP: 11854 mouse.type = cast(MouseEventType) 2; 11855 mouse.button = MouseButton.left; 11856 mouseEvent(false, wParam); 11857 break; 11858 case WM_RBUTTONDOWN: 11859 case WM_RBUTTONDBLCLK: 11860 mouse.type = cast(MouseEventType) 1; 11861 mouse.button = MouseButton.right; 11862 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 11863 mouseEvent(false, wParam); 11864 break; 11865 case WM_RBUTTONUP: 11866 mouse.type = cast(MouseEventType) 2; 11867 mouse.button = MouseButton.right; 11868 mouseEvent(false, wParam); 11869 break; 11870 case WM_MBUTTONDOWN: 11871 case WM_MBUTTONDBLCLK: 11872 mouse.type = cast(MouseEventType) 1; 11873 mouse.button = MouseButton.middle; 11874 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 11875 mouseEvent(false, wParam); 11876 break; 11877 case WM_MBUTTONUP: 11878 mouse.type = cast(MouseEventType) 2; 11879 mouse.button = MouseButton.middle; 11880 mouseEvent(false, wParam); 11881 break; 11882 case WM_XBUTTONDOWN: 11883 case WM_XBUTTONDBLCLK: 11884 mouse.type = cast(MouseEventType) 1; 11885 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 11886 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 11887 mouseEvent(false, wParam); 11888 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 11889 case WM_XBUTTONUP: 11890 mouse.type = cast(MouseEventType) 2; 11891 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 11892 mouseEvent(false, wParam); 11893 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 11894 11895 default: return 1; 11896 } 11897 return 0; 11898 } 11899 11900 HWND hwnd; 11901 private int oldWidth; 11902 private int oldHeight; 11903 private bool inSizeMove; 11904 11905 /++ 11906 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. 11907 11908 History: 11909 Added November 23, 2021 11910 11911 Not fully stable, may be moved out of the impl struct. 11912 11913 Default value changed to `true` on February 15, 2021 11914 +/ 11915 bool doLiveResizing = true; 11916 11917 package int bmpWidth; 11918 package int bmpHeight; 11919 11920 // the extern(Windows) wndproc should just forward to this 11921 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 11922 try { 11923 assert(hwnd is this.hwnd); 11924 11925 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 11926 switch(msg) { 11927 case WM_MENUCHAR: // menu active but key not associated with a thing. 11928 // you would ideally use this for like a search function but sdpy not that ideally designed. alas. 11929 // The main things we can do are select, execute, close, or ignore 11930 // the default is ignore, but it doesn't *just* ignore it - it also dings an audio alert to 11931 // the user. This can be a bit annoying for sdpy things so instead im overriding and setting it 11932 // to close, which can be really annoying when you hit the wrong button. but meh i think for sdpy 11933 // that's the lesser bad choice rn. Can always override by returning true in triggerEvents.... 11934 11935 // returns the value in the *high order word* of the return value 11936 // hence the << 16 11937 return 1 << 16; // MNC_CLOSE, close the menu without dinging at the user 11938 case WM_SETCURSOR: 11939 if(cast(HWND) wParam !is hwnd) 11940 return 0; // further processing elsewhere 11941 11942 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 11943 SetCursor(this.curHidden > 0 ? null : currentCursor); 11944 return 1; 11945 } else { 11946 return DefWindowProc(hwnd, msg, wParam, lParam); 11947 } 11948 //break; 11949 11950 case WM_CLOSE: 11951 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 11952 break; 11953 case WM_DESTROY: 11954 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 11955 SimpleWindow.nativeMapping.remove(hwnd); 11956 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 11957 11958 bool anyImportant = false; 11959 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 11960 if(w.beingOpenKeepsAppOpen) { 11961 anyImportant = true; 11962 break; 11963 } 11964 if(!anyImportant) { 11965 PostQuitMessage(0); 11966 } 11967 break; 11968 case 0x02E0 /*WM_DPICHANGED*/: 11969 this.actualDpi_ = LOWORD(wParam); // hiword is the y param but it is the same per docs 11970 11971 RECT* prcNewWindow = cast(RECT*)lParam; 11972 // docs say this is the recommended position and we should honor it 11973 SetWindowPos(hwnd, 11974 null, 11975 prcNewWindow.left, 11976 prcNewWindow.top, 11977 prcNewWindow.right - prcNewWindow.left, 11978 prcNewWindow.bottom - prcNewWindow.top, 11979 SWP_NOZORDER | SWP_NOACTIVATE); 11980 11981 // doing this because of https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/DPIAwarenessPerWindow/client/DpiAwarenessContext.cpp 11982 // im not sure it is completely correct 11983 // but without it the tabs and such do look weird as things change. 11984 if(SystemParametersInfoForDpi) { 11985 LOGFONT lfText; 11986 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, lfText.sizeof, &lfText, FALSE, this.actualDpi_); 11987 HFONT hFontNew = CreateFontIndirect(&lfText); 11988 if (hFontNew) 11989 { 11990 //DeleteObject(hFontOld); 11991 static extern(Windows) BOOL helper(HWND hWnd, LPARAM lParam) { 11992 SendMessage(hWnd, WM_SETFONT, cast(WPARAM)lParam, MAKELPARAM(TRUE, 0)); 11993 return TRUE; 11994 } 11995 EnumChildWindows(hwnd, &helper, cast(LPARAM) hFontNew); 11996 } 11997 } 11998 11999 if(this.onDpiChanged) 12000 this.onDpiChanged(); 12001 break; 12002 case WM_ENTERIDLE: 12003 // when a menu is up, it stops normal event processing (modal message loop) 12004 // but this at least gives us a chance to SOMETIMES catch up 12005 // FIXME: I can use SetTimer while idle to keep working i think... but idk when i'd destroy it. 12006 SimpleWindow.processAllCustomEvents; 12007 SimpleWindow.processAllCustomEvents; 12008 SleepEx(0, true); 12009 break; 12010 case WM_SIZE: 12011 if(wParam == 1 /* SIZE_MINIMIZED */) 12012 break; 12013 _width = LOWORD(lParam); 12014 _height = HIWORD(lParam); 12015 12016 // I want to avoid tearing in the windows (my code is inefficient 12017 // so this is a hack around that) so while sizing, we don't trigger, 12018 // but we do want to trigger on events like mazimize. 12019 if(!inSizeMove || doLiveResizing) 12020 goto size_changed; 12021 break; 12022 /+ 12023 case WM_SIZING: 12024 import std.stdio; writeln("size"); 12025 break; 12026 +/ 12027 // I don't like the tearing I get when redrawing on WM_SIZE 12028 // (I know there's other ways to fix that but I don't like that behavior anyway) 12029 // so instead it is going to redraw only at the end of a size. 12030 case 0x0231: /* WM_ENTERSIZEMOVE */ 12031 inSizeMove = true; 12032 break; 12033 case 0x0232: /* WM_EXITSIZEMOVE */ 12034 inSizeMove = false; 12035 12036 size_changed: 12037 12038 // nothing relevant changed, don't bother redrawing 12039 if(oldWidth == width && oldHeight == height) { 12040 break; 12041 } 12042 12043 // note: OpenGL windows don't use a backing bmp, so no need to change them 12044 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 12045 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 12046 // gotta get the double buffer bmp to match the window 12047 // FIXME: could this be more efficient? it never relinquishes a large bitmap 12048 if(width > bmpWidth || height > bmpHeight) { 12049 auto hdc = GetDC(hwnd); 12050 auto oldBuffer = buffer; 12051 buffer = CreateCompatibleBitmap(hdc, width, height); 12052 12053 auto hdcBmp = CreateCompatibleDC(hdc); 12054 auto oldBmp = SelectObject(hdcBmp, buffer); 12055 12056 auto hdcOldBmp = CreateCompatibleDC(hdc); 12057 auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer); 12058 12059 /+ 12060 RECT r; 12061 r.left = 0; 12062 r.top = 0; 12063 r.right = width; 12064 r.bottom = height; 12065 auto c = Color.green; 12066 auto brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 12067 FillRect(hdcBmp, &r, brush); 12068 DeleteObject(brush); 12069 +/ 12070 12071 BitBlt(hdcBmp, 0, 0, bmpWidth, bmpHeight, hdcOldBmp, 0, 0, SRCCOPY); 12072 12073 bmpWidth = width; 12074 bmpHeight = height; 12075 12076 SelectObject(hdcOldBmp, oldOldBmp); 12077 DeleteDC(hdcOldBmp); 12078 12079 SelectObject(hdcBmp, oldBmp); 12080 DeleteDC(hdcBmp); 12081 12082 ReleaseDC(hwnd, hdc); 12083 12084 DeleteObject(oldBuffer); 12085 } 12086 } 12087 12088 version(without_opengl) {} else 12089 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 12090 glViewport(0, 0, width, height); 12091 } 12092 12093 if(windowResized !is null) 12094 windowResized(width, height); 12095 12096 if(inSizeMove) { 12097 SimpleWindow.processAllCustomEvents(); 12098 SimpleWindow.processAllCustomEvents(); 12099 } else { 12100 // when it is all done, make sure everything is freshly drawn or there might be 12101 // weird bugs left. 12102 RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); 12103 } 12104 12105 oldWidth = this.width; 12106 oldHeight = this.height; 12107 break; 12108 case WM_ERASEBKGND: 12109 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 12110 if (!this._visibleForTheFirstTimeCalled) { 12111 this._visibleForTheFirstTimeCalled = true; 12112 if (this.visibleForTheFirstTime !is null) { 12113 version(without_opengl) {} else { 12114 if(openglMode == OpenGlOptions.yes) { 12115 this.setAsCurrentOpenGlContextNT(); 12116 glViewport(0, 0, width, height); 12117 } 12118 } 12119 this.visibleForTheFirstTime(); 12120 } 12121 } 12122 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 12123 version(without_opengl) {} else { 12124 if (openglMode == OpenGlOptions.yes) return 1; 12125 } 12126 // call windows default handler, so it can paint standard controls 12127 goto default; 12128 case WM_CTLCOLORBTN: 12129 case WM_CTLCOLORSTATIC: 12130 SetBkMode(cast(HDC) wParam, TRANSPARENT); 12131 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 12132 GetSysColorBrush(COLOR_3DFACE); 12133 //break; 12134 case WM_SHOWWINDOW: 12135 this._visible = (wParam != 0); 12136 if (!this._visibleForTheFirstTimeCalled && this._visible) { 12137 this._visibleForTheFirstTimeCalled = true; 12138 if (this.visibleForTheFirstTime !is null) { 12139 version(without_opengl) {} else { 12140 if(openglMode == OpenGlOptions.yes) { 12141 this.setAsCurrentOpenGlContextNT(); 12142 glViewport(0, 0, width, height); 12143 } 12144 } 12145 this.visibleForTheFirstTime(); 12146 } 12147 } 12148 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 12149 break; 12150 case WM_PAINT: { 12151 if (!this._visibleForTheFirstTimeCalled) { 12152 this._visibleForTheFirstTimeCalled = true; 12153 if (this.visibleForTheFirstTime !is null) { 12154 version(without_opengl) {} else { 12155 if(openglMode == OpenGlOptions.yes) { 12156 this.setAsCurrentOpenGlContextNT(); 12157 glViewport(0, 0, width, height); 12158 } 12159 } 12160 this.visibleForTheFirstTime(); 12161 } 12162 } 12163 12164 BITMAP bm; 12165 PAINTSTRUCT ps; 12166 12167 HDC hdc = BeginPaint(hwnd, &ps); 12168 12169 if(openglMode == OpenGlOptions.no) { 12170 12171 HDC hdcMem = CreateCompatibleDC(hdc); 12172 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 12173 12174 GetObject(buffer, bm.sizeof, &bm); 12175 12176 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 12177 if(resizability == Resizability.automaticallyScaleIfPossible) 12178 StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 12179 else 12180 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 12181 //BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.top - ps.rcPaint.bottom, hdcMem, 0, 0, SRCCOPY); 12182 12183 SelectObject(hdcMem, hbmOld); 12184 DeleteDC(hdcMem); 12185 EndPaint(hwnd, &ps); 12186 } else { 12187 EndPaint(hwnd, &ps); 12188 version(without_opengl) {} else 12189 redrawOpenGlSceneNow(); 12190 } 12191 } break; 12192 default: 12193 return DefWindowProc(hwnd, msg, wParam, lParam); 12194 } 12195 return 0; 12196 12197 } 12198 catch(Throwable t) { 12199 sdpyPrintDebugString(t.toString); 12200 return 0; 12201 } 12202 } 12203 } 12204 12205 mixin template NativeImageImplementation() { 12206 HBITMAP handle; 12207 ubyte* rawData; 12208 12209 final: 12210 12211 Color getPixel(int x, int y) { 12212 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12213 // remember, bmps are upside down 12214 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12215 12216 Color c; 12217 if(enableAlpha) 12218 c.a = rawData[offset + 3]; 12219 else 12220 c.a = 255; 12221 c.b = rawData[offset + 0]; 12222 c.g = rawData[offset + 1]; 12223 c.r = rawData[offset + 2]; 12224 c.unPremultiply(); 12225 return c; 12226 } 12227 12228 void setPixel(int x, int y, Color c) { 12229 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12230 // remember, bmps are upside down 12231 auto offset = itemsPerLine * (height - y - 1) + x * 3; 12232 12233 if(enableAlpha) 12234 c.premultiply(); 12235 12236 rawData[offset + 0] = c.b; 12237 rawData[offset + 1] = c.g; 12238 rawData[offset + 2] = c.r; 12239 if(enableAlpha) 12240 rawData[offset + 3] = c.a; 12241 } 12242 12243 void convertToRgbaBytes(ubyte[] where) { 12244 assert(where.length == this.width * this.height * 4); 12245 12246 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 12247 int idx = 0; 12248 int offset = itemsPerLine * (height - 1); 12249 // remember, bmps are upside down 12250 for(int y = height - 1; y >= 0; y--) { 12251 auto offsetStart = offset; 12252 for(int x = 0; x < width; x++) { 12253 where[idx + 0] = rawData[offset + 2]; // r 12254 where[idx + 1] = rawData[offset + 1]; // g 12255 where[idx + 2] = rawData[offset + 0]; // b 12256 if(enableAlpha) { 12257 where[idx + 3] = rawData[offset + 3]; // a 12258 unPremultiplyRgba(where[idx .. idx + 4]); 12259 offset++; 12260 } else 12261 where[idx + 3] = 255; // a 12262 idx += 4; 12263 offset += 3; 12264 } 12265 12266 offset = offsetStart - itemsPerLine; 12267 } 12268 } 12269 12270 void setFromRgbaBytes(in ubyte[] what) { 12271 assert(what.length == this.width * this.height * 4); 12272 12273 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 12274 int idx = 0; 12275 int offset = itemsPerLine * (height - 1); 12276 // remember, bmps are upside down 12277 for(int y = height - 1; y >= 0; y--) { 12278 auto offsetStart = offset; 12279 for(int x = 0; x < width; x++) { 12280 if(enableAlpha) { 12281 auto a = what[idx + 3]; 12282 12283 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 12284 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 12285 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 12286 rawData[offset + 3] = a; // a 12287 //premultiplyBgra(rawData[offset .. offset + 4]); 12288 offset++; 12289 } else { 12290 rawData[offset + 2] = what[idx + 0]; // r 12291 rawData[offset + 1] = what[idx + 1]; // g 12292 rawData[offset + 0] = what[idx + 2]; // b 12293 } 12294 idx += 4; 12295 offset += 3; 12296 } 12297 12298 offset = offsetStart - itemsPerLine; 12299 } 12300 } 12301 12302 12303 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 12304 BITMAPINFO infoheader; 12305 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 12306 infoheader.bmiHeader.biWidth = width; 12307 infoheader.bmiHeader.biHeight = height; 12308 infoheader.bmiHeader.biPlanes = 1; 12309 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 12310 infoheader.bmiHeader.biCompression = BI_RGB; 12311 12312 handle = CreateDIBSection( 12313 null, 12314 &infoheader, 12315 DIB_RGB_COLORS, 12316 cast(void**) &rawData, 12317 null, 12318 0); 12319 if(handle is null) 12320 throw new WindowsApiException("create image failed"); 12321 12322 } 12323 12324 void dispose() { 12325 DeleteObject(handle); 12326 } 12327 } 12328 12329 enum KEY_ESCAPE = 27; 12330 } 12331 version(X11) { 12332 /// This is the default font used. You might change this before doing anything else with 12333 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 12334 /// for cross-platform compatibility. 12335 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12336 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 12337 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 12338 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 12339 12340 alias int delegate(XEvent) NativeEventHandler; 12341 alias Window NativeWindowHandle; 12342 12343 enum KEY_ESCAPE = 9; 12344 12345 mixin template NativeScreenPainterImplementation() { 12346 Display* display; 12347 Drawable d; 12348 Drawable destiny; 12349 12350 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 12351 GC gc; 12352 12353 __gshared bool fontAttempted; 12354 12355 __gshared XFontStruct* defaultfont; 12356 __gshared XFontSet defaultfontset; 12357 12358 XFontStruct* font; 12359 XFontSet fontset; 12360 12361 void create(NativeWindowHandle window) { 12362 this.display = XDisplayConnection.get(); 12363 12364 Drawable buffer = None; 12365 if(auto sw = cast(SimpleWindow) this.window) { 12366 buffer = sw.impl.buffer; 12367 this.destiny = cast(Drawable) window; 12368 } else { 12369 buffer = cast(Drawable) window; 12370 this.destiny = None; 12371 } 12372 12373 this.d = cast(Drawable) buffer; 12374 12375 auto dgc = DefaultGC(display, DefaultScreen(display)); 12376 12377 this.gc = XCreateGC(display, d, 0, null); 12378 12379 XCopyGC(display, dgc, 0xffffffff, this.gc); 12380 12381 ensureDefaultFontLoaded(); 12382 12383 font = defaultfont; 12384 fontset = defaultfontset; 12385 12386 if(font) { 12387 XSetFont(display, gc, font.fid); 12388 } 12389 } 12390 12391 static void ensureDefaultFontLoaded() { 12392 if(!fontAttempted) { 12393 auto display = XDisplayConnection.get; 12394 auto font = XLoadQueryFont(display, xfontstr.ptr); 12395 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 12396 if(font is null) { 12397 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 12398 font = XLoadQueryFont(display, xfontstr.ptr); 12399 } 12400 12401 char** lol; 12402 int lol2; 12403 char* lol3; 12404 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 12405 12406 fontAttempted = true; 12407 12408 defaultfont = font; 12409 defaultfontset = fontset; 12410 } 12411 } 12412 12413 arsd.color.Rectangle _clipRectangle; 12414 void setClipRectangle(int x, int y, int width, int height) { 12415 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 12416 if(width == 0 || height == 0) { 12417 XSetClipMask(display, gc, None); 12418 12419 if(xrenderPicturePainter) { 12420 12421 XRectangle[1] rects; 12422 rects[0] = XRectangle(short.min, short.min, short.max, short.max); 12423 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12424 } 12425 12426 version(with_xft) { 12427 if(xftFont is null || xftDraw is null) 12428 return; 12429 XftDrawSetClip(xftDraw, null); 12430 } 12431 } else { 12432 XRectangle[1] rects; 12433 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 12434 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 12435 12436 if(xrenderPicturePainter) 12437 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12438 12439 version(with_xft) { 12440 if(xftFont is null || xftDraw is null) 12441 return; 12442 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 12443 } 12444 } 12445 } 12446 12447 version(with_xft) { 12448 XftFont* xftFont; 12449 XftDraw* xftDraw; 12450 12451 XftColor xftColor; 12452 12453 void updateXftColor() { 12454 if(xftFont is null) 12455 return; 12456 12457 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 12458 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 12459 12460 XftColorAllocValue( 12461 display, 12462 DefaultVisual(display, DefaultScreen(display)), 12463 DefaultColormap(display, 0), 12464 &colorIn, 12465 &xftColor 12466 ); 12467 } 12468 } 12469 12470 void setFont(OperatingSystemFont font) { 12471 version(with_xft) { 12472 if(font && font.isXft && font.xftFont) 12473 this.xftFont = font.xftFont; 12474 else 12475 this.xftFont = null; 12476 12477 if(this.xftFont) { 12478 if(xftDraw is null) { 12479 xftDraw = XftDrawCreate( 12480 display, 12481 d, 12482 DefaultVisual(display, DefaultScreen(display)), 12483 DefaultColormap(display, 0) 12484 ); 12485 12486 updateXftColor(); 12487 } 12488 12489 return; 12490 } 12491 } 12492 12493 if(font && font.font) { 12494 this.font = font.font; 12495 this.fontset = font.fontset; 12496 XSetFont(display, gc, font.font.fid); 12497 } else { 12498 this.font = defaultfont; 12499 this.fontset = defaultfontset; 12500 } 12501 12502 } 12503 12504 private Picture xrenderPicturePainter; 12505 12506 bool manualInvalidations; 12507 void invalidateRect(Rectangle invalidRect) { 12508 // FIXME if manualInvalidations 12509 } 12510 12511 void dispose() { 12512 this.rasterOp = RasterOp.normal; 12513 12514 if(xrenderPicturePainter) { 12515 XRenderFreePicture(display, xrenderPicturePainter); 12516 xrenderPicturePainter = None; 12517 } 12518 12519 // FIXME: this.window.width/height is probably wrong 12520 12521 // src x,y then dest x, y 12522 if(destiny != None) { 12523 // FIXME: if manual invalidations we can actually only copy some of the area. 12524 // if(manualInvalidations) 12525 XSetClipMask(display, gc, None); 12526 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 12527 } 12528 12529 XFreeGC(display, gc); 12530 12531 version(with_xft) 12532 if(xftDraw) { 12533 XftDrawDestroy(xftDraw); 12534 xftDraw = null; 12535 } 12536 12537 /+ 12538 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 12539 if(font && font !is defaultfont) { 12540 XFreeFont(display, font); 12541 font = null; 12542 } 12543 if(fontset && fontset !is defaultfontset) { 12544 XFreeFontSet(display, fontset); 12545 fontset = null; 12546 } 12547 +/ 12548 XFlush(display); 12549 12550 if(window.paintingFinishedDg !is null) 12551 window.paintingFinishedDg()(); 12552 } 12553 12554 bool backgroundIsNotTransparent = true; 12555 bool foregroundIsNotTransparent = true; 12556 12557 bool _penInitialized = false; 12558 Pen _activePen; 12559 12560 Color _outlineColor; 12561 Color _fillColor; 12562 12563 @property void pen(Pen p) { 12564 if(_penInitialized && p == _activePen) { 12565 return; 12566 } 12567 _penInitialized = true; 12568 _activePen = p; 12569 _outlineColor = p.color; 12570 12571 int style; 12572 12573 byte dashLength; 12574 12575 final switch(p.style) { 12576 case Pen.Style.Solid: 12577 style = 0 /*LineSolid*/; 12578 break; 12579 case Pen.Style.Dashed: 12580 style = 1 /*LineOnOffDash*/; 12581 dashLength = 4; 12582 break; 12583 case Pen.Style.Dotted: 12584 style = 1 /*LineOnOffDash*/; 12585 dashLength = 1; 12586 break; 12587 } 12588 12589 XSetLineAttributes(display, gc, p.width, style, 0, 0); 12590 if(dashLength) 12591 XSetDashes(display, gc, 0, &dashLength, 1); 12592 12593 if(p.color.a == 0) { 12594 foregroundIsNotTransparent = false; 12595 return; 12596 } 12597 12598 foregroundIsNotTransparent = true; 12599 12600 XSetForeground(display, gc, colorToX(p.color, display)); 12601 12602 version(with_xft) 12603 updateXftColor(); 12604 } 12605 12606 RasterOp _currentRasterOp; 12607 bool _currentRasterOpInitialized = false; 12608 @property void rasterOp(RasterOp op) { 12609 if(_currentRasterOpInitialized && _currentRasterOp == op) 12610 return; 12611 _currentRasterOp = op; 12612 _currentRasterOpInitialized = true; 12613 int mode; 12614 final switch(op) { 12615 case RasterOp.normal: 12616 mode = GXcopy; 12617 break; 12618 case RasterOp.xor: 12619 mode = GXxor; 12620 break; 12621 } 12622 XSetFunction(display, gc, mode); 12623 } 12624 12625 12626 bool _fillColorInitialized = false; 12627 12628 @property void fillColor(Color c) { 12629 if(_fillColorInitialized && _fillColor == c) 12630 return; // already good, no need to waste time calling it 12631 _fillColor = c; 12632 _fillColorInitialized = true; 12633 if(c.a == 0) { 12634 backgroundIsNotTransparent = false; 12635 return; 12636 } 12637 12638 backgroundIsNotTransparent = true; 12639 12640 XSetBackground(display, gc, colorToX(c, display)); 12641 12642 } 12643 12644 void swapColors() { 12645 auto tmp = _fillColor; 12646 fillColor = _outlineColor; 12647 auto newPen = _activePen; 12648 newPen.color = tmp; 12649 pen(newPen); 12650 } 12651 12652 uint colorToX(Color c, Display* display) { 12653 auto visual = DefaultVisual(display, DefaultScreen(display)); 12654 import core.bitop; 12655 uint color = 0; 12656 { 12657 auto startBit = bsf(visual.red_mask); 12658 auto lastBit = bsr(visual.red_mask); 12659 auto r = cast(uint) c.r; 12660 r >>= 7 - (lastBit - startBit); 12661 r <<= startBit; 12662 color |= r; 12663 } 12664 { 12665 auto startBit = bsf(visual.green_mask); 12666 auto lastBit = bsr(visual.green_mask); 12667 auto g = cast(uint) c.g; 12668 g >>= 7 - (lastBit - startBit); 12669 g <<= startBit; 12670 color |= g; 12671 } 12672 { 12673 auto startBit = bsf(visual.blue_mask); 12674 auto lastBit = bsr(visual.blue_mask); 12675 auto b = cast(uint) c.b; 12676 b >>= 7 - (lastBit - startBit); 12677 b <<= startBit; 12678 color |= b; 12679 } 12680 12681 12682 12683 return color; 12684 } 12685 12686 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 12687 // source x, source y 12688 if(ix >= i.width) return; 12689 if(iy >= i.height) return; 12690 if(ix + w > i.width) w = i.width - ix; 12691 if(iy + h > i.height) h = i.height - iy; 12692 if(i.usingXshm) 12693 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 12694 else 12695 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 12696 } 12697 12698 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 12699 if(s.enableAlpha) { 12700 // the Sprite must be created first, meaning if we're here, XRender is already loaded 12701 if(this.xrenderPicturePainter == None) { 12702 XRenderPictureAttributes attrs; 12703 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 12704 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 12705 12706 // need to initialize the clip 12707 XRectangle[1] rects; 12708 rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height); 12709 12710 XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length); 12711 } 12712 12713 XRenderComposite( 12714 display, 12715 3, // PicOpOver 12716 s.xrenderPicture, 12717 None, 12718 this.xrenderPicturePainter, 12719 ix, 12720 iy, 12721 0, 12722 0, 12723 x, 12724 y, 12725 w ? w : s.width, 12726 h ? h : s.height 12727 ); 12728 } else { 12729 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 12730 } 12731 } 12732 12733 int fontHeight() { 12734 version(with_xft) 12735 if(xftFont !is null) 12736 return xftFont.height; 12737 if(font) 12738 return font.max_bounds.ascent + font.max_bounds.descent; 12739 return 12; // pretty common default... 12740 } 12741 12742 int textWidth(in char[] line) { 12743 version(with_xft) 12744 if(xftFont) { 12745 if(line.length == 0) 12746 return 0; 12747 XGlyphInfo extents; 12748 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 12749 return extents.width; 12750 } 12751 12752 if(fontset) { 12753 if(line.length == 0) 12754 return 0; 12755 XRectangle rect; 12756 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 12757 12758 return rect.width; 12759 } 12760 12761 if(font) 12762 // FIXME: unicode 12763 return XTextWidth( font, line.ptr, cast(int) line.length); 12764 else 12765 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 12766 } 12767 12768 Size textSize(in char[] text) { 12769 auto maxWidth = 0; 12770 auto lineHeight = fontHeight; 12771 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 12772 foreach(line; text.split('\n')) { 12773 int textWidth = this.textWidth(line); 12774 if(textWidth > maxWidth) 12775 maxWidth = textWidth; 12776 h += lineHeight + 4; 12777 } 12778 return Size(maxWidth, h); 12779 } 12780 12781 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 12782 const(char)[] text; 12783 version(with_xft) 12784 if(xftFont) { 12785 text = originalText; 12786 goto loaded; 12787 } 12788 12789 if(fontset) 12790 text = originalText; 12791 else { 12792 text.reserve(originalText.length); 12793 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 12794 // then strip the rest so there isn't garbage 12795 foreach(dchar ch; originalText) 12796 if(ch < 256) 12797 text ~= cast(ubyte) ch; 12798 else 12799 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 12800 } 12801 loaded: 12802 if(text.length == 0) 12803 return; 12804 12805 // FIXME: should we clip it to the bounding box? 12806 int textHeight = fontHeight; 12807 12808 auto lines = text.split('\n'); 12809 12810 const lineHeight = textHeight; 12811 textHeight *= lines.length; 12812 12813 int cy = y; 12814 12815 if(alignment & TextAlignment.VerticalBottom) { 12816 if(y2 <= 0) 12817 return; 12818 auto h = y2 - y; 12819 if(h > textHeight) { 12820 cy += h - textHeight; 12821 cy -= lineHeight / 2; 12822 } 12823 } else if(alignment & TextAlignment.VerticalCenter) { 12824 if(y2 <= 0) 12825 return; 12826 auto h = y2 - y; 12827 if(textHeight < h) { 12828 cy += (h - textHeight) / 2; 12829 //cy -= lineHeight / 4; 12830 } 12831 } 12832 12833 foreach(line; text.split('\n')) { 12834 int textWidth = this.textWidth(line); 12835 12836 int px = x, py = cy; 12837 12838 if(alignment & TextAlignment.Center) { 12839 if(x2 <= 0) 12840 return; 12841 auto w = x2 - x; 12842 if(w > textWidth) 12843 px += (w - textWidth) / 2; 12844 } else if(alignment & TextAlignment.Right) { 12845 if(x2 <= 0) 12846 return; 12847 auto pos = x2 - textWidth; 12848 if(pos > x) 12849 px = pos; 12850 } 12851 12852 version(with_xft) 12853 if(xftFont) { 12854 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 12855 12856 goto carry_on; 12857 } 12858 12859 if(fontset) 12860 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 12861 else 12862 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 12863 carry_on: 12864 cy += lineHeight + 4; 12865 } 12866 } 12867 12868 void drawPixel(int x, int y) { 12869 XDrawPoint(display, d, gc, x, y); 12870 } 12871 12872 // The basic shapes, outlined 12873 12874 void drawLine(int x1, int y1, int x2, int y2) { 12875 if(foregroundIsNotTransparent) 12876 XDrawLine(display, d, gc, x1, y1, x2, y2); 12877 } 12878 12879 void drawRectangle(int x, int y, int width, int height) { 12880 if(backgroundIsNotTransparent) { 12881 swapColors(); 12882 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 12883 swapColors(); 12884 } 12885 // 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 12886 if(foregroundIsNotTransparent) 12887 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 12888 } 12889 12890 /// Arguments are the points of the bounding rectangle 12891 void drawEllipse(int x1, int y1, int x2, int y2) { 12892 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 12893 } 12894 12895 // NOTE: start and finish are in units of degrees * 64 12896 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 12897 if(backgroundIsNotTransparent) { 12898 swapColors(); 12899 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 12900 swapColors(); 12901 } 12902 if(foregroundIsNotTransparent) { 12903 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 12904 // Windows draws the straight lines on the edges too so FIXME sort of 12905 } 12906 } 12907 12908 void drawPolygon(Point[] vertexes) { 12909 XPoint[16] pointsBuffer; 12910 XPoint[] points; 12911 if(vertexes.length <= pointsBuffer.length) 12912 points = pointsBuffer[0 .. vertexes.length]; 12913 else 12914 points.length = vertexes.length; 12915 12916 foreach(i, p; vertexes) { 12917 points[i].x = cast(short) p.x; 12918 points[i].y = cast(short) p.y; 12919 } 12920 12921 if(backgroundIsNotTransparent) { 12922 swapColors(); 12923 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 12924 swapColors(); 12925 } 12926 if(foregroundIsNotTransparent) { 12927 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 12928 } 12929 } 12930 } 12931 12932 /* XRender { */ 12933 12934 struct XRenderColor { 12935 ushort red; 12936 ushort green; 12937 ushort blue; 12938 ushort alpha; 12939 } 12940 12941 alias Picture = XID; 12942 alias PictFormat = XID; 12943 12944 struct XGlyphInfo { 12945 ushort width; 12946 ushort height; 12947 short x; 12948 short y; 12949 short xOff; 12950 short yOff; 12951 } 12952 12953 struct XRenderDirectFormat { 12954 short red; 12955 short redMask; 12956 short green; 12957 short greenMask; 12958 short blue; 12959 short blueMask; 12960 short alpha; 12961 short alphaMask; 12962 } 12963 12964 struct XRenderPictFormat { 12965 PictFormat id; 12966 int type; 12967 int depth; 12968 XRenderDirectFormat direct; 12969 Colormap colormap; 12970 } 12971 12972 enum PictFormatID = (1 << 0); 12973 enum PictFormatType = (1 << 1); 12974 enum PictFormatDepth = (1 << 2); 12975 enum PictFormatRed = (1 << 3); 12976 enum PictFormatRedMask =(1 << 4); 12977 enum PictFormatGreen = (1 << 5); 12978 enum PictFormatGreenMask=(1 << 6); 12979 enum PictFormatBlue = (1 << 7); 12980 enum PictFormatBlueMask =(1 << 8); 12981 enum PictFormatAlpha = (1 << 9); 12982 enum PictFormatAlphaMask=(1 << 10); 12983 enum PictFormatColormap =(1 << 11); 12984 12985 struct XRenderPictureAttributes { 12986 int repeat; 12987 Picture alpha_map; 12988 int alpha_x_origin; 12989 int alpha_y_origin; 12990 int clip_x_origin; 12991 int clip_y_origin; 12992 Pixmap clip_mask; 12993 Bool graphics_exposures; 12994 int subwindow_mode; 12995 int poly_edge; 12996 int poly_mode; 12997 Atom dither; 12998 Bool component_alpha; 12999 } 13000 13001 alias int XFixed; 13002 13003 struct XPointFixed { 13004 XFixed x, y; 13005 } 13006 13007 struct XCircle { 13008 XFixed x; 13009 XFixed y; 13010 XFixed radius; 13011 } 13012 13013 struct XTransform { 13014 XFixed[3][3] matrix; 13015 } 13016 13017 struct XFilters { 13018 int nfilter; 13019 char **filter; 13020 int nalias; 13021 short *alias_; 13022 } 13023 13024 struct XIndexValue { 13025 c_ulong pixel; 13026 ushort red, green, blue, alpha; 13027 } 13028 13029 struct XAnimCursor { 13030 Cursor cursor; 13031 c_ulong delay; 13032 } 13033 13034 struct XLinearGradient { 13035 XPointFixed p1; 13036 XPointFixed p2; 13037 } 13038 13039 struct XRadialGradient { 13040 XCircle inner; 13041 XCircle outer; 13042 } 13043 13044 struct XConicalGradient { 13045 XPointFixed center; 13046 XFixed angle; /* in degrees */ 13047 } 13048 13049 enum PictStandardARGB32 = 0; 13050 enum PictStandardRGB24 = 1; 13051 enum PictStandardA8 = 2; 13052 enum PictStandardA4 = 3; 13053 enum PictStandardA1 = 4; 13054 enum PictStandardNUM = 5; 13055 13056 interface XRender { 13057 extern(C) @nogc: 13058 13059 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 13060 13061 Status XRenderQueryVersion (Display *dpy, 13062 int *major_versionp, 13063 int *minor_versionp); 13064 13065 Status XRenderQueryFormats (Display *dpy); 13066 13067 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 13068 13069 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 13070 13071 XRenderPictFormat * 13072 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 13073 13074 XRenderPictFormat * 13075 XRenderFindFormat (Display *dpy, 13076 c_ulong mask, 13077 const XRenderPictFormat *templ, 13078 int count); 13079 XRenderPictFormat * 13080 XRenderFindStandardFormat (Display *dpy, 13081 int format); 13082 13083 XIndexValue * 13084 XRenderQueryPictIndexValues(Display *dpy, 13085 const XRenderPictFormat *format, 13086 int *num); 13087 13088 Picture XRenderCreatePicture( 13089 Display *dpy, 13090 Drawable drawable, 13091 const XRenderPictFormat *format, 13092 c_ulong valuemask, 13093 const XRenderPictureAttributes *attributes); 13094 13095 void XRenderChangePicture (Display *dpy, 13096 Picture picture, 13097 c_ulong valuemask, 13098 const XRenderPictureAttributes *attributes); 13099 13100 void 13101 XRenderSetPictureClipRectangles (Display *dpy, 13102 Picture picture, 13103 int xOrigin, 13104 int yOrigin, 13105 const XRectangle *rects, 13106 int n); 13107 13108 void 13109 XRenderSetPictureClipRegion (Display *dpy, 13110 Picture picture, 13111 Region r); 13112 13113 void 13114 XRenderSetPictureTransform (Display *dpy, 13115 Picture picture, 13116 XTransform *transform); 13117 13118 void 13119 XRenderFreePicture (Display *dpy, 13120 Picture picture); 13121 13122 void 13123 XRenderComposite (Display *dpy, 13124 int op, 13125 Picture src, 13126 Picture mask, 13127 Picture dst, 13128 int src_x, 13129 int src_y, 13130 int mask_x, 13131 int mask_y, 13132 int dst_x, 13133 int dst_y, 13134 uint width, 13135 uint height); 13136 13137 13138 Picture XRenderCreateSolidFill (Display *dpy, 13139 const XRenderColor *color); 13140 13141 Picture XRenderCreateLinearGradient (Display *dpy, 13142 const XLinearGradient *gradient, 13143 const XFixed *stops, 13144 const XRenderColor *colors, 13145 int nstops); 13146 13147 Picture XRenderCreateRadialGradient (Display *dpy, 13148 const XRadialGradient *gradient, 13149 const XFixed *stops, 13150 const XRenderColor *colors, 13151 int nstops); 13152 13153 Picture XRenderCreateConicalGradient (Display *dpy, 13154 const XConicalGradient *gradient, 13155 const XFixed *stops, 13156 const XRenderColor *colors, 13157 int nstops); 13158 13159 13160 13161 Cursor 13162 XRenderCreateCursor (Display *dpy, 13163 Picture source, 13164 uint x, 13165 uint y); 13166 13167 XFilters * 13168 XRenderQueryFilters (Display *dpy, Drawable drawable); 13169 13170 void 13171 XRenderSetPictureFilter (Display *dpy, 13172 Picture picture, 13173 const char *filter, 13174 XFixed *params, 13175 int nparams); 13176 13177 Cursor 13178 XRenderCreateAnimCursor (Display *dpy, 13179 int ncursor, 13180 XAnimCursor *cursors); 13181 } 13182 13183 __gshared bool XRenderLibrarySuccessfullyLoaded = true; 13184 mixin DynamicLoad!(XRender, "Xrender", 1, XRenderLibrarySuccessfullyLoaded) XRenderLibrary; 13185 13186 /* XRender } */ 13187 13188 /* Xrandr { */ 13189 13190 struct XRRMonitorInfo { 13191 Atom name; 13192 Bool primary; 13193 Bool automatic; 13194 int noutput; 13195 int x; 13196 int y; 13197 int width; 13198 int height; 13199 int mwidth; 13200 int mheight; 13201 /*RROutput*/ void *outputs; 13202 } 13203 13204 struct XRRScreenChangeNotifyEvent { 13205 int type; /* event base */ 13206 c_ulong serial; /* # of last request processed by server */ 13207 Bool send_event; /* true if this came from a SendEvent request */ 13208 Display *display; /* Display the event was read from */ 13209 Window window; /* window which selected for this event */ 13210 Window root; /* Root window for changed screen */ 13211 Time timestamp; /* when the screen change occurred */ 13212 Time config_timestamp; /* when the last configuration change */ 13213 ushort/*SizeID*/ size_index; 13214 ushort/*SubpixelOrder*/ subpixel_order; 13215 ushort/*Rotation*/ rotation; 13216 int width; 13217 int height; 13218 int mwidth; 13219 int mheight; 13220 } 13221 13222 enum RRScreenChangeNotify = 0; 13223 13224 enum RRScreenChangeNotifyMask = 1; 13225 13226 __gshared int xrrEventBase = -1; 13227 13228 13229 interface XRandr { 13230 extern(C) @nogc: 13231 Bool XRRQueryExtension (Display *dpy, int *event_base_return, int *error_base_return); 13232 Status XRRQueryVersion (Display *dpy, int *major_version_return, int *minor_version_return); 13233 13234 XRRMonitorInfo * XRRGetMonitors(Display *dpy, Window window, Bool get_active, int *nmonitors); 13235 void XRRFreeMonitors(XRRMonitorInfo *monitors); 13236 13237 void XRRSelectInput(Display *dpy, Window window, int mask); 13238 } 13239 13240 __gshared bool XRandrLibrarySuccessfullyLoaded = true; 13241 mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrLibrary; 13242 /* Xrandr } */ 13243 13244 /* Xft { */ 13245 13246 // actually freetype 13247 alias void FT_Face; 13248 13249 // actually fontconfig 13250 private alias FcBool = int; 13251 alias void FcCharSet; 13252 alias void FcPattern; 13253 alias void FcResult; 13254 enum FcEndian { FcEndianBig, FcEndianLittle } 13255 struct FcFontSet { 13256 int nfont; 13257 int sfont; 13258 FcPattern** fonts; 13259 } 13260 13261 // actually XRegion 13262 struct BOX { 13263 short x1, x2, y1, y2; 13264 } 13265 struct _XRegion { 13266 c_long size; 13267 c_long numRects; 13268 BOX* rects; 13269 BOX extents; 13270 } 13271 13272 alias Region = _XRegion*; 13273 13274 // ok actually Xft 13275 13276 struct XftFontInfo; 13277 13278 struct XftFont { 13279 int ascent; 13280 int descent; 13281 int height; 13282 int max_advance_width; 13283 FcCharSet* charset; 13284 FcPattern* pattern; 13285 } 13286 13287 struct XftDraw; 13288 13289 struct XftColor { 13290 c_ulong pixel; 13291 XRenderColor color; 13292 } 13293 13294 struct XftCharSpec { 13295 dchar ucs4; 13296 short x; 13297 short y; 13298 } 13299 13300 struct XftCharFontSpec { 13301 XftFont *font; 13302 dchar ucs4; 13303 short x; 13304 short y; 13305 } 13306 13307 struct XftGlyphSpec { 13308 uint glyph; 13309 short x; 13310 short y; 13311 } 13312 13313 struct XftGlyphFontSpec { 13314 XftFont *font; 13315 uint glyph; 13316 short x; 13317 short y; 13318 } 13319 13320 interface Xft { 13321 extern(C) @nogc pure: 13322 13323 Bool XftColorAllocName (Display *dpy, 13324 const Visual *visual, 13325 Colormap cmap, 13326 const char *name, 13327 XftColor *result); 13328 13329 Bool XftColorAllocValue (Display *dpy, 13330 Visual *visual, 13331 Colormap cmap, 13332 const XRenderColor *color, 13333 XftColor *result); 13334 13335 void XftColorFree (Display *dpy, 13336 Visual *visual, 13337 Colormap cmap, 13338 XftColor *color); 13339 13340 Bool XftDefaultHasRender (Display *dpy); 13341 13342 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 13343 13344 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 13345 13346 XftDraw * XftDrawCreate (Display *dpy, 13347 Drawable drawable, 13348 Visual *visual, 13349 Colormap colormap); 13350 13351 XftDraw * XftDrawCreateBitmap (Display *dpy, 13352 Pixmap bitmap); 13353 13354 XftDraw * XftDrawCreateAlpha (Display *dpy, 13355 Pixmap pixmap, 13356 int depth); 13357 13358 void XftDrawChange (XftDraw *draw, 13359 Drawable drawable); 13360 13361 Display * XftDrawDisplay (XftDraw *draw); 13362 13363 Drawable XftDrawDrawable (XftDraw *draw); 13364 13365 Colormap XftDrawColormap (XftDraw *draw); 13366 13367 Visual * XftDrawVisual (XftDraw *draw); 13368 13369 void XftDrawDestroy (XftDraw *draw); 13370 13371 Picture XftDrawPicture (XftDraw *draw); 13372 13373 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 13374 13375 void XftDrawGlyphs (XftDraw *draw, 13376 const XftColor *color, 13377 XftFont *pub, 13378 int x, 13379 int y, 13380 const uint *glyphs, 13381 int nglyphs); 13382 13383 void XftDrawString8 (XftDraw *draw, 13384 const XftColor *color, 13385 XftFont *pub, 13386 int x, 13387 int y, 13388 const char *string, 13389 int len); 13390 13391 void XftDrawString16 (XftDraw *draw, 13392 const XftColor *color, 13393 XftFont *pub, 13394 int x, 13395 int y, 13396 const wchar *string, 13397 int len); 13398 13399 void XftDrawString32 (XftDraw *draw, 13400 const XftColor *color, 13401 XftFont *pub, 13402 int x, 13403 int y, 13404 const dchar *string, 13405 int len); 13406 13407 void XftDrawStringUtf8 (XftDraw *draw, 13408 const XftColor *color, 13409 XftFont *pub, 13410 int x, 13411 int y, 13412 const char *string, 13413 int len); 13414 void XftDrawStringUtf16 (XftDraw *draw, 13415 const XftColor *color, 13416 XftFont *pub, 13417 int x, 13418 int y, 13419 const char *string, 13420 FcEndian endian, 13421 int len); 13422 13423 void XftDrawCharSpec (XftDraw *draw, 13424 const XftColor *color, 13425 XftFont *pub, 13426 const XftCharSpec *chars, 13427 int len); 13428 13429 void XftDrawCharFontSpec (XftDraw *draw, 13430 const XftColor *color, 13431 const XftCharFontSpec *chars, 13432 int len); 13433 13434 void XftDrawGlyphSpec (XftDraw *draw, 13435 const XftColor *color, 13436 XftFont *pub, 13437 const XftGlyphSpec *glyphs, 13438 int len); 13439 13440 void XftDrawGlyphFontSpec (XftDraw *draw, 13441 const XftColor *color, 13442 const XftGlyphFontSpec *glyphs, 13443 int len); 13444 13445 void XftDrawRect (XftDraw *draw, 13446 const XftColor *color, 13447 int x, 13448 int y, 13449 uint width, 13450 uint height); 13451 13452 Bool XftDrawSetClip (XftDraw *draw, 13453 Region r); 13454 13455 13456 Bool XftDrawSetClipRectangles (XftDraw *draw, 13457 int xOrigin, 13458 int yOrigin, 13459 const XRectangle *rects, 13460 int n); 13461 13462 void XftDrawSetSubwindowMode (XftDraw *draw, 13463 int mode); 13464 13465 void XftGlyphExtents (Display *dpy, 13466 XftFont *pub, 13467 const uint *glyphs, 13468 int nglyphs, 13469 XGlyphInfo *extents); 13470 13471 void XftTextExtents8 (Display *dpy, 13472 XftFont *pub, 13473 const char *string, 13474 int len, 13475 XGlyphInfo *extents); 13476 13477 void XftTextExtents16 (Display *dpy, 13478 XftFont *pub, 13479 const wchar *string, 13480 int len, 13481 XGlyphInfo *extents); 13482 13483 void XftTextExtents32 (Display *dpy, 13484 XftFont *pub, 13485 const dchar *string, 13486 int len, 13487 XGlyphInfo *extents); 13488 13489 void XftTextExtentsUtf8 (Display *dpy, 13490 XftFont *pub, 13491 const char *string, 13492 int len, 13493 XGlyphInfo *extents); 13494 13495 void XftTextExtentsUtf16 (Display *dpy, 13496 XftFont *pub, 13497 const char *string, 13498 FcEndian endian, 13499 int len, 13500 XGlyphInfo *extents); 13501 13502 FcPattern * XftFontMatch (Display *dpy, 13503 int screen, 13504 const FcPattern *pattern, 13505 FcResult *result); 13506 13507 XftFont * XftFontOpen (Display *dpy, int screen, ...); 13508 13509 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 13510 13511 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 13512 13513 FT_Face XftLockFace (XftFont *pub); 13514 13515 void XftUnlockFace (XftFont *pub); 13516 13517 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 13518 13519 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 13520 13521 dchar XftFontInfoHash (const XftFontInfo *fi); 13522 13523 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 13524 13525 XftFont * XftFontOpenInfo (Display *dpy, 13526 FcPattern *pattern, 13527 XftFontInfo *fi); 13528 13529 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 13530 13531 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 13532 13533 void XftFontClose (Display *dpy, XftFont *pub); 13534 13535 FcBool XftInitFtLibrary(); 13536 void XftFontLoadGlyphs (Display *dpy, 13537 XftFont *pub, 13538 FcBool need_bitmaps, 13539 const uint *glyphs, 13540 int nglyph); 13541 13542 void XftFontUnloadGlyphs (Display *dpy, 13543 XftFont *pub, 13544 const uint *glyphs, 13545 int nglyph); 13546 13547 FcBool XftFontCheckGlyph (Display *dpy, 13548 XftFont *pub, 13549 FcBool need_bitmaps, 13550 uint glyph, 13551 uint *missing, 13552 int *nmissing); 13553 13554 FcBool XftCharExists (Display *dpy, 13555 XftFont *pub, 13556 dchar ucs4); 13557 13558 uint XftCharIndex (Display *dpy, 13559 XftFont *pub, 13560 dchar ucs4); 13561 FcBool XftInit (const char *config); 13562 13563 int XftGetVersion (); 13564 13565 FcFontSet * XftListFonts (Display *dpy, 13566 int screen, 13567 ...); 13568 13569 FcPattern *XftNameParse (const char *name); 13570 13571 void XftGlyphRender (Display *dpy, 13572 int op, 13573 Picture src, 13574 XftFont *pub, 13575 Picture dst, 13576 int srcx, 13577 int srcy, 13578 int x, 13579 int y, 13580 const uint *glyphs, 13581 int nglyphs); 13582 13583 void XftGlyphSpecRender (Display *dpy, 13584 int op, 13585 Picture src, 13586 XftFont *pub, 13587 Picture dst, 13588 int srcx, 13589 int srcy, 13590 const XftGlyphSpec *glyphs, 13591 int nglyphs); 13592 13593 void XftCharSpecRender (Display *dpy, 13594 int op, 13595 Picture src, 13596 XftFont *pub, 13597 Picture dst, 13598 int srcx, 13599 int srcy, 13600 const XftCharSpec *chars, 13601 int len); 13602 void XftGlyphFontSpecRender (Display *dpy, 13603 int op, 13604 Picture src, 13605 Picture dst, 13606 int srcx, 13607 int srcy, 13608 const XftGlyphFontSpec *glyphs, 13609 int nglyphs); 13610 13611 void XftCharFontSpecRender (Display *dpy, 13612 int op, 13613 Picture src, 13614 Picture dst, 13615 int srcx, 13616 int srcy, 13617 const XftCharFontSpec *chars, 13618 int len); 13619 13620 void XftTextRender8 (Display *dpy, 13621 int op, 13622 Picture src, 13623 XftFont *pub, 13624 Picture dst, 13625 int srcx, 13626 int srcy, 13627 int x, 13628 int y, 13629 const char *string, 13630 int len); 13631 void XftTextRender16 (Display *dpy, 13632 int op, 13633 Picture src, 13634 XftFont *pub, 13635 Picture dst, 13636 int srcx, 13637 int srcy, 13638 int x, 13639 int y, 13640 const wchar *string, 13641 int len); 13642 13643 void XftTextRender16BE (Display *dpy, 13644 int op, 13645 Picture src, 13646 XftFont *pub, 13647 Picture dst, 13648 int srcx, 13649 int srcy, 13650 int x, 13651 int y, 13652 const char *string, 13653 int len); 13654 13655 void XftTextRender16LE (Display *dpy, 13656 int op, 13657 Picture src, 13658 XftFont *pub, 13659 Picture dst, 13660 int srcx, 13661 int srcy, 13662 int x, 13663 int y, 13664 const char *string, 13665 int len); 13666 13667 void XftTextRender32 (Display *dpy, 13668 int op, 13669 Picture src, 13670 XftFont *pub, 13671 Picture dst, 13672 int srcx, 13673 int srcy, 13674 int x, 13675 int y, 13676 const dchar *string, 13677 int len); 13678 13679 void XftTextRender32BE (Display *dpy, 13680 int op, 13681 Picture src, 13682 XftFont *pub, 13683 Picture dst, 13684 int srcx, 13685 int srcy, 13686 int x, 13687 int y, 13688 const char *string, 13689 int len); 13690 13691 void XftTextRender32LE (Display *dpy, 13692 int op, 13693 Picture src, 13694 XftFont *pub, 13695 Picture dst, 13696 int srcx, 13697 int srcy, 13698 int x, 13699 int y, 13700 const char *string, 13701 int len); 13702 13703 void XftTextRenderUtf8 (Display *dpy, 13704 int op, 13705 Picture src, 13706 XftFont *pub, 13707 Picture dst, 13708 int srcx, 13709 int srcy, 13710 int x, 13711 int y, 13712 const char *string, 13713 int len); 13714 13715 void XftTextRenderUtf16 (Display *dpy, 13716 int op, 13717 Picture src, 13718 XftFont *pub, 13719 Picture dst, 13720 int srcx, 13721 int srcy, 13722 int x, 13723 int y, 13724 const char *string, 13725 FcEndian endian, 13726 int len); 13727 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 13728 13729 } 13730 13731 interface FontConfig { 13732 extern(C) @nogc pure: 13733 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 13734 void FcFontSetDestroy(FcFontSet*); 13735 char* FcNameUnparse(const FcPattern *); 13736 } 13737 13738 mixin DynamicLoad!(Xft, "Xft", 2, librariesSuccessfullyLoaded) XftLibrary; 13739 mixin DynamicLoad!(FontConfig, "fontconfig", 1, librariesSuccessfullyLoaded) FontConfigLibrary; 13740 13741 13742 /* Xft } */ 13743 13744 class XDisconnectException : Exception { 13745 bool userRequested; 13746 this(bool userRequested = true) { 13747 this.userRequested = userRequested; 13748 super("X disconnected"); 13749 } 13750 } 13751 13752 /++ 13753 Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this. 13754 13755 Please note that it returns 13756 +/ 13757 XErrorEvent[] trapXErrors(scope void delegate() dg) { 13758 13759 static XErrorEvent[] errorBuffer; 13760 13761 static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow { 13762 errorBuffer ~= *evt; 13763 return 0; 13764 } 13765 13766 auto savedErrorHandler = XSetErrorHandler(&handler); 13767 13768 try { 13769 dg(); 13770 } finally { 13771 XSync(XDisplayConnection.get, 0/*False*/); 13772 XSetErrorHandler(savedErrorHandler); 13773 } 13774 13775 auto bfr = errorBuffer; 13776 errorBuffer = null; 13777 13778 return bfr; 13779 } 13780 13781 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`. 13782 class XDisplayConnection { 13783 private __gshared Display* display; 13784 private __gshared XIM xim; 13785 private __gshared char* displayName; 13786 13787 private __gshared int connectionSequence_; 13788 private __gshared bool isLocal_; 13789 13790 /// use this for lazy caching when reconnection 13791 static int connectionSequenceNumber() { return connectionSequence_; } 13792 13793 /++ 13794 Guesses if the connection appears to be local. 13795 13796 History: 13797 Added June 3, 2021 13798 +/ 13799 static @property bool isLocal() nothrow @trusted @nogc { 13800 return isLocal_; 13801 } 13802 13803 /// Attempts recreation of state, may require application assistance 13804 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 13805 /// then call this, and if successful, reenter the loop. 13806 static void discardAndRecreate(string newDisplayString = null) { 13807 if(insideXEventLoop) 13808 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 13809 13810 // 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 13811 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 13812 13813 foreach(handle; chnenhm) { 13814 handle.discardConnectionState(); 13815 } 13816 13817 discardState(); 13818 13819 if(newDisplayString !is null) 13820 setDisplayName(newDisplayString); 13821 13822 auto display = get(); 13823 13824 foreach(handle; chnenhm) { 13825 handle.recreateAfterDisconnect(); 13826 } 13827 } 13828 13829 private __gshared EventMask rootEventMask; 13830 13831 /++ 13832 Requests the specified input from the root window on the connection, in addition to any other request. 13833 13834 13835 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. 13836 13837 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 13838 +/ 13839 static void addRootInput(EventMask mask) { 13840 auto old = rootEventMask; 13841 rootEventMask |= mask; 13842 get(); // to ensure display connected 13843 if(display !is null && rootEventMask != old) 13844 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 13845 } 13846 13847 static void discardState() { 13848 freeImages(); 13849 13850 foreach(atomPtr; interredAtoms) 13851 *atomPtr = 0; 13852 interredAtoms = null; 13853 interredAtoms.assumeSafeAppend(); 13854 13855 ScreenPainterImplementation.fontAttempted = false; 13856 ScreenPainterImplementation.defaultfont = null; 13857 ScreenPainterImplementation.defaultfontset = null; 13858 13859 Image.impl.xshmQueryCompleted = false; 13860 Image.impl._xshmAvailable = false; 13861 13862 SimpleWindow.nativeMapping = null; 13863 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 13864 // GlobalHotkeyManager 13865 13866 display = null; 13867 xim = null; 13868 } 13869 13870 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 13871 private static void createXIM () { 13872 import core.stdc.locale : setlocale, LC_ALL; 13873 import core.stdc.stdio : stderr, fprintf; 13874 import core.stdc.stdlib : free; 13875 import core.stdc.string : strdup; 13876 13877 static immutable string[3] mtry = [ null, "@im=local", "@im=" ]; 13878 13879 auto olocale = strdup(setlocale(LC_ALL, null)); 13880 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 13881 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 13882 13883 //fprintf(stderr, "opening IM...\n"); 13884 foreach (string s; mtry) { 13885 if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 13886 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 13887 } 13888 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 13889 } 13890 13891 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 13892 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 13893 static struct ImgList { 13894 size_t img; // class; hide it from GC 13895 ImgList* next; 13896 } 13897 13898 static __gshared ImgList* imglist = null; 13899 static __gshared bool imglistLocked = false; // true: don't register and unregister images 13900 13901 static void registerImage (Image img) { 13902 if (!imglistLocked && img !is null) { 13903 import core.stdc.stdlib : malloc; 13904 auto it = cast(ImgList*)malloc(ImgList.sizeof); 13905 assert(it !is null); // do proper checks 13906 it.img = cast(size_t)cast(void*)img; 13907 it.next = imglist; 13908 imglist = it; 13909 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 13910 } 13911 } 13912 13913 static void unregisterImage (Image img) { 13914 if (!imglistLocked && img !is null) { 13915 import core.stdc.stdlib : free; 13916 ImgList* prev = null; 13917 ImgList* cur = imglist; 13918 while (cur !is null) { 13919 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 13920 prev = cur; 13921 cur = cur.next; 13922 } 13923 if (cur !is null) { 13924 if (prev is null) imglist = cur.next; else prev.next = cur.next; 13925 free(cur); 13926 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 13927 } else { 13928 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 13929 } 13930 } 13931 } 13932 13933 static void freeImages () { // needed for discardAndRecreate 13934 imglistLocked = true; 13935 scope(exit) imglistLocked = false; 13936 ImgList* cur = imglist; 13937 ImgList* next = null; 13938 while (cur !is null) { 13939 import core.stdc.stdlib : free; 13940 next = cur.next; 13941 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 13942 (cast(Image)cast(void*)cur.img).dispose(); 13943 free(cur); 13944 cur = next; 13945 } 13946 imglist = null; 13947 } 13948 13949 /// can be used to override normal handling of display name 13950 /// from environment and/or command line 13951 static setDisplayName(string newDisplayName) { 13952 displayName = cast(char*) (newDisplayName ~ '\0'); 13953 } 13954 13955 /// resets to the default display string 13956 static resetDisplayName() { 13957 displayName = null; 13958 } 13959 13960 /// 13961 static Display* get() { 13962 if(display is null) { 13963 if(!librariesSuccessfullyLoaded) 13964 throw new Exception("Unable to load X11 client libraries"); 13965 display = XOpenDisplay(displayName); 13966 13967 isLocal_ = false; 13968 13969 connectionSequence_++; 13970 if(display is null) 13971 throw new Exception("Unable to open X display"); 13972 13973 auto str = display.display_name; 13974 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 13975 // and otherwise it probably isn't 13976 if(str is null || (str[0] != ':' && str[0] != '/')) 13977 isLocal_ = false; 13978 else 13979 isLocal_ = true; 13980 13981 //XSetErrorHandler(&adrlogger); 13982 //XSynchronize(display, true); 13983 13984 13985 XSetIOErrorHandler(&x11ioerrCB); 13986 Bool sup; 13987 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 13988 createXIM(); 13989 version(with_eventloop) { 13990 import arsd.eventloop; 13991 addFileEventListeners(display.fd, &eventListener, null, null); 13992 } 13993 } 13994 13995 return display; 13996 } 13997 13998 extern(C) 13999 static int x11ioerrCB(Display* dpy) { 14000 throw new XDisconnectException(false); 14001 } 14002 14003 version(with_eventloop) { 14004 import arsd.eventloop; 14005 static void eventListener(OsFileHandle fd) { 14006 //this.mtLock(); 14007 //scope(exit) this.mtUnlock(); 14008 while(XPending(display)) 14009 doXNextEvent(display); 14010 } 14011 } 14012 14013 // close connection on program exit -- we need this to properly free all images 14014 static ~this () { 14015 // the gui thread must clean up after itself or else Xlib might deadlock 14016 // using this flag on any thread destruction is the easiest way i know of 14017 // (shared static this is run by the LAST thread to exit, which may not be 14018 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 14019 if(thisIsGuiThread) 14020 close(); 14021 } 14022 14023 /// 14024 static void close() { 14025 if(display is null) 14026 return; 14027 14028 version(with_eventloop) { 14029 import arsd.eventloop; 14030 removeFileEventListeners(display.fd); 14031 } 14032 14033 // now remove all registered images to prevent shared memory leaks 14034 freeImages(); 14035 14036 // tbh I don't know why it is doing this but like if this happens to run 14037 // from the other thread there's frequent hanging inside here. 14038 if(thisIsGuiThread) 14039 XCloseDisplay(display); 14040 display = null; 14041 } 14042 } 14043 14044 mixin template NativeImageImplementation() { 14045 XImage* handle; 14046 ubyte* rawData; 14047 14048 XShmSegmentInfo shminfo; 14049 14050 __gshared bool xshmQueryCompleted; 14051 __gshared bool _xshmAvailable; 14052 public static @property bool xshmAvailable() { 14053 if(!xshmQueryCompleted) { 14054 int i1, i2, i3; 14055 xshmQueryCompleted = true; 14056 14057 if(!XDisplayConnection.isLocal) 14058 _xshmAvailable = false; 14059 else 14060 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 14061 } 14062 return _xshmAvailable; 14063 } 14064 14065 bool usingXshm; 14066 final: 14067 14068 private __gshared bool xshmfailed; 14069 14070 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 14071 auto display = XDisplayConnection.get(); 14072 assert(display !is null); 14073 auto screen = DefaultScreen(display); 14074 14075 // it will only use shared memory for somewhat largish images, 14076 // since otherwise we risk wasting shared memory handles on a lot of little ones 14077 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 14078 14079 14080 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 14081 // the actual use still fails. For example, if the program is in a container and permission denied 14082 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 14083 // 14084 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 14085 14086 14087 // synchronize so preexisting buffers are clear 14088 XSync(display, false); 14089 xshmfailed = false; 14090 14091 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 14092 14093 14094 usingXshm = true; 14095 handle = XShmCreateImage( 14096 display, 14097 DefaultVisual(display, screen), 14098 enableAlpha ? 32: 24, 14099 ImageFormat.ZPixmap, 14100 null, 14101 &shminfo, 14102 width, height); 14103 if(handle is null) 14104 goto abortXshm1; 14105 14106 if(handle.bytes_per_line != 4 * width) 14107 goto abortXshm2; 14108 14109 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 14110 if(shminfo.shmid < 0) 14111 goto abortXshm3; 14112 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 14113 if(rawData == cast(ubyte*) -1) 14114 goto abortXshm4; 14115 shminfo.readOnly = 0; 14116 XShmAttach(display, &shminfo); 14117 14118 // and now to the final error check to ensure it actually worked. 14119 XSync(display, false); 14120 if(xshmfailed) 14121 goto abortXshm5; 14122 14123 XSetErrorHandler(oldErrorHandler); 14124 14125 XDisplayConnection.registerImage(this); 14126 // if I don't flush here there's a chance the dtor will run before the 14127 // ctor and lead to a bad value X error. While this hurts the efficiency 14128 // it is local anyway so prolly better to keep it simple 14129 XFlush(display); 14130 14131 return; 14132 14133 abortXshm5: 14134 shmdt(shminfo.shmaddr); 14135 rawData = null; 14136 14137 abortXshm4: 14138 shmctl(shminfo.shmid, IPC_RMID, null); 14139 14140 abortXshm3: 14141 // nothing needed, the shmget failed so there's nothing to free 14142 14143 abortXshm2: 14144 XDestroyImage(handle); 14145 handle = null; 14146 14147 abortXshm1: 14148 XSetErrorHandler(oldErrorHandler); 14149 usingXshm = false; 14150 handle = null; 14151 14152 shminfo = typeof(shminfo).init; 14153 14154 _xshmAvailable = false; // don't try again in the future 14155 14156 //import std.stdio; writeln("fallingback"); 14157 14158 goto fallback; 14159 14160 } else { 14161 fallback: 14162 14163 if (forcexshm) throw new Exception("can't create XShm Image"); 14164 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 14165 import core.stdc.stdlib : malloc; 14166 rawData = cast(ubyte*) malloc(width * height * 4); 14167 14168 handle = XCreateImage( 14169 display, 14170 DefaultVisual(display, screen), 14171 enableAlpha ? 32 : 24, // bpp 14172 ImageFormat.ZPixmap, 14173 0, // offset 14174 rawData, 14175 width, height, 14176 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 14177 } 14178 } 14179 14180 void dispose() { 14181 // note: this calls free(rawData) for us 14182 if(handle) { 14183 if (usingXshm) { 14184 XDisplayConnection.unregisterImage(this); 14185 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 14186 } 14187 XDestroyImage(handle); 14188 if(usingXshm) { 14189 shmdt(shminfo.shmaddr); 14190 shmctl(shminfo.shmid, IPC_RMID, null); 14191 } 14192 handle = null; 14193 } 14194 } 14195 14196 Color getPixel(int x, int y) { 14197 auto offset = (y * width + x) * 4; 14198 Color c; 14199 c.a = enableAlpha ? rawData[offset + 3] : 255; 14200 c.b = rawData[offset + 0]; 14201 c.g = rawData[offset + 1]; 14202 c.r = rawData[offset + 2]; 14203 if(enableAlpha) 14204 c.unPremultiply; 14205 return c; 14206 } 14207 14208 void setPixel(int x, int y, Color c) { 14209 if(enableAlpha) 14210 c.premultiply(); 14211 auto offset = (y * width + x) * 4; 14212 rawData[offset + 0] = c.b; 14213 rawData[offset + 1] = c.g; 14214 rawData[offset + 2] = c.r; 14215 if(enableAlpha) 14216 rawData[offset + 3] = c.a; 14217 } 14218 14219 void convertToRgbaBytes(ubyte[] where) { 14220 assert(where.length == this.width * this.height * 4); 14221 14222 // if rawData had a length.... 14223 //assert(rawData.length == where.length); 14224 for(int idx = 0; idx < where.length; idx += 4) { 14225 where[idx + 0] = rawData[idx + 2]; // r 14226 where[idx + 1] = rawData[idx + 1]; // g 14227 where[idx + 2] = rawData[idx + 0]; // b 14228 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 14229 14230 if(enableAlpha) 14231 unPremultiplyRgba(where[idx .. idx + 4]); 14232 } 14233 } 14234 14235 void setFromRgbaBytes(in ubyte[] where) { 14236 assert(where.length == this.width * this.height * 4); 14237 14238 // if rawData had a length.... 14239 //assert(rawData.length == where.length); 14240 for(int idx = 0; idx < where.length; idx += 4) { 14241 rawData[idx + 2] = where[idx + 0]; // r 14242 rawData[idx + 1] = where[idx + 1]; // g 14243 rawData[idx + 0] = where[idx + 2]; // b 14244 if(enableAlpha) { 14245 rawData[idx + 3] = where[idx + 3]; // a 14246 premultiplyBgra(rawData[idx .. idx + 4]); 14247 } 14248 } 14249 } 14250 14251 } 14252 14253 mixin template NativeSimpleWindowImplementation() { 14254 GC gc; 14255 Window window; 14256 Display* display; 14257 14258 Pixmap buffer; 14259 int bufferw, bufferh; // size of the buffer; can be bigger than window 14260 XIC xic; // input context 14261 int curHidden = 0; // counter 14262 Cursor blankCurPtr = 0; 14263 int cursorSequenceNumber = 0; 14264 int warpEventCount = 0; // number of mouse movement events to eat 14265 14266 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 14267 X11GetSelectionHandler[Atom] getSelectionHandlers; 14268 14269 version(without_opengl) {} else 14270 GLXContext glc; 14271 14272 private void fixFixedSize(bool forced=false) (int width, int height) { 14273 if (forced || this.resizability == Resizability.fixedSize) { 14274 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 14275 XSizeHints sh; 14276 static if (!forced) { 14277 c_long spr; 14278 XGetWMNormalHints(display, window, &sh, &spr); 14279 sh.flags |= PMaxSize | PMinSize; 14280 } else { 14281 sh.flags = PMaxSize | PMinSize; 14282 } 14283 sh.min_width = width; 14284 sh.min_height = height; 14285 sh.max_width = width; 14286 sh.max_height = height; 14287 XSetWMNormalHints(display, window, &sh); 14288 //XFlush(display); 14289 } 14290 } 14291 14292 ScreenPainter getPainter(bool manualInvalidations) { 14293 return ScreenPainter(this, window, manualInvalidations); 14294 } 14295 14296 void move(int x, int y) { 14297 XMoveWindow(display, window, x, y); 14298 } 14299 14300 void resize(int w, int h) { 14301 if (w < 1) w = 1; 14302 if (h < 1) h = 1; 14303 XResizeWindow(display, window, w, h); 14304 14305 // calling this now to avoid waiting for the server to 14306 // acknowledge the resize; draws without returning to the 14307 // event loop will thus actually work. the server's event 14308 // btw might overrule this and resize it again 14309 recordX11Resize(display, this, w, h); 14310 14311 // FIXME: do we need to set this as the opengl context to do the glViewport change? 14312 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 14313 } 14314 14315 void moveResize (int x, int y, int w, int h) { 14316 if (w < 1) w = 1; 14317 if (h < 1) h = 1; 14318 XMoveResizeWindow(display, window, x, y, w, h); 14319 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 14320 } 14321 14322 void hideCursor () { 14323 if (curHidden++ == 0) { 14324 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 14325 static const(char)[1] cmbmp = 0; 14326 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 14327 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 14328 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 14329 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 14330 XFreePixmap(display, pm); 14331 } 14332 XDefineCursor(display, window, blankCurPtr); 14333 } 14334 } 14335 14336 void showCursor () { 14337 if (--curHidden == 0) XUndefineCursor(display, window); 14338 } 14339 14340 void warpMouse (int x, int y) { 14341 // here i will send dummy "ignore next mouse motion" event, 14342 // 'cause `XWarpPointer()` sends synthesised mouse motion, 14343 // and we don't need to report it to the user (as warping is 14344 // used when the user needs movement deltas). 14345 //XClientMessageEvent xclient; 14346 XEvent e; 14347 e.xclient.type = EventType.ClientMessage; 14348 e.xclient.window = window; 14349 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14350 e.xclient.format = 32; 14351 e.xclient.data.l[0] = 0; 14352 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 14353 //{ 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]); } 14354 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14355 // now warp pointer... 14356 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 14357 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 14358 // ...and flush 14359 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 14360 XFlush(display); 14361 } 14362 14363 void sendDummyEvent () { 14364 // here i will send dummy event to ping event queue 14365 XEvent e; 14366 e.xclient.type = EventType.ClientMessage; 14367 e.xclient.window = window; 14368 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 14369 e.xclient.format = 32; 14370 e.xclient.data.l[0] = 0; 14371 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 14372 XFlush(display); 14373 } 14374 14375 void setTitle(string title) { 14376 if (title.ptr is null) title = ""; 14377 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14378 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14379 XTextProperty windowName; 14380 windowName.value = title.ptr; 14381 windowName.encoding = XA_UTF8; //XA_STRING; 14382 windowName.format = 8; 14383 windowName.nitems = cast(uint)title.length; 14384 XSetWMName(display, window, &windowName); 14385 char[1024] namebuf = 0; 14386 auto maxlen = namebuf.length-1; 14387 if (maxlen > title.length) maxlen = title.length; 14388 namebuf[0..maxlen] = title[0..maxlen]; 14389 XStoreName(display, window, namebuf.ptr); 14390 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 14391 flushGui(); // without this OpenGL windows has a LONG delay before changing title 14392 } 14393 14394 string[] getTitles() { 14395 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 14396 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 14397 XTextProperty textProp; 14398 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 14399 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 14400 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 14401 } else 14402 return []; 14403 } else 14404 return null; 14405 } 14406 14407 string getTitle() { 14408 auto titles = getTitles(); 14409 return titles.length ? titles[0] : null; 14410 } 14411 14412 void setMinSize (int minwidth, int minheight) { 14413 import core.stdc.config : c_long; 14414 if (minwidth < 1) minwidth = 1; 14415 if (minheight < 1) minheight = 1; 14416 XSizeHints sh; 14417 c_long spr; 14418 XGetWMNormalHints(display, window, &sh, &spr); 14419 sh.min_width = minwidth; 14420 sh.min_height = minheight; 14421 sh.flags |= PMinSize; 14422 XSetWMNormalHints(display, window, &sh); 14423 flushGui(); 14424 } 14425 14426 void setMaxSize (int maxwidth, int maxheight) { 14427 import core.stdc.config : c_long; 14428 if (maxwidth < 1) maxwidth = 1; 14429 if (maxheight < 1) maxheight = 1; 14430 XSizeHints sh; 14431 c_long spr; 14432 XGetWMNormalHints(display, window, &sh, &spr); 14433 sh.max_width = maxwidth; 14434 sh.max_height = maxheight; 14435 sh.flags |= PMaxSize; 14436 XSetWMNormalHints(display, window, &sh); 14437 flushGui(); 14438 } 14439 14440 void setResizeGranularity (int granx, int grany) { 14441 import core.stdc.config : c_long; 14442 if (granx < 1) granx = 1; 14443 if (grany < 1) grany = 1; 14444 XSizeHints sh; 14445 c_long spr; 14446 XGetWMNormalHints(display, window, &sh, &spr); 14447 sh.width_inc = granx; 14448 sh.height_inc = grany; 14449 sh.flags |= PResizeInc; 14450 XSetWMNormalHints(display, window, &sh); 14451 flushGui(); 14452 } 14453 14454 void setOpacity (uint opacity) { 14455 arch_ulong o = opacity; 14456 if (opacity == uint.max) 14457 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 14458 else 14459 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 14460 XA_CARDINAL, 32, PropModeReplace, &o, 1); 14461 } 14462 14463 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 14464 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 14465 display = XDisplayConnection.get(); 14466 auto screen = DefaultScreen(display); 14467 14468 bool overrideRedirect = false; 14469 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification)// || windowType == WindowTypes.nestedChild) 14470 overrideRedirect = true; 14471 14472 version(without_opengl) {} 14473 else { 14474 if(opengl == OpenGlOptions.yes) { 14475 GLXFBConfig fbconf = null; 14476 XVisualInfo* vi = null; 14477 bool useLegacy = false; 14478 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 14479 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 14480 int[23] visualAttribs = [ 14481 GLX_X_RENDERABLE , 1/*True*/, 14482 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 14483 GLX_RENDER_TYPE , GLX_RGBA_BIT, 14484 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 14485 GLX_RED_SIZE , 8, 14486 GLX_GREEN_SIZE , 8, 14487 GLX_BLUE_SIZE , 8, 14488 GLX_ALPHA_SIZE , 8, 14489 GLX_DEPTH_SIZE , 24, 14490 GLX_STENCIL_SIZE , 8, 14491 GLX_DOUBLEBUFFER , 1/*True*/, 14492 0/*None*/, 14493 ]; 14494 int fbcount; 14495 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 14496 if (fbcount == 0) { 14497 useLegacy = true; // try to do at least something 14498 } else { 14499 // pick the FB config/visual with the most samples per pixel 14500 int bestidx = -1, bestns = -1; 14501 foreach (int fbi; 0..fbcount) { 14502 int sb, samples; 14503 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 14504 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 14505 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 14506 } 14507 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 14508 fbconf = fbc[bestidx]; 14509 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 14510 XFree(fbc); 14511 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 14512 } 14513 } 14514 if (vi is null || useLegacy) { 14515 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 14516 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 14517 useLegacy = true; 14518 } 14519 if (vi is null) throw new Exception("no open gl visual found"); 14520 14521 XSetWindowAttributes swa; 14522 auto root = RootWindow(display, screen); 14523 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 14524 14525 swa.override_redirect = overrideRedirect; 14526 14527 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14528 0, 0, width, height, 14529 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap | CWOverrideRedirect, &swa); 14530 14531 // now try to use `glXCreateContextAttribsARB()` if it's here 14532 if (!useLegacy) { 14533 // request fairly advanced context, even with stencil buffer! 14534 int[9] contextAttribs = [ 14535 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 14536 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 14537 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 14538 // for modern context, set "forward compatibility" flag too 14539 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 14540 0/*None*/, 14541 ]; 14542 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 14543 if (glc is null && sdpyOpenGLContextAllowFallback) { 14544 sdpyOpenGLContextVersion = 0; 14545 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14546 } 14547 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 14548 } else { 14549 // fallback to old GLX call 14550 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 14551 sdpyOpenGLContextVersion = 0; 14552 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 14553 } 14554 } 14555 // sync to ensure any errors generated are processed 14556 XSync(display, 0/*False*/); 14557 //{ import core.stdc.stdio; printf("ogl is here\n"); } 14558 if(glc is null) 14559 throw new Exception("glc"); 14560 } 14561 } 14562 14563 if(opengl == OpenGlOptions.no) { 14564 14565 XSetWindowAttributes swa; 14566 swa.background_pixel = WhitePixel(display, screen); 14567 swa.border_pixel = BlackPixel(display, screen); 14568 swa.override_redirect = overrideRedirect; 14569 auto root = RootWindow(display, screen); 14570 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 14571 14572 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 14573 0, 0, width, height, 14574 // I'm commenting that CWBackPixel thing just because it actually causes flicker for no apparent benefit. 14575 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap /*| CWBackPixel*/ | CWBorderPixel | CWOverrideRedirect, &swa); 14576 14577 14578 14579 /* 14580 window = XCreateSimpleWindow( 14581 display, 14582 parent is null ? RootWindow(display, screen) : parent.impl.window, 14583 0, 0, // x, y 14584 width, height, 14585 1, // border width 14586 BlackPixel(display, screen), // border 14587 WhitePixel(display, screen)); // background 14588 */ 14589 14590 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 14591 bufferw = width; 14592 bufferh = height; 14593 14594 gc = DefaultGC(display, screen); 14595 14596 // clear out the buffer to get us started... 14597 XSetForeground(display, gc, WhitePixel(display, screen)); 14598 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 14599 XSetForeground(display, gc, BlackPixel(display, screen)); 14600 } 14601 14602 // input context 14603 //TODO: create this only for top-level windows, and reuse that? 14604 if (XDisplayConnection.xim !is null) { 14605 xic = XCreateIC(XDisplayConnection.xim, 14606 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 14607 /*XNClientWindow*/"clientWindow".ptr, window, 14608 /*XNFocusWindow*/"focusWindow".ptr, window, 14609 null); 14610 if (xic is null) { 14611 import core.stdc.stdio : stderr, fprintf; 14612 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 14613 } 14614 } 14615 14616 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 14617 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 14618 // window class 14619 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 14620 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 14621 XClassHint klass; 14622 XWMHints wh; 14623 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14624 wh.input = true; 14625 wh.flags |= InputHint; 14626 } 14627 XSizeHints size; 14628 klass.res_name = sdpyWindowClassStr; 14629 klass.res_class = sdpyWindowClassStr; 14630 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 14631 } 14632 14633 setTitle(title); 14634 SimpleWindow.nativeMapping[window] = this; 14635 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 14636 14637 // This gives our window a close button 14638 if (windowType != WindowTypes.eventOnly) { 14639 Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 14640 int useAtoms; 14641 if(this.customizationFlags & WindowFlags.managesChildWindowFocus) { 14642 useAtoms = 2; 14643 } else { 14644 useAtoms = 1; 14645 } 14646 assert(useAtoms <= atoms.length); 14647 XSetWMProtocols(display, window, atoms.ptr, useAtoms); 14648 } 14649 14650 // FIXME: windowType and customizationFlags 14651 Atom[8] wsatoms; // here, due to goto 14652 int wmsacount = 0; // here, due to goto 14653 14654 try 14655 final switch(windowType) { 14656 case WindowTypes.normal: 14657 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14658 break; 14659 case WindowTypes.undecorated: 14660 motifHideDecorations(); 14661 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 14662 break; 14663 case WindowTypes.eventOnly: 14664 _hidden = true; 14665 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 14666 goto hiddenWindow; 14667 //break; 14668 case WindowTypes.nestedChild: 14669 // handled in XCreateWindow calls 14670 break; 14671 14672 case WindowTypes.dropdownMenu: 14673 motifHideDecorations(); 14674 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 14675 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14676 break; 14677 case WindowTypes.popupMenu: 14678 motifHideDecorations(); 14679 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 14680 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14681 break; 14682 case WindowTypes.notification: 14683 motifHideDecorations(); 14684 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 14685 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 14686 break; 14687 /+ 14688 case WindowTypes.menu: 14689 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14690 motifHideDecorations(); 14691 break; 14692 case WindowTypes.desktop: 14693 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 14694 break; 14695 case WindowTypes.dock: 14696 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 14697 break; 14698 case WindowTypes.toolbar: 14699 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 14700 break; 14701 case WindowTypes.menu: 14702 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 14703 break; 14704 case WindowTypes.utility: 14705 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 14706 break; 14707 case WindowTypes.splash: 14708 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 14709 break; 14710 case WindowTypes.dialog: 14711 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 14712 break; 14713 case WindowTypes.tooltip: 14714 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 14715 break; 14716 case WindowTypes.notification: 14717 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 14718 break; 14719 case WindowTypes.combo: 14720 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 14721 break; 14722 case WindowTypes.dnd: 14723 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 14724 break; 14725 +/ 14726 } 14727 catch(Exception e) { 14728 // XInternAtom failed, prolly a WM 14729 // that doesn't support these things 14730 } 14731 14732 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 14733 // the two following flags may be ignored by WM 14734 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 14735 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 14736 14737 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 14738 14739 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 14740 14741 // What would be ideal here is if they only were 14742 // selected if there was actually an event handler 14743 // for them... 14744 14745 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 14746 14747 hiddenWindow: 14748 14749 // set the pid property for lookup later by window managers 14750 // a standard convenience 14751 import core.sys.posix.unistd; 14752 arch_ulong pid = getpid(); 14753 14754 XChangeProperty( 14755 display, 14756 impl.window, 14757 GetAtom!("_NET_WM_PID", true)(display), 14758 XA_CARDINAL, 14759 32 /* bits */, 14760 0 /*PropModeReplace*/, 14761 &pid, 14762 1); 14763 14764 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 14765 if(parent is null) assert(0); 14766 XChangeProperty( 14767 display, 14768 impl.window, 14769 GetAtom!("WM_TRANSIENT_FOR", true)(display), 14770 XA_WINDOW, 14771 32 /* bits */, 14772 0 /*PropModeReplace*/, 14773 &parent.impl.window, 14774 1); 14775 14776 } 14777 14778 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 14779 XMapWindow(display, window); 14780 } else { 14781 _hidden = true; 14782 } 14783 } 14784 14785 void selectDefaultInput(bool forceIncludeMouseMotion) { 14786 auto mask = EventMask.ExposureMask | 14787 EventMask.KeyPressMask | 14788 EventMask.KeyReleaseMask | 14789 EventMask.PropertyChangeMask | 14790 EventMask.FocusChangeMask | 14791 EventMask.StructureNotifyMask | 14792 EventMask.VisibilityChangeMask 14793 | EventMask.ButtonPressMask 14794 | EventMask.ButtonReleaseMask 14795 ; 14796 14797 // xshm is our shortcut for local connections 14798 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 14799 mask |= EventMask.PointerMotionMask; 14800 else 14801 mask |= EventMask.ButtonMotionMask; 14802 14803 XSelectInput(display, window, mask); 14804 } 14805 14806 14807 void setNetWMWindowType(Atom type) { 14808 Atom[2] atoms; 14809 14810 atoms[0] = type; 14811 // generic fallback 14812 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 14813 14814 XChangeProperty( 14815 display, 14816 impl.window, 14817 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 14818 XA_ATOM, 14819 32 /* bits */, 14820 0 /*PropModeReplace*/, 14821 atoms.ptr, 14822 cast(int) atoms.length); 14823 } 14824 14825 void motifHideDecorations(bool hide = true) { 14826 MwmHints hints; 14827 hints.flags = MWM_HINTS_DECORATIONS; 14828 hints.decorations = hide ? 0 : 1; 14829 14830 XChangeProperty( 14831 display, 14832 impl.window, 14833 GetAtom!"_MOTIF_WM_HINTS"(display), 14834 GetAtom!"_MOTIF_WM_HINTS"(display), 14835 32 /* bits */, 14836 0 /*PropModeReplace*/, 14837 &hints, 14838 hints.sizeof / 4); 14839 } 14840 14841 /*k8: unused 14842 void createOpenGlContext() { 14843 14844 } 14845 */ 14846 14847 void closeWindow() { 14848 // I can't close this or a child window closing will 14849 // break events for everyone. So I'm just leaking it right 14850 // now and that is probably perfectly fine... 14851 version(none) 14852 if (customEventFDRead != -1) { 14853 import core.sys.posix.unistd : close; 14854 auto same = customEventFDRead == customEventFDWrite; 14855 14856 close(customEventFDRead); 14857 if(!same) 14858 close(customEventFDWrite); 14859 customEventFDRead = -1; 14860 customEventFDWrite = -1; 14861 } 14862 if(buffer) 14863 XFreePixmap(display, buffer); 14864 bufferw = bufferh = 0; 14865 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 14866 XDestroyWindow(display, window); 14867 XFlush(display); 14868 } 14869 14870 void dispose() { 14871 } 14872 14873 bool destroyed = false; 14874 } 14875 14876 bool insideXEventLoop; 14877 } 14878 14879 version(X11) { 14880 14881 int mouseDoubleClickTimeout = 350; /// Double click timeout. X only, you probably shouldn't change this. 14882 14883 private class ResizeEvent { 14884 int width, height; 14885 } 14886 14887 void recordX11ResizeAsync(Display* display, SimpleWindow win, int width, int height) { 14888 if(win.pendingResizeEvent is null) { 14889 win.pendingResizeEvent = new ResizeEvent(); 14890 win.addEventListener((ResizeEvent re) { 14891 recordX11Resize(XDisplayConnection.get, win, re.width, re.height); 14892 }); 14893 } 14894 win.pendingResizeEvent.width = width; 14895 win.pendingResizeEvent.height = height; 14896 if(!win.eventQueued!ResizeEvent) { 14897 win.postEvent(win.pendingResizeEvent); 14898 } 14899 } 14900 14901 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 14902 if(width != win.width || height != win.height) { 14903 win._width = width; 14904 win._height = height; 14905 14906 if(win.openglMode == OpenGlOptions.no) { 14907 // FIXME: could this be more efficient? 14908 14909 if (win.bufferw < width || win.bufferh < height) { 14910 //{ 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); } 14911 // grow the internal buffer to match the window... 14912 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 14913 { 14914 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 14915 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 14916 scope(exit) XFreeGC(win.display, xgc); 14917 XSetClipMask(win.display, xgc, None); 14918 XSetForeground(win.display, xgc, 0); 14919 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 14920 } 14921 XCopyArea(display, 14922 cast(Drawable) win.buffer, 14923 cast(Drawable) newPixmap, 14924 win.gc, 0, 0, 14925 win.bufferw < width ? win.bufferw : win.width, 14926 win.bufferh < height ? win.bufferh : win.height, 14927 0, 0); 14928 14929 XFreePixmap(display, win.buffer); 14930 win.buffer = newPixmap; 14931 win.bufferw = width; 14932 win.bufferh = height; 14933 } 14934 14935 // clear unused parts of the buffer 14936 if (win.bufferw > width || win.bufferh > height) { 14937 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 14938 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 14939 scope(exit) XFreeGC(win.display, xgc); 14940 XSetClipMask(win.display, xgc, None); 14941 XSetForeground(win.display, xgc, 0); 14942 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 14943 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 14944 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 14945 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 14946 } 14947 14948 } 14949 14950 version(without_opengl) {} else 14951 if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) { 14952 glViewport(0, 0, width, height); 14953 } 14954 14955 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 14956 14957 if(win.windowResized !is null) { 14958 XUnlockDisplay(display); 14959 scope(exit) XLockDisplay(display); 14960 win.windowResized(width, height); 14961 } 14962 } 14963 } 14964 14965 14966 /// Platform-specific, you might use it when doing a custom event loop. 14967 bool doXNextEvent(Display* display) { 14968 bool done; 14969 XEvent e; 14970 XNextEvent(display, &e); 14971 version(sddddd) { 14972 import std.stdio, std.conv : to; 14973 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 14974 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 14975 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 14976 } 14977 } 14978 14979 // filter out compose events 14980 if (XFilterEvent(&e, None)) { 14981 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 14982 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 14983 return false; 14984 } 14985 // process keyboard mapping changes 14986 if (e.type == EventType.KeymapNotify) { 14987 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 14988 XRefreshKeyboardMapping(&e.xmapping); 14989 return false; 14990 } 14991 14992 version(with_eventloop) 14993 import arsd.eventloop; 14994 14995 if(SimpleWindow.handleNativeGlobalEvent !is null) { 14996 // see windows impl's comments 14997 XUnlockDisplay(display); 14998 scope(exit) XLockDisplay(display); 14999 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 15000 if(ret == 0) 15001 return done; 15002 } 15003 15004 15005 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 15006 if(win.getNativeEventHandler !is null) { 15007 XUnlockDisplay(display); 15008 scope(exit) XLockDisplay(display); 15009 auto ret = win.getNativeEventHandler()(e); 15010 if(ret == 0) 15011 return done; 15012 } 15013 } 15014 15015 if(xrrEventBase != -1 && e.type == xrrEventBase + RRScreenChangeNotify) { 15016 if(auto win = e.xany.window in SimpleWindow.nativeMapping) { 15017 // we get this because of the RRScreenChangeNotifyMask 15018 15019 // this isn't actually an ideal way to do it since it wastes time 15020 // but meh it is simple and it works. 15021 win.actualDpiLoadAttempted = false; 15022 SimpleWindow.xRandrInfoLoadAttemped = false; 15023 win.updateActualDpi(); // trigger a reload 15024 } 15025 } 15026 15027 switch(e.type) { 15028 case EventType.SelectionClear: 15029 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 15030 // FIXME so it is supposed to finish any in progress transfers... but idk... 15031 //import std.stdio; writeln("SelectionClear"); 15032 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 15033 } 15034 break; 15035 case EventType.SelectionRequest: 15036 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 15037 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 15038 // import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 15039 XUnlockDisplay(display); 15040 scope(exit) XLockDisplay(display); 15041 (*ssh).handleRequest(e); 15042 } 15043 break; 15044 case EventType.PropertyNotify: 15045 // import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 15046 15047 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 15048 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 15049 ssh.sendMoreIncr(&e.xproperty); 15050 } 15051 15052 15053 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 15054 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15055 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 15056 Atom target; 15057 int format; 15058 arch_ulong bytesafter, length; 15059 void* value; 15060 15061 ubyte[] s; 15062 Atom targetToKeep; 15063 15064 XGetWindowProperty( 15065 e.xproperty.display, 15066 e.xproperty.window, 15067 e.xproperty.atom, 15068 0, 15069 100000 /* length */, 15070 true, /* erase it to signal we got it and want more */ 15071 0 /*AnyPropertyType*/, 15072 &target, &format, &length, &bytesafter, &value); 15073 15074 if(!targetToKeep) 15075 targetToKeep = target; 15076 15077 auto id = (cast(ubyte*) value)[0 .. length]; 15078 15079 handler.handleIncrData(targetToKeep, id); 15080 15081 XFree(value); 15082 } 15083 } 15084 break; 15085 case EventType.SelectionNotify: 15086 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 15087 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 15088 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 15089 XUnlockDisplay(display); 15090 scope(exit) XLockDisplay(display); 15091 handler.handleData(None, null); 15092 } else { 15093 Atom target; 15094 int format; 15095 arch_ulong bytesafter, length; 15096 void* value; 15097 XGetWindowProperty( 15098 e.xselection.display, 15099 e.xselection.requestor, 15100 e.xselection.property, 15101 0, 15102 100000 /* length */, 15103 //false, /* don't erase it */ 15104 true, /* do erase it lol */ 15105 0 /*AnyPropertyType*/, 15106 &target, &format, &length, &bytesafter, &value); 15107 15108 // FIXME: I don't have to copy it now since it is in char[] instead of string 15109 15110 { 15111 XUnlockDisplay(display); 15112 scope(exit) XLockDisplay(display); 15113 15114 if(target == XA_ATOM) { 15115 // initial request, see what they are able to work with and request the best one 15116 // we can handle, if available 15117 15118 Atom[] answer = (cast(Atom*) value)[0 .. length]; 15119 Atom best = handler.findBestFormat(answer); 15120 15121 /+ 15122 writeln("got ", answer); 15123 foreach(a; answer) 15124 printf("%s\n", XGetAtomName(display, a)); 15125 writeln("best ", best); 15126 +/ 15127 15128 if(best != None) { 15129 // actually request the best format 15130 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 15131 } 15132 } else if(target == GetAtom!"INCR"(display)) { 15133 // incremental 15134 15135 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 15136 15137 // signal the sending program that we see 15138 // the incr and are ready to receive more. 15139 XDeleteProperty( 15140 e.xselection.display, 15141 e.xselection.requestor, 15142 e.xselection.property); 15143 } else { 15144 // unsupported type... maybe, forward 15145 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 15146 } 15147 } 15148 XFree(value); 15149 /* 15150 XDeleteProperty( 15151 e.xselection.display, 15152 e.xselection.requestor, 15153 e.xselection.property); 15154 */ 15155 } 15156 } 15157 break; 15158 case EventType.ConfigureNotify: 15159 auto event = e.xconfigure; 15160 if(auto win = event.window in SimpleWindow.nativeMapping) { 15161 //version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); } 15162 15163 /+ 15164 The ICCCM says window managers must send a synthetic event when the window 15165 is moved but NOT when it is resized. In the resize case, an event is sent 15166 with position (0, 0) which can be wrong and break the dpi calculations. 15167 15168 So we only consider the synthetic events from the WM and otherwise 15169 need to wait for some other event to get the position which... sucks. 15170 15171 I'd rather not have windows changing their layout on mouse motion after 15172 switching monitors... might be forced to but for now just ignoring it. 15173 15174 Easiest way to switch monitors without sending a size position is by 15175 maximize or fullscreen in a setup like mine, but on most setups those 15176 work on the monitor it is already living on, so it should be ok most the 15177 time. 15178 +/ 15179 if(event.send_event) { 15180 win.screenPositionKnown = true; 15181 win.screenPositionX = event.x; 15182 win.screenPositionY = event.y; 15183 win.updateActualDpi(); 15184 } 15185 15186 recordX11ResizeAsync(display, *win, event.width, event.height); 15187 } 15188 break; 15189 case EventType.Expose: 15190 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 15191 // if it is closing from a popup menu, it can get 15192 // an Expose event right by the end and trigger a 15193 // BadDrawable error ... we'll just check 15194 // closed to handle that. 15195 if((*win).closed) break; 15196 if((*win).openglMode == OpenGlOptions.no) { 15197 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 15198 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 15199 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); 15200 } else { 15201 // need to redraw the scene somehow 15202 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 15203 XUnlockDisplay(display); 15204 scope(exit) XLockDisplay(display); 15205 version(without_opengl) {} else 15206 win.redrawOpenGlSceneSoon(); 15207 } 15208 } 15209 } 15210 break; 15211 case EventType.FocusIn: 15212 case EventType.FocusOut: 15213 15214 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15215 /+ 15216 15217 void info(string detail) { 15218 string s; 15219 import std.conv; 15220 import std.datetime; 15221 s ~= to!string(Clock.currTime); 15222 s ~= " "; 15223 s ~= e.type == EventType.FocusIn ? "in " : "out"; 15224 s ~= " "; 15225 s ~= win.windowType == WindowTypes.nestedChild ? "child " : "main "; 15226 s ~= e.xfocus.mode == NotifyModes.NotifyNormal ? " normal ": " grabbed "; 15227 s ~= detail; 15228 s ~= " "; 15229 15230 sdpyPrintDebugString(s); 15231 15232 } 15233 15234 switch(e.xfocus.detail) { 15235 case NotifyDetail.NotifyAncestor: info("Ancestor"); break; 15236 case NotifyDetail.NotifyVirtual: info("Virtual"); break; 15237 case NotifyDetail.NotifyInferior: info("Inferior"); break; 15238 case NotifyDetail.NotifyNonlinear: info("Nonlinear"); break; 15239 case NotifyDetail.NotifyNonlinearVirtual: info("nlinearvirtual"); break; 15240 case NotifyDetail.NotifyPointer: info("pointer"); break; 15241 case NotifyDetail.NotifyPointerRoot: info("pointerroot"); break; 15242 case NotifyDetail.NotifyDetailNone: info("none"); break; 15243 default: 15244 15245 } 15246 +/ 15247 15248 15249 if (win.xic !is null) { 15250 //{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); } 15251 if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic); 15252 } 15253 15254 if(e.xfocus.detail == NotifyDetail.NotifyPointer) 15255 break; // just ignore these they seem irrelevant 15256 15257 auto old = win._focused; 15258 win._focused = e.type == EventType.FocusIn; 15259 15260 // yes, we are losing the focus, but to our own child. that's actually kinda keeping it. 15261 if(e.type == EventType.FocusOut && e.xfocus.detail == NotifyDetail.NotifyInferior) 15262 win._focused = true; 15263 15264 if(win.demandingAttention) 15265 demandAttention(*win, false); 15266 15267 if(old != win._focused && win.onFocusChange) { 15268 XUnlockDisplay(display); 15269 scope(exit) XLockDisplay(display); 15270 win.onFocusChange(win._focused); 15271 } 15272 } 15273 break; 15274 case EventType.VisibilityNotify: 15275 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 15276 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 15277 if (win.visibilityChanged !is null) { 15278 XUnlockDisplay(display); 15279 scope(exit) XLockDisplay(display); 15280 win.visibilityChanged(false); 15281 } 15282 } else { 15283 if (win.visibilityChanged !is null) { 15284 XUnlockDisplay(display); 15285 scope(exit) XLockDisplay(display); 15286 win.visibilityChanged(true); 15287 } 15288 } 15289 } 15290 break; 15291 case EventType.ClientMessage: 15292 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 15293 // "ignore next mouse motion" event, increment ignore counter for teh window 15294 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15295 ++(*win).warpEventCount; 15296 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 15297 } else { 15298 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 15299 } 15300 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 15301 // user clicked the close button on the window manager 15302 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15303 XUnlockDisplay(display); 15304 scope(exit) XLockDisplay(display); 15305 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 15306 } 15307 15308 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 15309 //import std.stdio; writeln("HAPPENED"); 15310 // user clicked the close button on the window manager 15311 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15312 XUnlockDisplay(display); 15313 scope(exit) XLockDisplay(display); 15314 15315 auto setTo = *win; 15316 15317 if(win.setRequestedInputFocus !is null) { 15318 auto s = win.setRequestedInputFocus(); 15319 if(s !is null) 15320 setTo = s; 15321 } 15322 15323 assert(setTo !is null); 15324 15325 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 15326 15327 XSetInputFocus(display, setTo.impl.window, RevertToParent, e.xclient.data.l[1]); 15328 } 15329 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 15330 foreach(nai; NotificationAreaIcon.activeIcons) 15331 nai.newManager(); 15332 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 15333 15334 bool xDragWindow = true; 15335 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 15336 //XDefineCursor(display, xDragWindow.impl.window, 15337 //import std.stdio; writeln("XdndStatus ", e.xclient.data.l); 15338 } 15339 if(auto dh = win.dropHandler) { 15340 15341 static Atom[3] xFormatsBuffer; 15342 static Atom[] xFormats; 15343 15344 void resetXFormats() { 15345 xFormatsBuffer[] = 0; 15346 xFormats = xFormatsBuffer[]; 15347 } 15348 15349 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 15350 // on Windows it is supposed to return the effect you actually do FIXME 15351 15352 auto sourceWindow = e.xclient.data.l[0]; 15353 15354 xFormatsBuffer[0] = e.xclient.data.l[2]; 15355 xFormatsBuffer[1] = e.xclient.data.l[3]; 15356 xFormatsBuffer[2] = e.xclient.data.l[4]; 15357 15358 if(e.xclient.data.l[1] & 1) { 15359 // can just grab it all but like we don't necessarily need them... 15360 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 15361 } else { 15362 int len; 15363 foreach(fmt; xFormatsBuffer) 15364 if(fmt) len++; 15365 xFormats = xFormatsBuffer[0 .. len]; 15366 } 15367 15368 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 15369 15370 dh.dragEnter(&pkg); 15371 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 15372 15373 auto pack = e.xclient.data.l[2]; 15374 15375 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 15376 15377 15378 XClientMessageEvent xclient; 15379 15380 xclient.type = EventType.ClientMessage; 15381 xclient.window = e.xclient.data.l[0]; 15382 xclient.message_type = GetAtom!"XdndStatus"(display); 15383 xclient.format = 32; 15384 xclient.data.l[0] = win.impl.window; 15385 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 15386 auto r = result.consistentWithin; 15387 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 15388 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 15389 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 15390 15391 XSendEvent( 15392 display, 15393 e.xclient.data.l[0], 15394 false, 15395 EventMask.NoEventMask, 15396 cast(XEvent*) &xclient 15397 ); 15398 15399 15400 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 15401 //import std.stdio; writeln("XdndLeave"); 15402 // drop cancelled. 15403 // data.l[0] is the source window 15404 dh.dragLeave(); 15405 15406 resetXFormats(); 15407 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 15408 // drop happening, should fetch data, then send finished 15409 //import std.stdio; writeln("XdndDrop"); 15410 15411 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 15412 15413 dh.drop(&pkg); 15414 15415 resetXFormats(); 15416 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 15417 // import std.stdio; writeln("XdndFinished"); 15418 15419 dh.finish(); 15420 } 15421 15422 } 15423 } 15424 break; 15425 case EventType.MapNotify: 15426 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 15427 (*win)._visible = true; 15428 if (!(*win)._visibleForTheFirstTimeCalled) { 15429 (*win)._visibleForTheFirstTimeCalled = true; 15430 if ((*win).visibleForTheFirstTime !is null) { 15431 XUnlockDisplay(display); 15432 scope(exit) XLockDisplay(display); 15433 version(without_opengl) {} else { 15434 if((*win).openglMode == OpenGlOptions.yes) { 15435 (*win).setAsCurrentOpenGlContextNT(); 15436 glViewport(0, 0, (*win).width, (*win).height); 15437 } 15438 } 15439 (*win).visibleForTheFirstTime(); 15440 } 15441 } 15442 if ((*win).visibilityChanged !is null) { 15443 XUnlockDisplay(display); 15444 scope(exit) XLockDisplay(display); 15445 (*win).visibilityChanged(true); 15446 } 15447 } 15448 break; 15449 case EventType.UnmapNotify: 15450 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 15451 win._visible = false; 15452 if (win.visibilityChanged !is null) { 15453 XUnlockDisplay(display); 15454 scope(exit) XLockDisplay(display); 15455 win.visibilityChanged(false); 15456 } 15457 } 15458 break; 15459 case EventType.DestroyNotify: 15460 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 15461 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 15462 win._closed = true; // just in case 15463 win.destroyed = true; 15464 if (win.xic !is null) { 15465 XDestroyIC(win.xic); 15466 win.xic = null; // just in calse 15467 } 15468 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 15469 bool anyImportant = false; 15470 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 15471 if(w.beingOpenKeepsAppOpen) { 15472 anyImportant = true; 15473 break; 15474 } 15475 if(!anyImportant) { 15476 EventLoop.quitApplication(); 15477 done = true; 15478 } 15479 } 15480 auto window = e.xdestroywindow.window; 15481 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 15482 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 15483 15484 version(with_eventloop) { 15485 if(done) exit(); 15486 } 15487 break; 15488 15489 case EventType.MotionNotify: 15490 MouseEvent mouse; 15491 auto event = e.xmotion; 15492 15493 mouse.type = MouseEventType.motion; 15494 mouse.x = event.x; 15495 mouse.y = event.y; 15496 mouse.modifierState = event.state; 15497 15498 mouse.timestamp = event.time; 15499 15500 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 15501 mouse.window = *win; 15502 if (win.warpEventCount > 0) { 15503 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 15504 --(*win).warpEventCount; 15505 (*win).mdx(mouse); // so deltas will be correctly updated 15506 } else { 15507 win.warpEventCount = 0; // just in case 15508 (*win).mdx(mouse); 15509 if((*win).handleMouseEvent) { 15510 XUnlockDisplay(display); 15511 scope(exit) XLockDisplay(display); 15512 (*win).handleMouseEvent(mouse); 15513 } 15514 } 15515 } 15516 15517 version(with_eventloop) 15518 send(mouse); 15519 break; 15520 case EventType.ButtonPress: 15521 case EventType.ButtonRelease: 15522 MouseEvent mouse; 15523 auto event = e.xbutton; 15524 15525 mouse.timestamp = event.time; 15526 15527 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 15528 mouse.x = event.x; 15529 mouse.y = event.y; 15530 15531 static Time lastMouseDownTime = 0; 15532 15533 mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 15534 if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time; 15535 15536 switch(event.button) { 15537 case 1: mouse.button = MouseButton.left; break; // left 15538 case 2: mouse.button = MouseButton.middle; break; // middle 15539 case 3: mouse.button = MouseButton.right; break; // right 15540 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 15541 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 15542 case 6: break; // idk 15543 case 7: break; // idk 15544 case 8: mouse.button = MouseButton.backButton; break; 15545 case 9: mouse.button = MouseButton.forwardButton; break; 15546 default: 15547 } 15548 15549 // FIXME: double check this 15550 mouse.modifierState = event.state; 15551 15552 //mouse.modifierState = event.detail; 15553 15554 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 15555 mouse.window = *win; 15556 (*win).mdx(mouse); 15557 if((*win).handleMouseEvent) { 15558 XUnlockDisplay(display); 15559 scope(exit) XLockDisplay(display); 15560 (*win).handleMouseEvent(mouse); 15561 } 15562 } 15563 version(with_eventloop) 15564 send(mouse); 15565 break; 15566 15567 case EventType.KeyPress: 15568 case EventType.KeyRelease: 15569 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 15570 KeyEvent ke; 15571 ke.pressed = e.type == EventType.KeyPress; 15572 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 15573 15574 auto sym = XKeycodeToKeysym( 15575 XDisplayConnection.get(), 15576 e.xkey.keycode, 15577 0); 15578 15579 ke.key = cast(Key) sym;//e.xkey.keycode; 15580 15581 ke.modifierState = e.xkey.state; 15582 15583 // import std.stdio; writefln("%x", sym); 15584 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 15585 int charbuflen = 0; // return value of XwcLookupString 15586 if (ke.pressed) { 15587 auto win = e.xkey.window in SimpleWindow.nativeMapping; 15588 if (win !is null && win.xic !is null) { 15589 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 15590 Status status; 15591 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 15592 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 15593 } else { 15594 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 15595 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 15596 char[16] buffer; 15597 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 15598 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 15599 } 15600 } 15601 15602 // if there's no char, subst one 15603 if (charbuflen == 0) { 15604 switch (sym) { 15605 case 0xff09: charbuf[charbuflen++] = '\t'; break; 15606 case 0xff8d: // keypad enter 15607 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 15608 default : // ignore 15609 } 15610 } 15611 15612 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 15613 ke.window = *win; 15614 15615 15616 if(win.inputProxy) 15617 win = &win.inputProxy; 15618 15619 // char events are separate since they are on Windows too 15620 // also, xcompose can generate long char sequences 15621 // don't send char events if Meta and/or Hyper is pressed 15622 // TODO: ctrl+char should only send control chars; not yet 15623 if ((e.xkey.state&ModifierState.ctrl) != 0) { 15624 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 15625 } 15626 15627 dchar[32] charsComingBuffer; 15628 int charsComingPosition; 15629 dchar[] charsComing = charsComingBuffer[]; 15630 15631 if (ke.pressed && charbuflen > 0) { 15632 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 15633 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 15634 if(charsComingPosition >= charsComing.length) 15635 charsComing.length = charsComingPosition + 8; 15636 15637 charsComing[charsComingPosition++] = ch; 15638 } 15639 15640 charsComing = charsComing[0 .. charsComingPosition]; 15641 } else { 15642 charsComing = null; 15643 } 15644 15645 ke.charsPossible = charsComing; 15646 15647 if (win.handleKeyEvent) { 15648 XUnlockDisplay(display); 15649 scope(exit) XLockDisplay(display); 15650 win.handleKeyEvent(ke); 15651 } 15652 15653 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 15654 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 15655 XUnlockDisplay(display); 15656 scope(exit) XLockDisplay(display); 15657 foreach(ch; charsComing) 15658 win.handleCharEvent(ch); 15659 } 15660 } 15661 15662 version(with_eventloop) 15663 send(ke); 15664 break; 15665 default: 15666 } 15667 15668 return done; 15669 } 15670 } 15671 15672 /* *************************************** */ 15673 /* Done with simpledisplay stuff */ 15674 /* *************************************** */ 15675 15676 // Necessary C library bindings follow 15677 version(Windows) {} else 15678 version(X11) { 15679 15680 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 15681 15682 // X11 bindings needed here 15683 /* 15684 A little of this is from the bindings project on 15685 D Source and some of it is copy/paste from the C 15686 header. 15687 15688 The DSource listing consistently used D's long 15689 where C used long. That's wrong - C long is 32 bit, so 15690 it should be int in D. I changed that here. 15691 15692 Note: 15693 This isn't complete, just took what I needed for myself. 15694 */ 15695 15696 import core.stdc.stddef : wchar_t; 15697 15698 interface XLib { 15699 extern(C) nothrow @nogc { 15700 char* XResourceManagerString(Display*); 15701 void XrmInitialize(); 15702 XrmDatabase XrmGetStringDatabase(char* data); 15703 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 15704 15705 Cursor XCreateFontCursor(Display*, uint shape); 15706 int XDefineCursor(Display* display, Window w, Cursor cursor); 15707 int XUndefineCursor(Display* display, Window w); 15708 15709 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 15710 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 15711 int XFreeCursor(Display* display, Cursor cursor); 15712 15713 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 15714 15715 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 15716 15717 char *XKeysymToString(KeySym keysym); 15718 KeySym XKeycodeToKeysym( 15719 Display* /* display */, 15720 KeyCode /* keycode */, 15721 int /* index */ 15722 ); 15723 15724 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 15725 15726 int XFree(void*); 15727 int XDeleteProperty(Display *display, Window w, Atom property); 15728 15729 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements); 15730 15731 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 15732 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 15733 *actual_type_return, int *actual_format_return, arch_ulong 15734 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 15735 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 15736 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 15737 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 15738 15739 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 15740 15741 Window XGetSelectionOwner(Display *display, Atom selection); 15742 15743 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 15744 15745 char** XListFonts(Display*, const char*, int, int*); 15746 void XFreeFontNames(char**); 15747 15748 Display* XOpenDisplay(const char*); 15749 int XCloseDisplay(Display*); 15750 15751 int XSynchronize(Display*, bool); 15752 15753 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 15754 15755 Bool XSupportsLocale(); 15756 char* XSetLocaleModifiers(const(char)* modifier_list); 15757 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 15758 Status XCloseOM(XOM om); 15759 15760 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 15761 Status XCloseIM(XIM im); 15762 15763 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 15764 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 15765 Display* XDisplayOfIM(XIM im); 15766 char* XLocaleOfIM(XIM im); 15767 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 15768 void XDestroyIC(XIC ic); 15769 void XSetICFocus(XIC ic); 15770 void XUnsetICFocus(XIC ic); 15771 //wchar_t* XwcResetIC(XIC ic); 15772 char* XmbResetIC(XIC ic); 15773 char* Xutf8ResetIC(XIC ic); 15774 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 15775 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 15776 XIM XIMOfIC(XIC ic); 15777 15778 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 15779 15780 15781 XFontStruct *XLoadQueryFont(Display *display, in char *name); 15782 int XFreeFont(Display *display, XFontStruct *font_struct); 15783 int XSetFont(Display* display, GC gc, Font font); 15784 int XTextWidth(XFontStruct*, in char*, int); 15785 15786 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 15787 int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n); 15788 15789 Window XCreateSimpleWindow( 15790 Display* /* display */, 15791 Window /* parent */, 15792 int /* x */, 15793 int /* y */, 15794 uint /* width */, 15795 uint /* height */, 15796 uint /* border_width */, 15797 uint /* border */, 15798 uint /* background */ 15799 ); 15800 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); 15801 15802 int XReparentWindow(Display*, Window, Window, int, int); 15803 int XClearWindow(Display*, Window); 15804 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 15805 int XMoveWindow(Display*, Window, int, int); 15806 int XResizeWindow(Display *display, Window w, uint width, uint height); 15807 15808 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 15809 15810 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 15811 15812 XImage *XCreateImage( 15813 Display* /* display */, 15814 Visual* /* visual */, 15815 uint /* depth */, 15816 int /* format */, 15817 int /* offset */, 15818 ubyte* /* data */, 15819 uint /* width */, 15820 uint /* height */, 15821 int /* bitmap_pad */, 15822 int /* bytes_per_line */ 15823 ); 15824 15825 Status XInitImage (XImage* image); 15826 15827 Atom XInternAtom( 15828 Display* /* display */, 15829 const char* /* atom_name */, 15830 Bool /* only_if_exists */ 15831 ); 15832 15833 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 15834 char* XGetAtomName(Display*, Atom); 15835 Status XGetAtomNames(Display*, Atom*, int count, char**); 15836 15837 int XPutImage( 15838 Display* /* display */, 15839 Drawable /* d */, 15840 GC /* gc */, 15841 XImage* /* image */, 15842 int /* src_x */, 15843 int /* src_y */, 15844 int /* dest_x */, 15845 int /* dest_y */, 15846 uint /* width */, 15847 uint /* height */ 15848 ); 15849 15850 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 15851 15852 15853 int XDestroyWindow( 15854 Display* /* display */, 15855 Window /* w */ 15856 ); 15857 15858 int XDestroyImage(XImage*); 15859 15860 int XSelectInput( 15861 Display* /* display */, 15862 Window /* w */, 15863 EventMask /* event_mask */ 15864 ); 15865 15866 int XMapWindow( 15867 Display* /* display */, 15868 Window /* w */ 15869 ); 15870 15871 Status XIconifyWindow(Display*, Window, int); 15872 int XMapRaised(Display*, Window); 15873 int XMapSubwindows(Display*, Window); 15874 15875 int XNextEvent( 15876 Display* /* display */, 15877 XEvent* /* event_return */ 15878 ); 15879 15880 int XMaskEvent(Display*, arch_long, XEvent*); 15881 15882 Bool XFilterEvent(XEvent *event, Window window); 15883 int XRefreshKeyboardMapping(XMappingEvent *event_map); 15884 15885 Status XSetWMProtocols( 15886 Display* /* display */, 15887 Window /* w */, 15888 Atom* /* protocols */, 15889 int /* count */ 15890 ); 15891 15892 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 15893 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 15894 15895 15896 Status XInitThreads(); 15897 void XLockDisplay (Display* display); 15898 void XUnlockDisplay (Display* display); 15899 15900 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 15901 15902 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 15903 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 15904 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 15905 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 15906 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 15907 15908 15909 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 15910 int XDrawString(Display*, Drawable, GC, int, int, in char*, int); 15911 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 15912 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 15913 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 15914 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 15915 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 15916 int XDrawPoint(Display*, Drawable, GC, int, int); 15917 int XSetForeground(Display*, GC, uint); 15918 int XSetBackground(Display*, GC, uint); 15919 15920 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 15921 void XFreeFontSet(Display*, XFontSet); 15922 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int); 15923 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 15924 15925 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 15926 15927 15928 //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); 15929 15930 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 15931 int XSetFunction(Display*, GC, int); 15932 15933 GC XCreateGC(Display*, Drawable, uint, void*); 15934 int XCopyGC(Display*, GC, uint, GC); 15935 int XFreeGC(Display*, GC); 15936 15937 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 15938 bool XCheckMaskEvent(Display*, int, XEvent*); 15939 15940 int XPending(Display*); 15941 int XEventsQueued(Display* display, int mode); 15942 15943 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 15944 int XFreePixmap(Display*, Pixmap); 15945 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 15946 int XFlush(Display*); 15947 int XBell(Display*, int); 15948 int XSync(Display*, bool); 15949 15950 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 15951 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 15952 15953 int XGrabKeyboard(Display*, Window, Bool, int, int, Time); 15954 int XUngrabKeyboard(Display*, Time); 15955 15956 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 15957 15958 KeySym XStringToKeysym(const char *string); 15959 15960 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 15961 15962 Window XDefaultRootWindow(Display*); 15963 15964 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); 15965 15966 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 15967 15968 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 15969 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 15970 15971 Status XAllocColor(Display*, Colormap, XColor*); 15972 15973 int XWithdrawWindow(Display*, Window, int); 15974 int XUnmapWindow(Display*, Window); 15975 int XLowerWindow(Display*, Window); 15976 int XRaiseWindow(Display*, Window); 15977 15978 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); 15979 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); 15980 15981 int XGetInputFocus(Display*, Window*, int*); 15982 int XSetInputFocus(Display*, Window, int, Time); 15983 15984 XErrorHandler XSetErrorHandler(XErrorHandler); 15985 15986 int XGetErrorText(Display*, int, char*, int); 15987 15988 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 15989 15990 15991 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); 15992 int XUngrabPointer(Display *display, Time time); 15993 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 15994 15995 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 15996 15997 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 15998 int XSetClipMask(Display*, GC, Pixmap); 15999 int XSetClipOrigin(Display*, GC, int, int); 16000 16001 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 16002 16003 void XSetWMName(Display*, Window, XTextProperty*); 16004 Status XGetWMName(Display*, Window, XTextProperty*); 16005 int XStoreName(Display* display, Window w, const(char)* window_name); 16006 16007 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 16008 16009 } 16010 } 16011 16012 interface Xext { 16013 extern(C) nothrow @nogc { 16014 Status XShmAttach(Display*, XShmSegmentInfo*); 16015 Status XShmDetach(Display*, XShmSegmentInfo*); 16016 Status XShmPutImage( 16017 Display* /* dpy */, 16018 Drawable /* d */, 16019 GC /* gc */, 16020 XImage* /* image */, 16021 int /* src_x */, 16022 int /* src_y */, 16023 int /* dst_x */, 16024 int /* dst_y */, 16025 uint /* src_width */, 16026 uint /* src_height */, 16027 Bool /* send_event */ 16028 ); 16029 16030 Status XShmQueryExtension(Display*); 16031 16032 XImage *XShmCreateImage( 16033 Display* /* dpy */, 16034 Visual* /* visual */, 16035 uint /* depth */, 16036 int /* format */, 16037 char* /* data */, 16038 XShmSegmentInfo* /* shminfo */, 16039 uint /* width */, 16040 uint /* height */ 16041 ); 16042 16043 Pixmap XShmCreatePixmap( 16044 Display* /* dpy */, 16045 Drawable /* d */, 16046 char* /* data */, 16047 XShmSegmentInfo* /* shminfo */, 16048 uint /* width */, 16049 uint /* height */, 16050 uint /* depth */ 16051 ); 16052 16053 } 16054 } 16055 16056 // this requires -lXpm 16057 //int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 16058 16059 16060 mixin DynamicLoad!(XLib, "X11", 6, librariesSuccessfullyLoaded) xlib; 16061 mixin DynamicLoad!(Xext, "Xext", 6, librariesSuccessfullyLoaded) xext; 16062 shared static this() { 16063 xlib.loadDynamicLibrary(); 16064 xext.loadDynamicLibrary(); 16065 } 16066 16067 16068 extern(C) nothrow @nogc { 16069 16070 alias XrmDatabase = void*; 16071 struct XrmValue { 16072 uint size; 16073 void* addr; 16074 } 16075 16076 struct XVisualInfo { 16077 Visual* visual; 16078 VisualID visualid; 16079 int screen; 16080 uint depth; 16081 int c_class; 16082 c_ulong red_mask; 16083 c_ulong green_mask; 16084 c_ulong blue_mask; 16085 int colormap_size; 16086 int bits_per_rgb; 16087 } 16088 16089 enum VisualNoMask= 0x0; 16090 enum VisualIDMask= 0x1; 16091 enum VisualScreenMask=0x2; 16092 enum VisualDepthMask= 0x4; 16093 enum VisualClassMask= 0x8; 16094 enum VisualRedMaskMask=0x10; 16095 enum VisualGreenMaskMask=0x20; 16096 enum VisualBlueMaskMask=0x40; 16097 enum VisualColormapSizeMask=0x80; 16098 enum VisualBitsPerRGBMask=0x100; 16099 enum VisualAllMask= 0x1FF; 16100 16101 enum AnyKey = 0; 16102 enum AnyModifier = 1 << 15; 16103 16104 // XIM and other crap 16105 struct _XOM {} 16106 struct _XIM {} 16107 struct _XIC {} 16108 alias XOM = _XOM*; 16109 alias XIM = _XIM*; 16110 alias XIC = _XIC*; 16111 16112 alias XIMStyle = arch_ulong; 16113 enum : arch_ulong { 16114 XIMPreeditArea = 0x0001, 16115 XIMPreeditCallbacks = 0x0002, 16116 XIMPreeditPosition = 0x0004, 16117 XIMPreeditNothing = 0x0008, 16118 XIMPreeditNone = 0x0010, 16119 XIMStatusArea = 0x0100, 16120 XIMStatusCallbacks = 0x0200, 16121 XIMStatusNothing = 0x0400, 16122 XIMStatusNone = 0x0800, 16123 } 16124 16125 16126 /* X Shared Memory Extension functions */ 16127 //pragma(lib, "Xshm"); 16128 alias arch_ulong ShmSeg; 16129 struct XShmSegmentInfo { 16130 ShmSeg shmseg; 16131 int shmid; 16132 ubyte* shmaddr; 16133 Bool readOnly; 16134 } 16135 16136 // and the necessary OS functions 16137 int shmget(int, size_t, int); 16138 void* shmat(int, in void*, int); 16139 int shmdt(in void*); 16140 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 16141 16142 enum IPC_PRIVATE = 0; 16143 enum IPC_CREAT = 512; 16144 enum IPC_RMID = 0; 16145 16146 /* MIT-SHM end */ 16147 16148 16149 enum MappingType:int { 16150 MappingModifier =0, 16151 MappingKeyboard =1, 16152 MappingPointer =2 16153 } 16154 16155 /* ImageFormat -- PutImage, GetImage */ 16156 enum ImageFormat:int { 16157 XYBitmap =0, /* depth 1, XYFormat */ 16158 XYPixmap =1, /* depth == drawable depth */ 16159 ZPixmap =2 /* depth == drawable depth */ 16160 } 16161 16162 enum ModifierName:int { 16163 ShiftMapIndex =0, 16164 LockMapIndex =1, 16165 ControlMapIndex =2, 16166 Mod1MapIndex =3, 16167 Mod2MapIndex =4, 16168 Mod3MapIndex =5, 16169 Mod4MapIndex =6, 16170 Mod5MapIndex =7 16171 } 16172 16173 enum ButtonMask:int { 16174 Button1Mask =1<<8, 16175 Button2Mask =1<<9, 16176 Button3Mask =1<<10, 16177 Button4Mask =1<<11, 16178 Button5Mask =1<<12, 16179 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16180 } 16181 16182 enum KeyOrButtonMask:uint { 16183 ShiftMask =1<<0, 16184 LockMask =1<<1, 16185 ControlMask =1<<2, 16186 Mod1Mask =1<<3, 16187 Mod2Mask =1<<4, 16188 Mod3Mask =1<<5, 16189 Mod4Mask =1<<6, 16190 Mod5Mask =1<<7, 16191 Button1Mask =1<<8, 16192 Button2Mask =1<<9, 16193 Button3Mask =1<<10, 16194 Button4Mask =1<<11, 16195 Button5Mask =1<<12, 16196 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 16197 } 16198 16199 enum ButtonName:int { 16200 Button1 =1, 16201 Button2 =2, 16202 Button3 =3, 16203 Button4 =4, 16204 Button5 =5 16205 } 16206 16207 /* Notify modes */ 16208 enum NotifyModes:int 16209 { 16210 NotifyNormal =0, 16211 NotifyGrab =1, 16212 NotifyUngrab =2, 16213 NotifyWhileGrabbed =3 16214 } 16215 enum NotifyHint = 1; /* for MotionNotify events */ 16216 16217 /* Notify detail */ 16218 enum NotifyDetail:int 16219 { 16220 NotifyAncestor =0, 16221 NotifyVirtual =1, 16222 NotifyInferior =2, 16223 NotifyNonlinear =3, 16224 NotifyNonlinearVirtual =4, 16225 NotifyPointer =5, 16226 NotifyPointerRoot =6, 16227 NotifyDetailNone =7 16228 } 16229 16230 /* Visibility notify */ 16231 16232 enum VisibilityNotify:int 16233 { 16234 VisibilityUnobscured =0, 16235 VisibilityPartiallyObscured =1, 16236 VisibilityFullyObscured =2 16237 } 16238 16239 16240 enum WindowStackingMethod:int 16241 { 16242 Above =0, 16243 Below =1, 16244 TopIf =2, 16245 BottomIf =3, 16246 Opposite =4 16247 } 16248 16249 /* Circulation request */ 16250 enum CirculationRequest:int 16251 { 16252 PlaceOnTop =0, 16253 PlaceOnBottom =1 16254 } 16255 16256 enum PropertyNotification:int 16257 { 16258 PropertyNewValue =0, 16259 PropertyDelete =1 16260 } 16261 16262 enum ColorMapNotification:int 16263 { 16264 ColormapUninstalled =0, 16265 ColormapInstalled =1 16266 } 16267 16268 16269 struct _XPrivate {} 16270 struct _XrmHashBucketRec {} 16271 16272 alias void* XPointer; 16273 alias void* XExtData; 16274 16275 version( X86_64 ) { 16276 alias ulong XID; 16277 alias ulong arch_ulong; 16278 alias long arch_long; 16279 } else version (AArch64) { 16280 alias ulong XID; 16281 alias ulong arch_ulong; 16282 alias long arch_long; 16283 } else { 16284 alias uint XID; 16285 alias uint arch_ulong; 16286 alias int arch_long; 16287 } 16288 16289 alias XID Window; 16290 alias XID Drawable; 16291 alias XID Pixmap; 16292 16293 alias arch_ulong Atom; 16294 alias int Bool; 16295 alias Display XDisplay; 16296 16297 alias int ByteOrder; 16298 alias arch_ulong Time; 16299 alias void ScreenFormat; 16300 16301 struct XImage { 16302 int width, height; /* size of image */ 16303 int xoffset; /* number of pixels offset in X direction */ 16304 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 16305 void *data; /* pointer to image data */ 16306 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 16307 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 16308 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 16309 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 16310 int depth; /* depth of image */ 16311 int bytes_per_line; /* accelarator to next line */ 16312 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 16313 arch_ulong red_mask; /* bits in z arrangment */ 16314 arch_ulong green_mask; 16315 arch_ulong blue_mask; 16316 XPointer obdata; /* hook for the object routines to hang on */ 16317 static struct F { /* image manipulation routines */ 16318 XImage* function( 16319 XDisplay* /* display */, 16320 Visual* /* visual */, 16321 uint /* depth */, 16322 int /* format */, 16323 int /* offset */, 16324 ubyte* /* data */, 16325 uint /* width */, 16326 uint /* height */, 16327 int /* bitmap_pad */, 16328 int /* bytes_per_line */) create_image; 16329 int function(XImage *) destroy_image; 16330 arch_ulong function(XImage *, int, int) get_pixel; 16331 int function(XImage *, int, int, arch_ulong) put_pixel; 16332 XImage* function(XImage *, int, int, uint, uint) sub_image; 16333 int function(XImage *, arch_long) add_pixel; 16334 } 16335 F f; 16336 } 16337 version(X86_64) static assert(XImage.sizeof == 136); 16338 else version(X86) static assert(XImage.sizeof == 88); 16339 16340 struct XCharStruct { 16341 short lbearing; /* origin to left edge of raster */ 16342 short rbearing; /* origin to right edge of raster */ 16343 short width; /* advance to next char's origin */ 16344 short ascent; /* baseline to top edge of raster */ 16345 short descent; /* baseline to bottom edge of raster */ 16346 ushort attributes; /* per char flags (not predefined) */ 16347 } 16348 16349 /* 16350 * To allow arbitrary information with fonts, there are additional properties 16351 * returned. 16352 */ 16353 struct XFontProp { 16354 Atom name; 16355 arch_ulong card32; 16356 } 16357 16358 alias Atom Font; 16359 16360 struct XFontStruct { 16361 XExtData *ext_data; /* Hook for extension to hang data */ 16362 Font fid; /* Font ID for this font */ 16363 uint direction; /* Direction the font is painted */ 16364 uint min_char_or_byte2; /* First character */ 16365 uint max_char_or_byte2; /* Last character */ 16366 uint min_byte1; /* First row that exists (for two-byte fonts) */ 16367 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 16368 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 16369 uint default_char; /* Char to print for undefined character */ 16370 int n_properties; /* How many properties there are */ 16371 XFontProp *properties; /* Pointer to array of additional properties*/ 16372 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 16373 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 16374 XCharStruct *per_char; /* first_char to last_char information */ 16375 int ascent; /* Max extent above baseline for spacing */ 16376 int descent; /* Max descent below baseline for spacing */ 16377 } 16378 16379 16380 /* 16381 * Definitions of specific events. 16382 */ 16383 struct XKeyEvent 16384 { 16385 int type; /* of event */ 16386 arch_ulong serial; /* # of last request processed by server */ 16387 Bool send_event; /* true if this came from a SendEvent request */ 16388 Display *display; /* Display the event was read from */ 16389 Window window; /* "event" window it is reported relative to */ 16390 Window root; /* root window that the event occurred on */ 16391 Window subwindow; /* child window */ 16392 Time time; /* milliseconds */ 16393 int x, y; /* pointer x, y coordinates in event window */ 16394 int x_root, y_root; /* coordinates relative to root */ 16395 KeyOrButtonMask state; /* key or button mask */ 16396 uint keycode; /* detail */ 16397 Bool same_screen; /* same screen flag */ 16398 } 16399 version(X86_64) static assert(XKeyEvent.sizeof == 96); 16400 alias XKeyEvent XKeyPressedEvent; 16401 alias XKeyEvent XKeyReleasedEvent; 16402 16403 struct XButtonEvent 16404 { 16405 int type; /* of event */ 16406 arch_ulong serial; /* # of last request processed by server */ 16407 Bool send_event; /* true if this came from a SendEvent request */ 16408 Display *display; /* Display the event was read from */ 16409 Window window; /* "event" window it is reported relative to */ 16410 Window root; /* root window that the event occurred on */ 16411 Window subwindow; /* child window */ 16412 Time time; /* milliseconds */ 16413 int x, y; /* pointer x, y coordinates in event window */ 16414 int x_root, y_root; /* coordinates relative to root */ 16415 KeyOrButtonMask state; /* key or button mask */ 16416 uint button; /* detail */ 16417 Bool same_screen; /* same screen flag */ 16418 } 16419 alias XButtonEvent XButtonPressedEvent; 16420 alias XButtonEvent XButtonReleasedEvent; 16421 16422 struct XMotionEvent{ 16423 int type; /* of event */ 16424 arch_ulong serial; /* # of last request processed by server */ 16425 Bool send_event; /* true if this came from a SendEvent request */ 16426 Display *display; /* Display the event was read from */ 16427 Window window; /* "event" window reported relative to */ 16428 Window root; /* root window that the event occurred on */ 16429 Window subwindow; /* child window */ 16430 Time time; /* milliseconds */ 16431 int x, y; /* pointer x, y coordinates in event window */ 16432 int x_root, y_root; /* coordinates relative to root */ 16433 KeyOrButtonMask state; /* key or button mask */ 16434 byte is_hint; /* detail */ 16435 Bool same_screen; /* same screen flag */ 16436 } 16437 alias XMotionEvent XPointerMovedEvent; 16438 16439 struct XCrossingEvent{ 16440 int type; /* of event */ 16441 arch_ulong serial; /* # of last request processed by server */ 16442 Bool send_event; /* true if this came from a SendEvent request */ 16443 Display *display; /* Display the event was read from */ 16444 Window window; /* "event" window reported relative to */ 16445 Window root; /* root window that the event occurred on */ 16446 Window subwindow; /* child window */ 16447 Time time; /* milliseconds */ 16448 int x, y; /* pointer x, y coordinates in event window */ 16449 int x_root, y_root; /* coordinates relative to root */ 16450 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 16451 NotifyDetail detail; 16452 /* 16453 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16454 * NotifyNonlinear,NotifyNonlinearVirtual 16455 */ 16456 Bool same_screen; /* same screen flag */ 16457 Bool focus; /* Boolean focus */ 16458 KeyOrButtonMask state; /* key or button mask */ 16459 } 16460 alias XCrossingEvent XEnterWindowEvent; 16461 alias XCrossingEvent XLeaveWindowEvent; 16462 16463 struct XFocusChangeEvent{ 16464 int type; /* FocusIn or FocusOut */ 16465 arch_ulong serial; /* # of last request processed by server */ 16466 Bool send_event; /* true if this came from a SendEvent request */ 16467 Display *display; /* Display the event was read from */ 16468 Window window; /* window of event */ 16469 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 16470 NotifyGrab, NotifyUngrab */ 16471 NotifyDetail detail; 16472 /* 16473 * NotifyAncestor, NotifyVirtual, NotifyInferior, 16474 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 16475 * NotifyPointerRoot, NotifyDetailNone 16476 */ 16477 } 16478 alias XFocusChangeEvent XFocusInEvent; 16479 alias XFocusChangeEvent XFocusOutEvent; 16480 16481 enum CWBackPixmap = (1L<<0); 16482 enum CWBackPixel = (1L<<1); 16483 enum CWBorderPixmap = (1L<<2); 16484 enum CWBorderPixel = (1L<<3); 16485 enum CWBitGravity = (1L<<4); 16486 enum CWWinGravity = (1L<<5); 16487 enum CWBackingStore = (1L<<6); 16488 enum CWBackingPlanes = (1L<<7); 16489 enum CWBackingPixel = (1L<<8); 16490 enum CWOverrideRedirect = (1L<<9); 16491 enum CWSaveUnder = (1L<<10); 16492 enum CWEventMask = (1L<<11); 16493 enum CWDontPropagate = (1L<<12); 16494 enum CWColormap = (1L<<13); 16495 enum CWCursor = (1L<<14); 16496 16497 struct XWindowAttributes { 16498 int x, y; /* location of window */ 16499 int width, height; /* width and height of window */ 16500 int border_width; /* border width of window */ 16501 int depth; /* depth of window */ 16502 Visual *visual; /* the associated visual structure */ 16503 Window root; /* root of screen containing window */ 16504 int class_; /* InputOutput, InputOnly*/ 16505 int bit_gravity; /* one of the bit gravity values */ 16506 int win_gravity; /* one of the window gravity values */ 16507 int backing_store; /* NotUseful, WhenMapped, Always */ 16508 arch_ulong backing_planes; /* planes to be preserved if possible */ 16509 arch_ulong backing_pixel; /* value to be used when restoring planes */ 16510 Bool save_under; /* boolean, should bits under be saved? */ 16511 Colormap colormap; /* color map to be associated with window */ 16512 Bool map_installed; /* boolean, is color map currently installed*/ 16513 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 16514 arch_long all_event_masks; /* set of events all people have interest in*/ 16515 arch_long your_event_mask; /* my event mask */ 16516 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 16517 Bool override_redirect; /* boolean value for override-redirect */ 16518 Screen *screen; /* back pointer to correct screen */ 16519 } 16520 16521 enum IsUnmapped = 0; 16522 enum IsUnviewable = 1; 16523 enum IsViewable = 2; 16524 16525 struct XSetWindowAttributes { 16526 Pixmap background_pixmap;/* background, None, or ParentRelative */ 16527 arch_ulong background_pixel;/* background pixel */ 16528 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 16529 arch_ulong border_pixel;/* border pixel value */ 16530 int bit_gravity; /* one of bit gravity values */ 16531 int win_gravity; /* one of the window gravity values */ 16532 int backing_store; /* NotUseful, WhenMapped, Always */ 16533 arch_ulong backing_planes;/* planes to be preserved if possible */ 16534 arch_ulong backing_pixel;/* value to use in restoring planes */ 16535 Bool save_under; /* should bits under be saved? (popups) */ 16536 arch_long event_mask; /* set of events that should be saved */ 16537 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 16538 Bool override_redirect; /* boolean value for override_redirect */ 16539 Colormap colormap; /* color map to be associated with window */ 16540 Cursor cursor; /* cursor to be displayed (or None) */ 16541 } 16542 16543 16544 alias int Status; 16545 16546 16547 enum EventMask:int 16548 { 16549 NoEventMask =0, 16550 KeyPressMask =1<<0, 16551 KeyReleaseMask =1<<1, 16552 ButtonPressMask =1<<2, 16553 ButtonReleaseMask =1<<3, 16554 EnterWindowMask =1<<4, 16555 LeaveWindowMask =1<<5, 16556 PointerMotionMask =1<<6, 16557 PointerMotionHintMask =1<<7, 16558 Button1MotionMask =1<<8, 16559 Button2MotionMask =1<<9, 16560 Button3MotionMask =1<<10, 16561 Button4MotionMask =1<<11, 16562 Button5MotionMask =1<<12, 16563 ButtonMotionMask =1<<13, 16564 KeymapStateMask =1<<14, 16565 ExposureMask =1<<15, 16566 VisibilityChangeMask =1<<16, 16567 StructureNotifyMask =1<<17, 16568 ResizeRedirectMask =1<<18, 16569 SubstructureNotifyMask =1<<19, 16570 SubstructureRedirectMask=1<<20, 16571 FocusChangeMask =1<<21, 16572 PropertyChangeMask =1<<22, 16573 ColormapChangeMask =1<<23, 16574 OwnerGrabButtonMask =1<<24 16575 } 16576 16577 struct MwmHints { 16578 c_ulong flags; 16579 c_ulong functions; 16580 c_ulong decorations; 16581 c_long input_mode; 16582 c_ulong status; 16583 } 16584 16585 enum { 16586 MWM_HINTS_FUNCTIONS = (1L << 0), 16587 MWM_HINTS_DECORATIONS = (1L << 1), 16588 16589 MWM_FUNC_ALL = (1L << 0), 16590 MWM_FUNC_RESIZE = (1L << 1), 16591 MWM_FUNC_MOVE = (1L << 2), 16592 MWM_FUNC_MINIMIZE = (1L << 3), 16593 MWM_FUNC_MAXIMIZE = (1L << 4), 16594 MWM_FUNC_CLOSE = (1L << 5), 16595 16596 MWM_DECOR_ALL = (1L << 0), 16597 MWM_DECOR_BORDER = (1L << 1), 16598 MWM_DECOR_RESIZEH = (1L << 2), 16599 MWM_DECOR_TITLE = (1L << 3), 16600 MWM_DECOR_MENU = (1L << 4), 16601 MWM_DECOR_MINIMIZE = (1L << 5), 16602 MWM_DECOR_MAXIMIZE = (1L << 6), 16603 } 16604 16605 import core.stdc.config : c_long, c_ulong; 16606 16607 /* Size hints mask bits */ 16608 16609 enum USPosition = (1L << 0) /* user specified x, y */; 16610 enum USSize = (1L << 1) /* user specified width, height */; 16611 enum PPosition = (1L << 2) /* program specified position */; 16612 enum PSize = (1L << 3) /* program specified size */; 16613 enum PMinSize = (1L << 4) /* program specified minimum size */; 16614 enum PMaxSize = (1L << 5) /* program specified maximum size */; 16615 enum PResizeInc = (1L << 6) /* program specified resize increments */; 16616 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 16617 enum PBaseSize = (1L << 8); 16618 enum PWinGravity = (1L << 9); 16619 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 16620 struct XSizeHints { 16621 arch_long flags; /* marks which fields in this structure are defined */ 16622 int x, y; /* Obsolete */ 16623 int width, height; /* Obsolete */ 16624 int min_width, min_height; 16625 int max_width, max_height; 16626 int width_inc, height_inc; 16627 struct Aspect { 16628 int x; /* numerator */ 16629 int y; /* denominator */ 16630 } 16631 16632 Aspect min_aspect; 16633 Aspect max_aspect; 16634 int base_width, base_height; 16635 int win_gravity; 16636 /* this structure may be extended in the future */ 16637 } 16638 16639 16640 16641 enum EventType:int 16642 { 16643 KeyPress =2, 16644 KeyRelease =3, 16645 ButtonPress =4, 16646 ButtonRelease =5, 16647 MotionNotify =6, 16648 EnterNotify =7, 16649 LeaveNotify =8, 16650 FocusIn =9, 16651 FocusOut =10, 16652 KeymapNotify =11, 16653 Expose =12, 16654 GraphicsExpose =13, 16655 NoExpose =14, 16656 VisibilityNotify =15, 16657 CreateNotify =16, 16658 DestroyNotify =17, 16659 UnmapNotify =18, 16660 MapNotify =19, 16661 MapRequest =20, 16662 ReparentNotify =21, 16663 ConfigureNotify =22, 16664 ConfigureRequest =23, 16665 GravityNotify =24, 16666 ResizeRequest =25, 16667 CirculateNotify =26, 16668 CirculateRequest =27, 16669 PropertyNotify =28, 16670 SelectionClear =29, 16671 SelectionRequest =30, 16672 SelectionNotify =31, 16673 ColormapNotify =32, 16674 ClientMessage =33, 16675 MappingNotify =34, 16676 LASTEvent =35 /* must be bigger than any event # */ 16677 } 16678 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 16679 struct XKeymapEvent 16680 { 16681 int type; 16682 arch_ulong serial; /* # of last request processed by server */ 16683 Bool send_event; /* true if this came from a SendEvent request */ 16684 Display *display; /* Display the event was read from */ 16685 Window window; 16686 byte[32] key_vector; 16687 } 16688 16689 struct XExposeEvent 16690 { 16691 int type; 16692 arch_ulong serial; /* # of last request processed by server */ 16693 Bool send_event; /* true if this came from a SendEvent request */ 16694 Display *display; /* Display the event was read from */ 16695 Window window; 16696 int x, y; 16697 int width, height; 16698 int count; /* if non-zero, at least this many more */ 16699 } 16700 16701 struct XGraphicsExposeEvent{ 16702 int type; 16703 arch_ulong serial; /* # of last request processed by server */ 16704 Bool send_event; /* true if this came from a SendEvent request */ 16705 Display *display; /* Display the event was read from */ 16706 Drawable drawable; 16707 int x, y; 16708 int width, height; 16709 int count; /* if non-zero, at least this many more */ 16710 int major_code; /* core is CopyArea or CopyPlane */ 16711 int minor_code; /* not defined in the core */ 16712 } 16713 16714 struct XNoExposeEvent{ 16715 int type; 16716 arch_ulong serial; /* # of last request processed by server */ 16717 Bool send_event; /* true if this came from a SendEvent request */ 16718 Display *display; /* Display the event was read from */ 16719 Drawable drawable; 16720 int major_code; /* core is CopyArea or CopyPlane */ 16721 int minor_code; /* not defined in the core */ 16722 } 16723 16724 struct XVisibilityEvent{ 16725 int type; 16726 arch_ulong serial; /* # of last request processed by server */ 16727 Bool send_event; /* true if this came from a SendEvent request */ 16728 Display *display; /* Display the event was read from */ 16729 Window window; 16730 VisibilityNotify state; /* Visibility state */ 16731 } 16732 16733 struct XCreateWindowEvent{ 16734 int type; 16735 arch_ulong serial; /* # of last request processed by server */ 16736 Bool send_event; /* true if this came from a SendEvent request */ 16737 Display *display; /* Display the event was read from */ 16738 Window parent; /* parent of the window */ 16739 Window window; /* window id of window created */ 16740 int x, y; /* window location */ 16741 int width, height; /* size of window */ 16742 int border_width; /* border width */ 16743 Bool override_redirect; /* creation should be overridden */ 16744 } 16745 16746 struct XDestroyWindowEvent 16747 { 16748 int type; 16749 arch_ulong serial; /* # of last request processed by server */ 16750 Bool send_event; /* true if this came from a SendEvent request */ 16751 Display *display; /* Display the event was read from */ 16752 Window event; 16753 Window window; 16754 } 16755 16756 struct XUnmapEvent 16757 { 16758 int type; 16759 arch_ulong serial; /* # of last request processed by server */ 16760 Bool send_event; /* true if this came from a SendEvent request */ 16761 Display *display; /* Display the event was read from */ 16762 Window event; 16763 Window window; 16764 Bool from_configure; 16765 } 16766 16767 struct XMapEvent 16768 { 16769 int type; 16770 arch_ulong serial; /* # of last request processed by server */ 16771 Bool send_event; /* true if this came from a SendEvent request */ 16772 Display *display; /* Display the event was read from */ 16773 Window event; 16774 Window window; 16775 Bool override_redirect; /* Boolean, is override set... */ 16776 } 16777 16778 struct XMapRequestEvent 16779 { 16780 int type; 16781 arch_ulong serial; /* # of last request processed by server */ 16782 Bool send_event; /* true if this came from a SendEvent request */ 16783 Display *display; /* Display the event was read from */ 16784 Window parent; 16785 Window window; 16786 } 16787 16788 struct XReparentEvent 16789 { 16790 int type; 16791 arch_ulong serial; /* # of last request processed by server */ 16792 Bool send_event; /* true if this came from a SendEvent request */ 16793 Display *display; /* Display the event was read from */ 16794 Window event; 16795 Window window; 16796 Window parent; 16797 int x, y; 16798 Bool override_redirect; 16799 } 16800 16801 struct XConfigureEvent 16802 { 16803 int type; 16804 arch_ulong serial; /* # of last request processed by server */ 16805 Bool send_event; /* true if this came from a SendEvent request */ 16806 Display *display; /* Display the event was read from */ 16807 Window event; 16808 Window window; 16809 int x, y; 16810 int width, height; 16811 int border_width; 16812 Window above; 16813 Bool override_redirect; 16814 } 16815 16816 struct XGravityEvent 16817 { 16818 int type; 16819 arch_ulong serial; /* # of last request processed by server */ 16820 Bool send_event; /* true if this came from a SendEvent request */ 16821 Display *display; /* Display the event was read from */ 16822 Window event; 16823 Window window; 16824 int x, y; 16825 } 16826 16827 struct XResizeRequestEvent 16828 { 16829 int type; 16830 arch_ulong serial; /* # of last request processed by server */ 16831 Bool send_event; /* true if this came from a SendEvent request */ 16832 Display *display; /* Display the event was read from */ 16833 Window window; 16834 int width, height; 16835 } 16836 16837 struct XConfigureRequestEvent 16838 { 16839 int type; 16840 arch_ulong serial; /* # of last request processed by server */ 16841 Bool send_event; /* true if this came from a SendEvent request */ 16842 Display *display; /* Display the event was read from */ 16843 Window parent; 16844 Window window; 16845 int x, y; 16846 int width, height; 16847 int border_width; 16848 Window above; 16849 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 16850 arch_ulong value_mask; 16851 } 16852 16853 struct XCirculateEvent 16854 { 16855 int type; 16856 arch_ulong serial; /* # of last request processed by server */ 16857 Bool send_event; /* true if this came from a SendEvent request */ 16858 Display *display; /* Display the event was read from */ 16859 Window event; 16860 Window window; 16861 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 16862 } 16863 16864 struct XCirculateRequestEvent 16865 { 16866 int type; 16867 arch_ulong serial; /* # of last request processed by server */ 16868 Bool send_event; /* true if this came from a SendEvent request */ 16869 Display *display; /* Display the event was read from */ 16870 Window parent; 16871 Window window; 16872 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 16873 } 16874 16875 struct XPropertyEvent 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 window; 16882 Atom atom; 16883 Time time; 16884 PropertyNotification state; /* NewValue, Deleted */ 16885 } 16886 16887 struct XSelectionClearEvent 16888 { 16889 int type; 16890 arch_ulong serial; /* # of last request processed by server */ 16891 Bool send_event; /* true if this came from a SendEvent request */ 16892 Display *display; /* Display the event was read from */ 16893 Window window; 16894 Atom selection; 16895 Time time; 16896 } 16897 16898 struct XSelectionRequestEvent 16899 { 16900 int type; 16901 arch_ulong serial; /* # of last request processed by server */ 16902 Bool send_event; /* true if this came from a SendEvent request */ 16903 Display *display; /* Display the event was read from */ 16904 Window owner; 16905 Window requestor; 16906 Atom selection; 16907 Atom target; 16908 Atom property; 16909 Time time; 16910 } 16911 16912 struct XSelectionEvent 16913 { 16914 int type; 16915 arch_ulong serial; /* # of last request processed by server */ 16916 Bool send_event; /* true if this came from a SendEvent request */ 16917 Display *display; /* Display the event was read from */ 16918 Window requestor; 16919 Atom selection; 16920 Atom target; 16921 Atom property; /* ATOM or None */ 16922 Time time; 16923 } 16924 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 16925 16926 struct XColormapEvent 16927 { 16928 int type; 16929 arch_ulong serial; /* # of last request processed by server */ 16930 Bool send_event; /* true if this came from a SendEvent request */ 16931 Display *display; /* Display the event was read from */ 16932 Window window; 16933 Colormap colormap; /* COLORMAP or None */ 16934 Bool new_; /* C++ */ 16935 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 16936 } 16937 version(X86_64) static assert(XColormapEvent.sizeof == 56); 16938 16939 struct XClientMessageEvent 16940 { 16941 int type; 16942 arch_ulong serial; /* # of last request processed by server */ 16943 Bool send_event; /* true if this came from a SendEvent request */ 16944 Display *display; /* Display the event was read from */ 16945 Window window; 16946 Atom message_type; 16947 int format; 16948 union Data{ 16949 byte[20] b; 16950 short[10] s; 16951 arch_ulong[5] l; 16952 } 16953 Data data; 16954 16955 } 16956 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 16957 16958 struct XMappingEvent 16959 { 16960 int type; 16961 arch_ulong serial; /* # of last request processed by server */ 16962 Bool send_event; /* true if this came from a SendEvent request */ 16963 Display *display; /* Display the event was read from */ 16964 Window window; /* unused */ 16965 MappingType request; /* one of MappingModifier, MappingKeyboard, 16966 MappingPointer */ 16967 int first_keycode; /* first keycode */ 16968 int count; /* defines range of change w. first_keycode*/ 16969 } 16970 16971 struct XErrorEvent 16972 { 16973 int type; 16974 Display *display; /* Display the event was read from */ 16975 XID resourceid; /* resource id */ 16976 arch_ulong serial; /* serial number of failed request */ 16977 ubyte error_code; /* error code of failed request */ 16978 ubyte request_code; /* Major op-code of failed request */ 16979 ubyte minor_code; /* Minor op-code of failed request */ 16980 } 16981 16982 struct XAnyEvent 16983 { 16984 int type; 16985 arch_ulong serial; /* # of last request processed by server */ 16986 Bool send_event; /* true if this came from a SendEvent request */ 16987 Display *display;/* Display the event was read from */ 16988 Window window; /* window on which event was requested in event mask */ 16989 } 16990 16991 union XEvent{ 16992 int type; /* must not be changed; first element */ 16993 XAnyEvent xany; 16994 XKeyEvent xkey; 16995 XButtonEvent xbutton; 16996 XMotionEvent xmotion; 16997 XCrossingEvent xcrossing; 16998 XFocusChangeEvent xfocus; 16999 XExposeEvent xexpose; 17000 XGraphicsExposeEvent xgraphicsexpose; 17001 XNoExposeEvent xnoexpose; 17002 XVisibilityEvent xvisibility; 17003 XCreateWindowEvent xcreatewindow; 17004 XDestroyWindowEvent xdestroywindow; 17005 XUnmapEvent xunmap; 17006 XMapEvent xmap; 17007 XMapRequestEvent xmaprequest; 17008 XReparentEvent xreparent; 17009 XConfigureEvent xconfigure; 17010 XGravityEvent xgravity; 17011 XResizeRequestEvent xresizerequest; 17012 XConfigureRequestEvent xconfigurerequest; 17013 XCirculateEvent xcirculate; 17014 XCirculateRequestEvent xcirculaterequest; 17015 XPropertyEvent xproperty; 17016 XSelectionClearEvent xselectionclear; 17017 XSelectionRequestEvent xselectionrequest; 17018 XSelectionEvent xselection; 17019 XColormapEvent xcolormap; 17020 XClientMessageEvent xclient; 17021 XMappingEvent xmapping; 17022 XErrorEvent xerror; 17023 XKeymapEvent xkeymap; 17024 arch_ulong[24] pad; 17025 } 17026 17027 17028 struct Display { 17029 XExtData *ext_data; /* hook for extension to hang data */ 17030 _XPrivate *private1; 17031 int fd; /* Network socket. */ 17032 int private2; 17033 int proto_major_version;/* major version of server's X protocol */ 17034 int proto_minor_version;/* minor version of servers X protocol */ 17035 char *vendor; /* vendor of the server hardware */ 17036 XID private3; 17037 XID private4; 17038 XID private5; 17039 int private6; 17040 XID function(Display*)resource_alloc;/* allocator function */ 17041 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 17042 int bitmap_unit; /* padding and data requirements */ 17043 int bitmap_pad; /* padding requirements on bitmaps */ 17044 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 17045 int nformats; /* number of pixmap formats in list */ 17046 ScreenFormat *pixmap_format; /* pixmap format list */ 17047 int private8; 17048 int release; /* release of the server */ 17049 _XPrivate *private9; 17050 _XPrivate *private10; 17051 int qlen; /* Length of input event queue */ 17052 arch_ulong last_request_read; /* seq number of last event read */ 17053 arch_ulong request; /* sequence number of last request. */ 17054 XPointer private11; 17055 XPointer private12; 17056 XPointer private13; 17057 XPointer private14; 17058 uint max_request_size; /* maximum number 32 bit words in request*/ 17059 _XrmHashBucketRec *db; 17060 int function (Display*)private15; 17061 char *display_name; /* "host:display" string used on this connect*/ 17062 int default_screen; /* default screen for operations */ 17063 int nscreens; /* number of screens on this server*/ 17064 Screen *screens; /* pointer to list of screens */ 17065 arch_ulong motion_buffer; /* size of motion buffer */ 17066 arch_ulong private16; 17067 int min_keycode; /* minimum defined keycode */ 17068 int max_keycode; /* maximum defined keycode */ 17069 XPointer private17; 17070 XPointer private18; 17071 int private19; 17072 byte *xdefaults; /* contents of defaults from server */ 17073 /* there is more to this structure, but it is private to Xlib */ 17074 } 17075 17076 // I got these numbers from a C program as a sanity test 17077 version(X86_64) { 17078 static assert(Display.sizeof == 296); 17079 static assert(XPointer.sizeof == 8); 17080 static assert(XErrorEvent.sizeof == 40); 17081 static assert(XAnyEvent.sizeof == 40); 17082 static assert(XMappingEvent.sizeof == 56); 17083 static assert(XEvent.sizeof == 192); 17084 } else version (AArch64) { 17085 // omit check for aarch64 17086 } else { 17087 static assert(Display.sizeof == 176); 17088 static assert(XPointer.sizeof == 4); 17089 static assert(XEvent.sizeof == 96); 17090 } 17091 17092 struct Depth 17093 { 17094 int depth; /* this depth (Z) of the depth */ 17095 int nvisuals; /* number of Visual types at this depth */ 17096 Visual *visuals; /* list of visuals possible at this depth */ 17097 } 17098 17099 alias void* GC; 17100 alias c_ulong VisualID; 17101 alias XID Colormap; 17102 alias XID Cursor; 17103 alias XID KeySym; 17104 alias uint KeyCode; 17105 enum None = 0; 17106 } 17107 17108 version(without_opengl) {} 17109 else { 17110 extern(C) nothrow @nogc { 17111 17112 17113 static if(!SdpyIsUsingIVGLBinds) { 17114 enum GLX_USE_GL= 1; /* support GLX rendering */ 17115 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 17116 enum GLX_LEVEL= 3; /* level in plane stacking */ 17117 enum GLX_RGBA= 4; /* true if RGBA mode */ 17118 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 17119 enum GLX_STEREO= 6; /* stereo buffering supported */ 17120 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 17121 enum GLX_RED_SIZE= 8; /* number of red component bits */ 17122 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 17123 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 17124 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 17125 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 17126 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 17127 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 17128 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 17129 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 17130 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 17131 17132 17133 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 17134 17135 17136 17137 enum GL_TRUE = 1; 17138 enum GL_FALSE = 0; 17139 alias int GLint; 17140 } 17141 17142 alias XID GLXContextID; 17143 alias XID GLXPixmap; 17144 alias XID GLXDrawable; 17145 alias XID GLXPbuffer; 17146 alias XID GLXWindow; 17147 alias XID GLXFBConfigID; 17148 alias void* GLXContext; 17149 17150 } 17151 } 17152 17153 enum AllocNone = 0; 17154 17155 extern(C) { 17156 /* WARNING, this type not in Xlib spec */ 17157 extern(C) alias XIOErrorHandler = int function (Display* display); 17158 } 17159 17160 extern(C) nothrow 17161 alias XErrorHandler = int function(Display*, XErrorEvent*); 17162 17163 extern(C) nothrow @nogc { 17164 struct Screen{ 17165 XExtData *ext_data; /* hook for extension to hang data */ 17166 Display *display; /* back pointer to display structure */ 17167 Window root; /* Root window id. */ 17168 int width, height; /* width and height of screen */ 17169 int mwidth, mheight; /* width and height of in millimeters */ 17170 int ndepths; /* number of depths possible */ 17171 Depth *depths; /* list of allowable depths on the screen */ 17172 int root_depth; /* bits per pixel */ 17173 Visual *root_visual; /* root visual */ 17174 GC default_gc; /* GC for the root root visual */ 17175 Colormap cmap; /* default color map */ 17176 uint white_pixel; 17177 uint black_pixel; /* White and Black pixel values */ 17178 int max_maps, min_maps; /* max and min color maps */ 17179 int backing_store; /* Never, WhenMapped, Always */ 17180 bool save_unders; 17181 int root_input_mask; /* initial root input mask */ 17182 } 17183 17184 struct Visual 17185 { 17186 XExtData *ext_data; /* hook for extension to hang data */ 17187 VisualID visualid; /* visual id of this visual */ 17188 int class_; /* class of screen (monochrome, etc.) */ 17189 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 17190 int bits_per_rgb; /* log base 2 of distinct color values */ 17191 int map_entries; /* color map entries */ 17192 } 17193 17194 alias Display* _XPrivDisplay; 17195 17196 extern(D) Screen* ScreenOfDisplay(Display* dpy, int scr) { 17197 assert(dpy !is null); 17198 return &dpy.screens[scr]; 17199 } 17200 17201 extern(D) Window RootWindow(Display *dpy,int scr) { 17202 return ScreenOfDisplay(dpy,scr).root; 17203 } 17204 17205 struct XWMHints { 17206 arch_long flags; 17207 Bool input; 17208 int initial_state; 17209 Pixmap icon_pixmap; 17210 Window icon_window; 17211 int icon_x, icon_y; 17212 Pixmap icon_mask; 17213 XID window_group; 17214 } 17215 17216 struct XClassHint { 17217 char* res_name; 17218 char* res_class; 17219 } 17220 17221 extern(D) int DefaultScreen(Display *dpy) { 17222 return dpy.default_screen; 17223 } 17224 17225 extern(D) int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 17226 extern(D) int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 17227 extern(D) int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 17228 extern(D) int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 17229 extern(D) int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 17230 extern(D) auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 17231 17232 extern(D) int ConnectionNumber(Display* dpy) { return dpy.fd; } 17233 17234 enum int AnyPropertyType = 0; 17235 enum int Success = 0; 17236 17237 enum int RevertToNone = None; 17238 enum int PointerRoot = 1; 17239 enum Time CurrentTime = 0; 17240 enum int RevertToPointerRoot = PointerRoot; 17241 enum int RevertToParent = 2; 17242 17243 extern(D) int DefaultDepthOfDisplay(Display* dpy) { 17244 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 17245 } 17246 17247 extern(D) Visual* DefaultVisual(Display *dpy,int scr) { 17248 return ScreenOfDisplay(dpy,scr).root_visual; 17249 } 17250 17251 extern(D) GC DefaultGC(Display *dpy,int scr) { 17252 return ScreenOfDisplay(dpy,scr).default_gc; 17253 } 17254 17255 extern(D) uint BlackPixel(Display *dpy,int scr) { 17256 return ScreenOfDisplay(dpy,scr).black_pixel; 17257 } 17258 17259 extern(D) uint WhitePixel(Display *dpy,int scr) { 17260 return ScreenOfDisplay(dpy,scr).white_pixel; 17261 } 17262 17263 alias void* XFontSet; // i think 17264 struct XmbTextItem { 17265 char* chars; 17266 int nchars; 17267 int delta; 17268 XFontSet font_set; 17269 } 17270 17271 struct XTextItem { 17272 char* chars; 17273 int nchars; 17274 int delta; 17275 Font font; 17276 } 17277 17278 enum { 17279 GXclear = 0x0, /* 0 */ 17280 GXand = 0x1, /* src AND dst */ 17281 GXandReverse = 0x2, /* src AND NOT dst */ 17282 GXcopy = 0x3, /* src */ 17283 GXandInverted = 0x4, /* NOT src AND dst */ 17284 GXnoop = 0x5, /* dst */ 17285 GXxor = 0x6, /* src XOR dst */ 17286 GXor = 0x7, /* src OR dst */ 17287 GXnor = 0x8, /* NOT src AND NOT dst */ 17288 GXequiv = 0x9, /* NOT src XOR dst */ 17289 GXinvert = 0xa, /* NOT dst */ 17290 GXorReverse = 0xb, /* src OR NOT dst */ 17291 GXcopyInverted = 0xc, /* NOT src */ 17292 GXorInverted = 0xd, /* NOT src OR dst */ 17293 GXnand = 0xe, /* NOT src OR NOT dst */ 17294 GXset = 0xf, /* 1 */ 17295 } 17296 enum QueueMode : int { 17297 QueuedAlready, 17298 QueuedAfterReading, 17299 QueuedAfterFlush 17300 } 17301 17302 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 17303 17304 struct XPoint { 17305 short x; 17306 short y; 17307 } 17308 17309 enum CoordMode:int { 17310 CoordModeOrigin = 0, 17311 CoordModePrevious = 1 17312 } 17313 17314 enum PolygonShape:int { 17315 Complex = 0, 17316 Nonconvex = 1, 17317 Convex = 2 17318 } 17319 17320 struct XTextProperty { 17321 const(char)* value; /* same as Property routines */ 17322 Atom encoding; /* prop type */ 17323 int format; /* prop data format: 8, 16, or 32 */ 17324 arch_ulong nitems; /* number of data items in value */ 17325 } 17326 17327 version( X86_64 ) { 17328 static assert(XTextProperty.sizeof == 32); 17329 } 17330 17331 17332 struct XGCValues { 17333 int function_; /* logical operation */ 17334 arch_ulong plane_mask;/* plane mask */ 17335 arch_ulong foreground;/* foreground pixel */ 17336 arch_ulong background;/* background pixel */ 17337 int line_width; /* line width */ 17338 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 17339 int cap_style; /* CapNotLast, CapButt, 17340 CapRound, CapProjecting */ 17341 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 17342 int fill_style; /* FillSolid, FillTiled, 17343 FillStippled, FillOpaeueStippled */ 17344 int fill_rule; /* EvenOddRule, WindingRule */ 17345 int arc_mode; /* ArcChord, ArcPieSlice */ 17346 Pixmap tile; /* tile pixmap for tiling operations */ 17347 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 17348 int ts_x_origin; /* offset for tile or stipple operations */ 17349 int ts_y_origin; 17350 Font font; /* default text font for text operations */ 17351 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 17352 Bool graphics_exposures;/* boolean, should exposures be generated */ 17353 int clip_x_origin; /* origin for clipping */ 17354 int clip_y_origin; 17355 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 17356 int dash_offset; /* patterned/dashed line information */ 17357 char dashes; 17358 } 17359 17360 struct XColor { 17361 arch_ulong pixel; 17362 ushort red, green, blue; 17363 byte flags; 17364 byte pad; 17365 } 17366 17367 struct XRectangle { 17368 short x; 17369 short y; 17370 ushort width; 17371 ushort height; 17372 } 17373 17374 enum ClipByChildren = 0; 17375 enum IncludeInferiors = 1; 17376 17377 enum Atom XA_PRIMARY = 1; 17378 enum Atom XA_SECONDARY = 2; 17379 enum Atom XA_STRING = 31; 17380 enum Atom XA_CARDINAL = 6; 17381 enum Atom XA_WM_NAME = 39; 17382 enum Atom XA_ATOM = 4; 17383 enum Atom XA_WINDOW = 33; 17384 enum Atom XA_WM_HINTS = 35; 17385 enum int PropModeAppend = 2; 17386 enum int PropModeReplace = 0; 17387 enum int PropModePrepend = 1; 17388 17389 enum int CopyFromParent = 0; 17390 enum int InputOutput = 1; 17391 17392 // XWMHints 17393 enum InputHint = 1 << 0; 17394 enum StateHint = 1 << 1; 17395 enum IconPixmapHint = (1L << 2); 17396 enum IconWindowHint = (1L << 3); 17397 enum IconPositionHint = (1L << 4); 17398 enum IconMaskHint = (1L << 5); 17399 enum WindowGroupHint = (1L << 6); 17400 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 17401 enum XUrgencyHint = (1L << 8); 17402 17403 // GC Components 17404 enum GCFunction = (1L<<0); 17405 enum GCPlaneMask = (1L<<1); 17406 enum GCForeground = (1L<<2); 17407 enum GCBackground = (1L<<3); 17408 enum GCLineWidth = (1L<<4); 17409 enum GCLineStyle = (1L<<5); 17410 enum GCCapStyle = (1L<<6); 17411 enum GCJoinStyle = (1L<<7); 17412 enum GCFillStyle = (1L<<8); 17413 enum GCFillRule = (1L<<9); 17414 enum GCTile = (1L<<10); 17415 enum GCStipple = (1L<<11); 17416 enum GCTileStipXOrigin = (1L<<12); 17417 enum GCTileStipYOrigin = (1L<<13); 17418 enum GCFont = (1L<<14); 17419 enum GCSubwindowMode = (1L<<15); 17420 enum GCGraphicsExposures= (1L<<16); 17421 enum GCClipXOrigin = (1L<<17); 17422 enum GCClipYOrigin = (1L<<18); 17423 enum GCClipMask = (1L<<19); 17424 enum GCDashOffset = (1L<<20); 17425 enum GCDashList = (1L<<21); 17426 enum GCArcMode = (1L<<22); 17427 enum GCLastBit = 22; 17428 17429 17430 enum int WithdrawnState = 0; 17431 enum int NormalState = 1; 17432 enum int IconicState = 3; 17433 17434 } 17435 } else version (OSXCocoa) { 17436 private: 17437 alias void* id; 17438 alias void* Class; 17439 alias void* SEL; 17440 alias void* IMP; 17441 alias void* Ivar; 17442 alias byte BOOL; 17443 alias const(void)* CFStringRef; 17444 alias const(void)* CFAllocatorRef; 17445 alias const(void)* CFTypeRef; 17446 alias const(void)* CGContextRef; 17447 alias const(void)* CGColorSpaceRef; 17448 alias const(void)* CGImageRef; 17449 alias ulong CGBitmapInfo; 17450 17451 struct objc_super { 17452 id self; 17453 Class superclass; 17454 } 17455 17456 struct CFRange { 17457 long location, length; 17458 } 17459 17460 struct NSPoint { 17461 double x, y; 17462 17463 static fromTuple(T)(T tupl) { 17464 return NSPoint(tupl.tupleof); 17465 } 17466 } 17467 struct NSSize { 17468 double width, height; 17469 } 17470 struct NSRect { 17471 NSPoint origin; 17472 NSSize size; 17473 } 17474 alias NSPoint CGPoint; 17475 alias NSSize CGSize; 17476 alias NSRect CGRect; 17477 17478 struct CGAffineTransform { 17479 double a, b, c, d, tx, ty; 17480 } 17481 17482 enum NSApplicationActivationPolicyRegular = 0; 17483 enum NSBackingStoreBuffered = 2; 17484 enum kCFStringEncodingUTF8 = 0x08000100; 17485 17486 enum : size_t { 17487 NSBorderlessWindowMask = 0, 17488 NSTitledWindowMask = 1 << 0, 17489 NSClosableWindowMask = 1 << 1, 17490 NSMiniaturizableWindowMask = 1 << 2, 17491 NSResizableWindowMask = 1 << 3, 17492 NSTexturedBackgroundWindowMask = 1 << 8 17493 } 17494 17495 enum : ulong { 17496 kCGImageAlphaNone, 17497 kCGImageAlphaPremultipliedLast, 17498 kCGImageAlphaPremultipliedFirst, 17499 kCGImageAlphaLast, 17500 kCGImageAlphaFirst, 17501 kCGImageAlphaNoneSkipLast, 17502 kCGImageAlphaNoneSkipFirst 17503 } 17504 enum : ulong { 17505 kCGBitmapAlphaInfoMask = 0x1F, 17506 kCGBitmapFloatComponents = (1 << 8), 17507 kCGBitmapByteOrderMask = 0x7000, 17508 kCGBitmapByteOrderDefault = (0 << 12), 17509 kCGBitmapByteOrder16Little = (1 << 12), 17510 kCGBitmapByteOrder32Little = (2 << 12), 17511 kCGBitmapByteOrder16Big = (3 << 12), 17512 kCGBitmapByteOrder32Big = (4 << 12) 17513 } 17514 enum CGPathDrawingMode { 17515 kCGPathFill, 17516 kCGPathEOFill, 17517 kCGPathStroke, 17518 kCGPathFillStroke, 17519 kCGPathEOFillStroke 17520 } 17521 enum objc_AssociationPolicy : size_t { 17522 OBJC_ASSOCIATION_ASSIGN = 0, 17523 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 17524 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 17525 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 17526 OBJC_ASSOCIATION_COPY = 0x303 //01403 17527 } 17528 17529 extern(C) { 17530 id objc_msgSend(id receiver, SEL selector, ...); 17531 id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...); 17532 id objc_getClass(const(char)* name); 17533 SEL sel_registerName(const(char)* str); 17534 Class objc_allocateClassPair(Class superclass, const(char)* name, 17535 size_t extra_bytes); 17536 void objc_registerClassPair(Class cls); 17537 BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types); 17538 id objc_getAssociatedObject(id object, void* key); 17539 void objc_setAssociatedObject(id object, void* key, id value, 17540 objc_AssociationPolicy policy); 17541 Ivar class_getInstanceVariable(Class cls, const(char)* name); 17542 id object_getIvar(id object, Ivar ivar); 17543 void object_setIvar(id object, Ivar ivar, id value); 17544 BOOL class_addIvar(Class cls, const(char)* name, 17545 size_t size, ubyte alignment, const(char)* types); 17546 17547 extern __gshared id NSApp; 17548 17549 void CFRelease(CFTypeRef obj); 17550 17551 CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator, 17552 const(char)* bytes, long numBytes, 17553 long encoding, 17554 BOOL isExternalRepresentation); 17555 long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding, 17556 char lossByte, bool isExternalRepresentation, 17557 char* buffer, long maxBufLen, long* usedBufLen); 17558 long CFStringGetLength(CFStringRef theString); 17559 17560 CGContextRef CGBitmapContextCreate(void* data, 17561 size_t width, size_t height, 17562 size_t bitsPerComponent, 17563 size_t bytesPerRow, 17564 CGColorSpaceRef colorspace, 17565 CGBitmapInfo bitmapInfo); 17566 void CGContextRelease(CGContextRef c); 17567 ubyte* CGBitmapContextGetData(CGContextRef c); 17568 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 17569 size_t CGBitmapContextGetWidth(CGContextRef c); 17570 size_t CGBitmapContextGetHeight(CGContextRef c); 17571 17572 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 17573 void CGColorSpaceRelease(CGColorSpaceRef cs); 17574 17575 void CGContextSetRGBStrokeColor(CGContextRef c, 17576 double red, double green, double blue, 17577 double alpha); 17578 void CGContextSetRGBFillColor(CGContextRef c, 17579 double red, double green, double blue, 17580 double alpha); 17581 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 17582 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, 17583 const(char)* str, size_t length); 17584 void CGContextStrokeLineSegments(CGContextRef c, 17585 const(CGPoint)* points, size_t count); 17586 17587 void CGContextBeginPath(CGContextRef c); 17588 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 17589 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 17590 void CGContextAddArc(CGContextRef c, double x, double y, double radius, 17591 double startAngle, double endAngle, long clockwise); 17592 void CGContextAddRect(CGContextRef c, CGRect rect); 17593 void CGContextAddLines(CGContextRef c, 17594 const(CGPoint)* points, size_t count); 17595 void CGContextSaveGState(CGContextRef c); 17596 void CGContextRestoreGState(CGContextRef c); 17597 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, 17598 ulong textEncoding); 17599 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 17600 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 17601 17602 void CGImageRelease(CGImageRef image); 17603 } 17604 17605 private: 17606 // A convenient method to create a CFString (=NSString) from a D string. 17607 CFStringRef createCFString(string str) { 17608 return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length, 17609 kCFStringEncodingUTF8, false); 17610 } 17611 17612 // Objective-C calls. 17613 RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) { 17614 auto _cmd = sel_registerName(selector.ptr); 17615 alias extern(C) RetType function(id, SEL, T) ExpectedType; 17616 return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args); 17617 } 17618 RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) { 17619 auto _cmd = sel_registerName(selector.ptr); 17620 auto cls = objc_getClass(className); 17621 alias extern(C) RetType function(id, SEL, T) ExpectedType; 17622 return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args); 17623 } 17624 RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) { 17625 return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args); 17626 } 17627 17628 alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay; 17629 alias objc_msgSend_classMethod!("alloc", id) alloc; 17630 alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:", 17631 id, NSRect, size_t, size_t, BOOL) initWithContentRect; 17632 alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle; 17633 alias objc_msgSend_specialized!("center", void) center; 17634 alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame; 17635 alias objc_msgSend_specialized!("setContentView:", void, id) setContentView; 17636 alias objc_msgSend_specialized!("release", void) release; 17637 alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor; 17638 alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor; 17639 alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront; 17640 alias objc_msgSend_specialized!("invalidate", void) invalidate; 17641 alias objc_msgSend_specialized!("close", void) close; 17642 alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:", 17643 id, double, id, SEL, id, BOOL) scheduledTimer; 17644 alias objc_msgSend_specialized!("run", void) run; 17645 alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext", 17646 id) currentNSGraphicsContext; 17647 alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort; 17648 alias objc_msgSend_specialized!("characters", CFStringRef) characters; 17649 alias objc_msgSend_specialized!("superclass", Class) superclass; 17650 alias objc_msgSend_specialized!("init", id) init; 17651 alias objc_msgSend_specialized!("addItem:", void, id) addItem; 17652 alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu; 17653 alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:", 17654 id, CFStringRef, SEL, CFStringRef) initWithTitle; 17655 alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu; 17656 alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate; 17657 alias objc_msgSend_specialized!("activateIgnoringOtherApps:", 17658 void, BOOL) activateIgnoringOtherApps; 17659 alias objc_msgSend_classMethod!("NSApplication", "sharedApplication", 17660 id) sharedNSApplication; 17661 alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy; 17662 } else static assert(0, "Unsupported operating system"); 17663 17664 17665 version(OSXCocoa) { 17666 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 17667 // 17668 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 17669 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 17670 // 17671 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 17672 // Probably won't even fully compile right now 17673 17674 import std.math : PI; 17675 import std.algorithm : map; 17676 import std.array : array; 17677 17678 alias SimpleWindow NativeWindowHandle; 17679 alias void delegate(id) NativeEventHandler; 17680 17681 __gshared Ivar simpleWindowIvar; 17682 17683 enum KEY_ESCAPE = 27; 17684 17685 mixin template NativeImageImplementation() { 17686 CGContextRef context; 17687 ubyte* rawData; 17688 final: 17689 17690 void convertToRgbaBytes(ubyte[] where) { 17691 assert(where.length == this.width * this.height * 4); 17692 17693 // if rawData had a length.... 17694 //assert(rawData.length == where.length); 17695 for(long idx = 0; idx < where.length; idx += 4) { 17696 auto alpha = rawData[idx + 3]; 17697 if(alpha == 255) { 17698 where[idx + 0] = rawData[idx + 0]; // r 17699 where[idx + 1] = rawData[idx + 1]; // g 17700 where[idx + 2] = rawData[idx + 2]; // b 17701 where[idx + 3] = rawData[idx + 3]; // a 17702 } else { 17703 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 17704 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 17705 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 17706 where[idx + 3] = rawData[idx + 3]; // a 17707 17708 } 17709 } 17710 } 17711 17712 void setFromRgbaBytes(in ubyte[] where) { 17713 // FIXME: this is probably wrong 17714 assert(where.length == this.width * this.height * 4); 17715 17716 // if rawData had a length.... 17717 //assert(rawData.length == where.length); 17718 for(long idx = 0; idx < where.length; idx += 4) { 17719 auto alpha = rawData[idx + 3]; 17720 if(alpha == 255) { 17721 rawData[idx + 0] = where[idx + 0]; // r 17722 rawData[idx + 1] = where[idx + 1]; // g 17723 rawData[idx + 2] = where[idx + 2]; // b 17724 rawData[idx + 3] = where[idx + 3]; // a 17725 } else { 17726 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 17727 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 17728 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 17729 rawData[idx + 3] = where[idx + 3]; // a 17730 17731 } 17732 } 17733 } 17734 17735 17736 void createImage(int width, int height, bool forcexshm=false) { 17737 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 17738 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 17739 colorSpace, 17740 kCGImageAlphaPremultipliedLast 17741 |kCGBitmapByteOrder32Big); 17742 CGColorSpaceRelease(colorSpace); 17743 rawData = CGBitmapContextGetData(context); 17744 } 17745 void dispose() { 17746 CGContextRelease(context); 17747 } 17748 17749 void setPixel(int x, int y, Color c) { 17750 auto offset = (y * width + x) * 4; 17751 if (c.a == 255) { 17752 rawData[offset + 0] = c.r; 17753 rawData[offset + 1] = c.g; 17754 rawData[offset + 2] = c.b; 17755 rawData[offset + 3] = c.a; 17756 } else { 17757 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 17758 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 17759 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 17760 rawData[offset + 3] = c.a; 17761 } 17762 } 17763 } 17764 17765 mixin template NativeScreenPainterImplementation() { 17766 CGContextRef context; 17767 ubyte[4] _outlineComponents; 17768 id view; 17769 17770 void create(NativeWindowHandle window) { 17771 context = window.drawingContext; 17772 view = window.view; 17773 } 17774 17775 void dispose() { 17776 setNeedsDisplay(view, true); 17777 } 17778 17779 bool manualInvalidations; 17780 void invalidateRect(Rectangle invalidRect) { } 17781 17782 // NotYetImplementedException 17783 Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); } 17784 void rasterOp(RasterOp op) {} 17785 Pen _activePen; 17786 Color _fillColor; 17787 Rectangle _clipRectangle; 17788 void setClipRectangle(int, int, int, int) {} 17789 void setFont(OperatingSystemFont) {} 17790 int fontHeight() { return 14; } 17791 17792 // end 17793 17794 void pen(Pen pen) { 17795 _activePen = pen; 17796 auto color = pen.color; // FIXME 17797 double alphaComponent = color.a/255.0f; 17798 CGContextSetRGBStrokeColor(context, 17799 color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 17800 17801 if (color.a != 255) { 17802 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 17803 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 17804 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 17805 _outlineComponents[3] = color.a; 17806 } else { 17807 _outlineComponents[0] = color.r; 17808 _outlineComponents[1] = color.g; 17809 _outlineComponents[2] = color.b; 17810 _outlineComponents[3] = color.a; 17811 } 17812 } 17813 17814 @property void fillColor(Color color) { 17815 CGContextSetRGBFillColor(context, 17816 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 17817 } 17818 17819 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 17820 // NotYetImplementedException for upper left/width/height 17821 auto cgImage = CGBitmapContextCreateImage(image.context); 17822 auto size = CGSize(CGBitmapContextGetWidth(image.context), 17823 CGBitmapContextGetHeight(image.context)); 17824 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 17825 CGImageRelease(cgImage); 17826 } 17827 17828 version(OSXCocoa) {} else // NotYetImplementedException 17829 void drawPixmap(Sprite image, int x, int y) { 17830 // FIXME: is this efficient? 17831 auto cgImage = CGBitmapContextCreateImage(image.context); 17832 auto size = CGSize(CGBitmapContextGetWidth(image.context), 17833 CGBitmapContextGetHeight(image.context)); 17834 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 17835 CGImageRelease(cgImage); 17836 } 17837 17838 17839 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 17840 // FIXME: alignment 17841 if (_outlineComponents[3] != 0) { 17842 CGContextSaveGState(context); 17843 auto invAlpha = 1.0f/_outlineComponents[3]; 17844 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 17845 _outlineComponents[1]*invAlpha, 17846 _outlineComponents[2]*invAlpha, 17847 _outlineComponents[3]/255.0f); 17848 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 17849 // auto cfstr = cast(id)createCFString(text); 17850 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 17851 // NSPoint(x, y), null); 17852 // CFRelease(cfstr); 17853 CGContextRestoreGState(context); 17854 } 17855 } 17856 17857 void drawPixel(int x, int y) { 17858 auto rawData = CGBitmapContextGetData(context); 17859 auto width = CGBitmapContextGetWidth(context); 17860 auto height = CGBitmapContextGetHeight(context); 17861 auto offset = ((height - y - 1) * width + x) * 4; 17862 rawData[offset .. offset+4] = _outlineComponents; 17863 } 17864 17865 void drawLine(int x1, int y1, int x2, int y2) { 17866 CGPoint[2] linePoints; 17867 linePoints[0] = CGPoint(x1, y1); 17868 linePoints[1] = CGPoint(x2, y2); 17869 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 17870 } 17871 17872 void drawRectangle(int x, int y, int width, int height) { 17873 CGContextBeginPath(context); 17874 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 17875 CGContextAddRect(context, rect); 17876 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17877 } 17878 17879 void drawEllipse(int x1, int y1, int x2, int y2) { 17880 CGContextBeginPath(context); 17881 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 17882 CGContextAddEllipseInRect(context, rect); 17883 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17884 } 17885 17886 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 17887 // @@@BUG@@@ Does not support elliptic arc (width != height). 17888 CGContextBeginPath(context); 17889 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 17890 start*PI/(180*64), finish*PI/(180*64), 0); 17891 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17892 } 17893 17894 void drawPolygon(Point[] intPoints) { 17895 CGContextBeginPath(context); 17896 auto points = array(map!(CGPoint.fromTuple)(intPoints)); 17897 CGContextAddLines(context, points.ptr, points.length); 17898 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 17899 } 17900 } 17901 17902 mixin template NativeSimpleWindowImplementation() { 17903 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 17904 synchronized { 17905 if (NSApp == null) initializeApp(); 17906 } 17907 17908 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 17909 17910 // create the window. 17911 window = initWithContentRect(alloc("NSWindow"), 17912 contentRect, 17913 NSTitledWindowMask 17914 |NSClosableWindowMask 17915 |NSMiniaturizableWindowMask 17916 |NSResizableWindowMask, 17917 NSBackingStoreBuffered, 17918 true); 17919 17920 // set the title & move the window to center. 17921 auto windowTitle = createCFString(title); 17922 setTitle(window, windowTitle); 17923 CFRelease(windowTitle); 17924 center(window); 17925 17926 // create area to draw on. 17927 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 17928 drawingContext = CGBitmapContextCreate(null, width, height, 17929 8, 4*width, colorSpace, 17930 kCGImageAlphaPremultipliedLast 17931 |kCGBitmapByteOrder32Big); 17932 CGColorSpaceRelease(colorSpace); 17933 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 17934 auto matrix = CGContextGetTextMatrix(drawingContext); 17935 matrix.c = -matrix.c; 17936 matrix.d = -matrix.d; 17937 CGContextSetTextMatrix(drawingContext, matrix); 17938 17939 // create the subview that things will be drawn on. 17940 view = initWithFrame(alloc("SDGraphicsView"), contentRect); 17941 setContentView(window, view); 17942 object_setIvar(view, simpleWindowIvar, cast(id)this); 17943 release(view); 17944 17945 setBackgroundColor(window, whiteNSColor); 17946 makeKeyAndOrderFront(window, null); 17947 } 17948 void dispose() { 17949 closeWindow(); 17950 release(window); 17951 } 17952 void closeWindow() { 17953 invalidate(timer); 17954 .close(window); 17955 } 17956 17957 ScreenPainter getPainter(bool manualInvalidations) { 17958 return ScreenPainter(this, this, manualInvalidations); 17959 } 17960 17961 id window; 17962 id timer; 17963 id view; 17964 CGContextRef drawingContext; 17965 } 17966 17967 extern(C) { 17968 private: 17969 BOOL returnTrue3(id self, SEL _cmd, id app) { 17970 return true; 17971 } 17972 BOOL returnTrue2(id self, SEL _cmd) { 17973 return true; 17974 } 17975 17976 void pulse(id self, SEL _cmd) { 17977 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 17978 simpleWindow.handlePulse(); 17979 setNeedsDisplay(self, true); 17980 } 17981 void drawRect(id self, SEL _cmd, NSRect rect) { 17982 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 17983 auto curCtx = graphicsPort(currentNSGraphicsContext); 17984 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 17985 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), 17986 CGBitmapContextGetHeight(simpleWindow.drawingContext)); 17987 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 17988 CGImageRelease(cgImage); 17989 } 17990 void keyDown(id self, SEL _cmd, id event) { 17991 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 17992 17993 // the event may have multiple characters, and we send them all at 17994 // once. 17995 if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) { 17996 auto chars = characters(event); 17997 auto range = CFRange(0, CFStringGetLength(chars)); 17998 auto buffer = new char[range.length*3]; 17999 long actualLength; 18000 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false, 18001 buffer.ptr, cast(int) buffer.length, &actualLength); 18002 foreach (dchar dc; buffer[0..actualLength]) { 18003 if (simpleWindow.handleCharEvent) 18004 simpleWindow.handleCharEvent(dc); 18005 // NotYetImplementedException 18006 //if (simpleWindow.handleKeyEvent) 18007 //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp? 18008 } 18009 } 18010 18011 // the event's 'keyCode' is hardware-dependent. I don't think people 18012 // will like it. Let's leave it to the native handler. 18013 18014 // perform the default action. 18015 18016 // so the default action is to make a bomp sound and i dont want that 18017 // sooooooooo yeah not gonna do that. 18018 18019 //auto superData = objc_super(self, superclass(self)); 18020 //alias extern(C) void function(objc_super*, SEL, id) T; 18021 //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event); 18022 } 18023 } 18024 18025 // initialize the app so that it can be interacted with the user. 18026 // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html 18027 private void initializeApp() { 18028 // push an autorelease pool to avoid leaking. 18029 init(alloc("NSAutoreleasePool")); 18030 18031 // create a new NSApp instance 18032 sharedNSApplication; 18033 setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular); 18034 18035 // create the "Quit" menu. 18036 auto menuBar = init(alloc("NSMenu")); 18037 auto appMenuItem = init(alloc("NSMenuItem")); 18038 addItem(menuBar, appMenuItem); 18039 setMainMenu(NSApp, menuBar); 18040 release(appMenuItem); 18041 release(menuBar); 18042 18043 auto appMenu = init(alloc("NSMenu")); 18044 auto quitTitle = createCFString("Quit"); 18045 auto q = createCFString("q"); 18046 auto quitItem = initWithTitle(alloc("NSMenuItem"), 18047 quitTitle, sel_registerName("terminate:"), q); 18048 addItem(appMenu, quitItem); 18049 setSubmenu(appMenuItem, appMenu); 18050 release(quitItem); 18051 release(appMenu); 18052 CFRelease(q); 18053 CFRelease(quitTitle); 18054 18055 // assign a delegate for the application, allow it to quit when the last 18056 // window is closed. 18057 auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), 18058 "SDWindowCloseDelegate", 0); 18059 class_addMethod(delegateClass, 18060 sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"), 18061 &returnTrue3, "c@:@"); 18062 objc_registerClassPair(delegateClass); 18063 18064 auto appDelegate = init(alloc("SDWindowCloseDelegate")); 18065 setDelegate(NSApp, appDelegate); 18066 activateIgnoringOtherApps(NSApp, true); 18067 18068 // create a new view that draws the graphics and respond to keyDown 18069 // events. 18070 auto viewClass = objc_allocateClassPair(objc_getClass("NSView"), 18071 "SDGraphicsView", (void*).sizeof); 18072 class_addIvar(viewClass, "simpledisplay_simpleWindow", 18073 (void*).sizeof, (void*).alignof, "^v"); 18074 class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"), 18075 &pulse, "v@:"); 18076 class_addMethod(viewClass, sel_registerName("drawRect:"), 18077 &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); 18078 class_addMethod(viewClass, sel_registerName("isFlipped"), 18079 &returnTrue2, "c@:"); 18080 class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"), 18081 &returnTrue2, "c@:"); 18082 class_addMethod(viewClass, sel_registerName("keyDown:"), 18083 &keyDown, "v@:@"); 18084 objc_registerClassPair(viewClass); 18085 simpleWindowIvar = class_getInstanceVariable(viewClass, 18086 "simpledisplay_simpleWindow"); 18087 } 18088 } 18089 18090 version(without_opengl) {} else 18091 extern(System) nothrow @nogc { 18092 //enum uint GL_VERSION = 0x1F02; 18093 //const(char)* glGetString (/*GLenum*/uint); 18094 version(X11) { 18095 static if (!SdpyIsUsingIVGLBinds) { 18096 18097 enum GLX_X_RENDERABLE = 0x8012; 18098 enum GLX_DRAWABLE_TYPE = 0x8010; 18099 enum GLX_RENDER_TYPE = 0x8011; 18100 enum GLX_X_VISUAL_TYPE = 0x22; 18101 enum GLX_TRUE_COLOR = 0x8002; 18102 enum GLX_WINDOW_BIT = 0x00000001; 18103 enum GLX_RGBA_BIT = 0x00000001; 18104 enum GLX_COLOR_INDEX_BIT = 0x00000002; 18105 enum GLX_SAMPLE_BUFFERS = 0x186a0; 18106 enum GLX_SAMPLES = 0x186a1; 18107 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18108 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18109 } 18110 18111 // GLX_EXT_swap_control 18112 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 18113 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 18114 18115 //k8: ugly code to prevent warnings when sdpy is compiled into .a 18116 extern(System) { 18117 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 18118 } 18119 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 18120 18121 // this made public so we don't have to get it again and again 18122 public bool glXCreateContextAttribsARB_present () { 18123 if (glXCreateContextAttribsARBFn is cast(void*)1) { 18124 // get it 18125 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 18126 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 18127 } 18128 return (glXCreateContextAttribsARBFn !is null); 18129 } 18130 18131 // this made public so we don't have to get it again and again 18132 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 18133 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 18134 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 18135 } 18136 18137 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 18138 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 18139 18140 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 18141 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 18142 if (_glx_swapInterval_fn is null) { 18143 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 18144 if (_glx_swapInterval_fn is null) { 18145 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 18146 return; 18147 } 18148 version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); } 18149 } 18150 18151 if(glXSwapIntervalMESA is null) { 18152 // it seems to require both to actually take effect on many computers 18153 // idk why 18154 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 18155 if(glXSwapIntervalMESA is null) 18156 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 18157 } 18158 18159 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 18160 glXSwapIntervalMESA(wait ? 1 : 0); 18161 18162 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 18163 } 18164 } else version(Windows) { 18165 static if (!SdpyIsUsingIVGLBinds) { 18166 enum GL_TRUE = 1; 18167 enum GL_FALSE = 0; 18168 alias int GLint; 18169 18170 public void* glbindGetProcAddress (const(char)* name) { 18171 void* res = wglGetProcAddress(name); 18172 if (res is null) { 18173 /+ 18174 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 18175 import core.sys.windows.windef, core.sys.windows.winbase; 18176 __gshared HINSTANCE dll = null; 18177 if (dll is null) { 18178 dll = LoadLibraryA("opengl32.dll"); 18179 if (dll is null) return null; // <32, but idc 18180 } 18181 res = GetProcAddress(dll, name); 18182 +/ 18183 res = GetProcAddress(gl.libHandle, name); 18184 } 18185 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 18186 return res; 18187 } 18188 } 18189 18190 18191 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 18192 void wglSetVSync(bool wait) { 18193 if(wglSwapIntervalEXT is null) { 18194 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 18195 if(wglSwapIntervalEXT is null) 18196 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 18197 } 18198 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 18199 return; 18200 18201 wglSwapIntervalEXT(wait ? 1 : 0); 18202 } 18203 18204 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 18205 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 18206 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 18207 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 18208 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 18209 18210 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 18211 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 18212 18213 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 18214 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 18215 18216 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 18217 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 18218 18219 void wglInitOtherFunctions () { 18220 if (wglCreateContextAttribsARB is null) { 18221 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 18222 } 18223 } 18224 } 18225 18226 static if (!SdpyIsUsingIVGLBinds) { 18227 18228 interface GL { 18229 extern(System) @nogc nothrow: 18230 18231 void glGetIntegerv(int, void*); 18232 void glMatrixMode(int); 18233 void glPushMatrix(); 18234 void glLoadIdentity(); 18235 void glOrtho(double, double, double, double, double, double); 18236 void glFrustum(double, double, double, double, double, double); 18237 18238 void glPopMatrix(); 18239 void glEnable(int); 18240 void glDisable(int); 18241 void glClear(int); 18242 void glBegin(int); 18243 void glVertex2f(float, float); 18244 void glVertex3f(float, float, float); 18245 void glEnd(); 18246 void glColor3b(byte, byte, byte); 18247 void glColor3ub(ubyte, ubyte, ubyte); 18248 void glColor4b(byte, byte, byte, byte); 18249 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 18250 void glColor3i(int, int, int); 18251 void glColor3ui(uint, uint, uint); 18252 void glColor4i(int, int, int, int); 18253 void glColor4ui(uint, uint, uint, uint); 18254 void glColor3f(float, float, float); 18255 void glColor4f(float, float, float, float); 18256 void glTranslatef(float, float, float); 18257 void glScalef(float, float, float); 18258 version(X11) { 18259 void glSecondaryColor3b(byte, byte, byte); 18260 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 18261 void glSecondaryColor3i(int, int, int); 18262 void glSecondaryColor3ui(uint, uint, uint); 18263 void glSecondaryColor3f(float, float, float); 18264 } 18265 18266 void glDrawElements(int, int, int, void*); 18267 18268 void glRotatef(float, float, float, float); 18269 18270 uint glGetError(); 18271 18272 void glDeleteTextures(int, uint*); 18273 18274 18275 void glRasterPos2i(int, int); 18276 void glDrawPixels(int, int, uint, uint, void*); 18277 void glClearColor(float, float, float, float); 18278 18279 18280 void glPixelStorei(uint, int); 18281 18282 void glGenTextures(uint, uint*); 18283 void glBindTexture(int, int); 18284 void glTexParameteri(uint, uint, int); 18285 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18286 void glTexImage2D(int, int, int, int, int, int, int, int, in void*); 18287 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 18288 /*GLsizei*/int width, /*GLsizei*/int height, 18289 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 18290 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 18291 18292 void glLineWidth(int); 18293 18294 18295 void glTexCoord2f(float, float); 18296 void glVertex2i(int, int); 18297 void glBlendFunc (int, int); 18298 void glDepthFunc (int); 18299 void glViewport(int, int, int, int); 18300 18301 void glClearDepth(double); 18302 18303 void glReadBuffer(uint); 18304 void glReadPixels(int, int, int, int, int, int, void*); 18305 18306 void glFlush(); 18307 void glFinish(); 18308 18309 version(Windows) { 18310 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 18311 HGLRC wglCreateContext(HDC); 18312 HGLRC wglCreateLayerContext(HDC, int); 18313 BOOL wglDeleteContext(HGLRC); 18314 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 18315 HGLRC wglGetCurrentContext(); 18316 HDC wglGetCurrentDC(); 18317 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 18318 PROC wglGetProcAddress(LPCSTR); 18319 BOOL wglMakeCurrent(HDC, HGLRC); 18320 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 18321 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 18322 BOOL wglShareLists(HGLRC, HGLRC); 18323 BOOL wglSwapLayerBuffers(HDC, UINT); 18324 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 18325 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 18326 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18327 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 18328 } 18329 18330 } 18331 18332 interface GL3 { 18333 extern(System) @nogc nothrow: 18334 18335 void glGenVertexArrays(GLsizei, GLuint*); 18336 void glBindVertexArray(GLuint); 18337 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 18338 void glGenerateMipmap(GLenum); 18339 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 18340 void glStencilMask(GLuint); 18341 void glStencilFunc(GLenum, GLint, GLuint); 18342 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18343 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 18344 GLuint glCreateProgram(); 18345 GLuint glCreateShader(GLenum); 18346 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 18347 void glCompileShader(GLuint); 18348 void glGetShaderiv(GLuint, GLenum, GLint*); 18349 void glAttachShader(GLuint, GLuint); 18350 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 18351 void glLinkProgram(GLuint); 18352 void glGetProgramiv(GLuint, GLenum, GLint*); 18353 void glDeleteProgram(GLuint); 18354 void glDeleteShader(GLuint); 18355 GLint glGetUniformLocation(GLuint, const(GLchar)*); 18356 void glGenBuffers(GLsizei, GLuint*); 18357 18358 void glUniform1f(GLint location, GLfloat v0); 18359 void glUniform2f(GLint location, GLfloat v0, GLfloat v1); 18360 void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); 18361 void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); 18362 void glUniform1i(GLint location, GLint v0); 18363 void glUniform2i(GLint location, GLint v0, GLint v1); 18364 void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); 18365 void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); 18366 void glUniform1ui(GLint location, GLuint v0); 18367 void glUniform2ui(GLint location, GLuint v0, GLuint v1); 18368 void glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); 18369 void glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); 18370 void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); 18371 void glUniform2fv(GLint location, GLsizei count, const GLfloat *value); 18372 void glUniform3fv(GLint location, GLsizei count, const GLfloat *value); 18373 void glUniform4fv(GLint location, GLsizei count, const GLfloat *value); 18374 void glUniform1iv(GLint location, GLsizei count, const GLint *value); 18375 void glUniform2iv(GLint location, GLsizei count, const GLint *value); 18376 void glUniform3iv(GLint location, GLsizei count, const GLint *value); 18377 void glUniform4iv(GLint location, GLsizei count, const GLint *value); 18378 void glUniform1uiv(GLint location, GLsizei count, const GLuint *value); 18379 void glUniform2uiv(GLint location, GLsizei count, const GLuint *value); 18380 void glUniform3uiv(GLint location, GLsizei count, const GLuint *value); 18381 void glUniform4uiv(GLint location, GLsizei count, const GLuint *value); 18382 void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18383 void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18384 void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18385 void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18386 void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18387 void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18388 void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18389 void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18390 void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); 18391 18392 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 18393 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 18394 void glDrawArrays(GLenum, GLint, GLsizei); 18395 void glStencilOp(GLenum, GLenum, GLenum); 18396 void glUseProgram(GLuint); 18397 void glCullFace(GLenum); 18398 void glFrontFace(GLenum); 18399 void glActiveTexture(GLenum); 18400 void glBindBuffer(GLenum, GLuint); 18401 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 18402 void glEnableVertexAttribArray(GLuint); 18403 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 18404 void glUniform1i(GLint, GLint); 18405 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 18406 void glDisableVertexAttribArray(GLuint); 18407 void glDeleteBuffers(GLsizei, const(GLuint)*); 18408 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 18409 void glLogicOp (GLenum opcode); 18410 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 18411 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 18412 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 18413 GLenum glCheckFramebufferStatus (GLenum target); 18414 void glBindFramebuffer (GLenum target, GLuint framebuffer); 18415 } 18416 18417 interface GL4 { 18418 extern(System) @nogc nothrow: 18419 18420 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 18421 /*GLsizei*/int width, /*GLsizei*/int height, 18422 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 18423 } 18424 18425 interface GLU { 18426 extern(System) @nogc nothrow: 18427 18428 void gluLookAt(double, double, double, double, double, double, double, double, double); 18429 void gluPerspective(double, double, double, double); 18430 18431 char* gluErrorString(uint); 18432 } 18433 18434 18435 enum GL_RED = 0x1903; 18436 enum GL_ALPHA = 0x1906; 18437 18438 enum uint GL_FRONT = 0x0404; 18439 18440 enum uint GL_BLEND = 0x0be2; 18441 enum uint GL_LEQUAL = 0x0203; 18442 18443 18444 enum uint GL_RGB = 0x1907; 18445 enum uint GL_BGRA = 0x80e1; 18446 enum uint GL_RGBA = 0x1908; 18447 enum uint GL_TEXTURE_2D = 0x0DE1; 18448 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 18449 enum uint GL_NEAREST = 0x2600; 18450 enum uint GL_LINEAR = 0x2601; 18451 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 18452 enum uint GL_TEXTURE_WRAP_S = 0x2802; 18453 enum uint GL_TEXTURE_WRAP_T = 0x2803; 18454 enum uint GL_REPEAT = 0x2901; 18455 enum uint GL_CLAMP = 0x2900; 18456 enum uint GL_CLAMP_TO_EDGE = 0x812F; 18457 enum uint GL_CLAMP_TO_BORDER = 0x812D; 18458 enum uint GL_DECAL = 0x2101; 18459 enum uint GL_MODULATE = 0x2100; 18460 enum uint GL_TEXTURE_ENV = 0x2300; 18461 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 18462 enum uint GL_REPLACE = 0x1E01; 18463 enum uint GL_LIGHTING = 0x0B50; 18464 enum uint GL_DITHER = 0x0BD0; 18465 18466 enum uint GL_NO_ERROR = 0; 18467 18468 18469 18470 enum int GL_VIEWPORT = 0x0BA2; 18471 enum int GL_MODELVIEW = 0x1700; 18472 enum int GL_TEXTURE = 0x1702; 18473 enum int GL_PROJECTION = 0x1701; 18474 enum int GL_DEPTH_TEST = 0x0B71; 18475 18476 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 18477 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 18478 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 18479 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 18480 18481 enum int GL_POINTS = 0x0000; 18482 enum int GL_LINES = 0x0001; 18483 enum int GL_LINE_LOOP = 0x0002; 18484 enum int GL_LINE_STRIP = 0x0003; 18485 enum int GL_TRIANGLES = 0x0004; 18486 enum int GL_TRIANGLE_STRIP = 5; 18487 enum int GL_TRIANGLE_FAN = 6; 18488 enum int GL_QUADS = 7; 18489 enum int GL_QUAD_STRIP = 8; 18490 enum int GL_POLYGON = 9; 18491 18492 alias GLvoid = void; 18493 alias GLboolean = ubyte; 18494 alias GLuint = uint; 18495 alias GLenum = uint; 18496 alias GLchar = char; 18497 alias GLsizei = int; 18498 alias GLfloat = float; 18499 alias GLintptr = size_t; 18500 alias GLsizeiptr = ptrdiff_t; 18501 18502 18503 enum uint GL_INVALID_ENUM = 0x0500; 18504 18505 enum uint GL_ZERO = 0; 18506 enum uint GL_ONE = 1; 18507 18508 enum uint GL_BYTE = 0x1400; 18509 enum uint GL_UNSIGNED_BYTE = 0x1401; 18510 enum uint GL_SHORT = 0x1402; 18511 enum uint GL_UNSIGNED_SHORT = 0x1403; 18512 enum uint GL_INT = 0x1404; 18513 enum uint GL_UNSIGNED_INT = 0x1405; 18514 enum uint GL_FLOAT = 0x1406; 18515 enum uint GL_2_BYTES = 0x1407; 18516 enum uint GL_3_BYTES = 0x1408; 18517 enum uint GL_4_BYTES = 0x1409; 18518 enum uint GL_DOUBLE = 0x140A; 18519 18520 enum uint GL_STREAM_DRAW = 0x88E0; 18521 18522 enum uint GL_CCW = 0x0901; 18523 18524 enum uint GL_STENCIL_TEST = 0x0B90; 18525 enum uint GL_SCISSOR_TEST = 0x0C11; 18526 18527 enum uint GL_EQUAL = 0x0202; 18528 enum uint GL_NOTEQUAL = 0x0205; 18529 18530 enum uint GL_ALWAYS = 0x0207; 18531 enum uint GL_KEEP = 0x1E00; 18532 18533 enum uint GL_INCR = 0x1E02; 18534 18535 enum uint GL_INCR_WRAP = 0x8507; 18536 enum uint GL_DECR_WRAP = 0x8508; 18537 18538 enum uint GL_CULL_FACE = 0x0B44; 18539 enum uint GL_BACK = 0x0405; 18540 18541 enum uint GL_FRAGMENT_SHADER = 0x8B30; 18542 enum uint GL_VERTEX_SHADER = 0x8B31; 18543 18544 enum uint GL_COMPILE_STATUS = 0x8B81; 18545 enum uint GL_LINK_STATUS = 0x8B82; 18546 18547 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 18548 18549 enum uint GL_STATIC_DRAW = 0x88E4; 18550 18551 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 18552 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 18553 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 18554 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 18555 18556 enum uint GL_GENERATE_MIPMAP = 0x8191; 18557 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 18558 18559 enum uint GL_TEXTURE0 = 0x84C0U; 18560 enum uint GL_TEXTURE1 = 0x84C1U; 18561 18562 enum uint GL_ARRAY_BUFFER = 0x8892; 18563 18564 enum uint GL_SRC_COLOR = 0x0300; 18565 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 18566 enum uint GL_SRC_ALPHA = 0x0302; 18567 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 18568 enum uint GL_DST_ALPHA = 0x0304; 18569 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 18570 enum uint GL_DST_COLOR = 0x0306; 18571 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 18572 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 18573 18574 enum uint GL_INVERT = 0x150AU; 18575 18576 enum uint GL_DEPTH_STENCIL = 0x84F9U; 18577 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 18578 18579 enum uint GL_FRAMEBUFFER = 0x8D40U; 18580 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 18581 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 18582 18583 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 18584 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 18585 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 18586 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 18587 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 18588 18589 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 18590 enum uint GL_CLEAR = 0x1500U; 18591 enum uint GL_COPY = 0x1503U; 18592 enum uint GL_XOR = 0x1506U; 18593 18594 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 18595 18596 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 18597 18598 } 18599 } 18600 18601 /++ 18602 History: 18603 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. 18604 +/ 18605 __gshared bool gluSuccessfullyLoaded = true; 18606 18607 version(without_opengl) {} else { 18608 static if(!SdpyIsUsingIVGLBinds) { 18609 version(Windows) { 18610 mixin DynamicLoad!(GL, "opengl32", 1, openGlLibrariesSuccessfullyLoaded) gl; 18611 mixin DynamicLoad!(GLU, "glu32", 1, gluSuccessfullyLoaded) glu; 18612 } else { 18613 mixin DynamicLoad!(GL, "GL", 1, openGlLibrariesSuccessfullyLoaded) gl; 18614 mixin DynamicLoad!(GLU, "GLU", 3, gluSuccessfullyLoaded) glu; 18615 } 18616 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 18617 18618 18619 shared static this() { 18620 gl.loadDynamicLibrary(); 18621 18622 // FIXME: this is NOT actually required and should NOT fail if it is not loaded 18623 // unless those functions are actually used 18624 // go to mark b openGlLibrariesSuccessfullyLoaded = false; 18625 glu.loadDynamicLibrary(); 18626 } 18627 } 18628 } 18629 18630 /++ 18631 Convenience method for converting D arrays to opengl buffer data 18632 18633 I would LOVE to overload it with the original glBufferData, but D won't 18634 let me since glBufferData is a function pointer :( 18635 18636 Added: August 25, 2020 (version 8.5) 18637 +/ 18638 version(without_opengl) {} else 18639 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 18640 glBufferData(target, data.length, data.ptr, usage); 18641 } 18642 18643 /+ 18644 /++ 18645 A matrix for simple uses that easily integrates with [OpenGlShader]. 18646 18647 Might not be useful to you since it only as some simple functions and 18648 probably isn't that fast. 18649 18650 Note it uses an inline static array for its storage, so copying it 18651 may be expensive. 18652 +/ 18653 struct BasicMatrix(int columns, int rows, T = float) { 18654 import core.stdc.math; 18655 18656 T[columns * rows] data = 0.0; 18657 18658 /++ 18659 Basic operations that operate *in place*. 18660 +/ 18661 void translate() { 18662 18663 } 18664 18665 /// ditto 18666 void scale() { 18667 18668 } 18669 18670 /// ditto 18671 void rotate() { 18672 18673 } 18674 18675 /++ 18676 18677 +/ 18678 static if(columns == rows) 18679 static BasicMatrix identity() { 18680 BasicMatrix m; 18681 foreach(i; 0 .. columns) 18682 data[0 + i + i * columns] = 1.0; 18683 return m; 18684 } 18685 18686 static BasicMatrix ortho() { 18687 return BasicMatrix.init; 18688 } 18689 } 18690 +/ 18691 18692 /++ 18693 Convenience class for using opengl shaders. 18694 18695 Ensure that you've loaded opengl 3+ and set your active 18696 context before trying to use this. 18697 18698 Added: August 25, 2020 (version 8.5) 18699 +/ 18700 version(without_opengl) {} else 18701 final class OpenGlShader { 18702 private int shaderProgram_; 18703 private @property void shaderProgram(int a) { 18704 shaderProgram_ = a; 18705 } 18706 /// Get the program ID for use in OpenGL functions. 18707 public @property int shaderProgram() { 18708 return shaderProgram_; 18709 } 18710 18711 /++ 18712 18713 +/ 18714 static struct Source { 18715 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 18716 string code; /// 18717 } 18718 18719 /++ 18720 Helper method to just compile some shader code and check for errors 18721 while you do glCreateShader, etc. on the outside yourself. 18722 18723 This just does `glShaderSource` and `glCompileShader` for the given code. 18724 18725 If you the OpenGlShader class constructor, you never need to call this yourself. 18726 +/ 18727 static void compile(int sid, Source code) { 18728 const(char)*[1] buffer; 18729 int[1] lengthBuffer; 18730 18731 buffer[0] = code.code.ptr; 18732 lengthBuffer[0] = cast(int) code.code.length; 18733 18734 glShaderSource(sid, cast(int) buffer.length, buffer.ptr, lengthBuffer.ptr); 18735 glCompileShader(sid); 18736 18737 int success; 18738 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 18739 if(!success) { 18740 char[512] info; 18741 int len; 18742 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 18743 18744 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 18745 } 18746 } 18747 18748 /++ 18749 Calls `glLinkProgram` and throws if error a occurs. 18750 18751 If you the OpenGlShader class constructor, you never need to call this yourself. 18752 +/ 18753 static void link(int shaderProgram) { 18754 glLinkProgram(shaderProgram); 18755 int success; 18756 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 18757 if(!success) { 18758 char[512] info; 18759 int len; 18760 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 18761 18762 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 18763 } 18764 } 18765 18766 /++ 18767 Constructs the shader object by calling `glCreateProgram`, then 18768 compiling each given [Source], and finally, linking them together. 18769 18770 Throws: on compile or link failure. 18771 +/ 18772 this(Source[] codes...) { 18773 shaderProgram = glCreateProgram(); 18774 18775 int[16] shadersBufferStack; 18776 18777 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 18778 shadersBufferStack[0 .. codes.length] : 18779 new int[](codes.length); 18780 18781 foreach(idx, code; codes) { 18782 shadersBuffer[idx] = glCreateShader(code.type); 18783 18784 compile(shadersBuffer[idx], code); 18785 18786 glAttachShader(shaderProgram, shadersBuffer[idx]); 18787 } 18788 18789 link(shaderProgram); 18790 18791 foreach(s; shadersBuffer) 18792 glDeleteShader(s); 18793 } 18794 18795 /// Calls `glUseProgram(this.shaderProgram)` 18796 void use() { 18797 glUseProgram(this.shaderProgram); 18798 } 18799 18800 /// Deletes the program. 18801 void delete_() { 18802 glDeleteProgram(shaderProgram); 18803 shaderProgram = 0; 18804 } 18805 18806 /++ 18807 [OpenGlShader.uniforms].name gives you one of these. 18808 18809 You can get the id out of it or just assign 18810 +/ 18811 static struct Uniform { 18812 /// the id passed to glUniform* 18813 int id; 18814 18815 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 18816 void opAssign(float x, float y, float z, float w) { 18817 if(id != -1) 18818 glUniform4f(id, x, y, z, w); 18819 } 18820 18821 void opAssign(float x) { 18822 if(id != -1) 18823 glUniform1f(id, x); 18824 } 18825 18826 void opAssign(float x, float y) { 18827 if(id != -1) 18828 glUniform2f(id, x, y); 18829 } 18830 18831 void opAssign(T)(T t) { 18832 t.glUniform(id); 18833 } 18834 } 18835 18836 static struct UniformsHelper { 18837 OpenGlShader _shader; 18838 18839 @property Uniform opDispatch(string name)() { 18840 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 18841 // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste 18842 //if(i == -1) 18843 //throw new Exception("Could not find uniform " ~ name); 18844 return Uniform(i); 18845 } 18846 18847 @property void opDispatch(string name, T)(T t) { 18848 Uniform f = this.opDispatch!name; 18849 t.glUniform(f); 18850 } 18851 } 18852 18853 /++ 18854 Gives access to the uniforms through dot access. 18855 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 18856 +/ 18857 @property UniformsHelper uniforms() { return UniformsHelper(this); } 18858 } 18859 18860 version(without_opengl) {} else { 18861 /++ 18862 A static container of experimental types and value constructors for opengl 3+ shaders. 18863 18864 18865 You can declare variables like: 18866 18867 ``` 18868 OGL.vec3f something; 18869 ``` 18870 18871 But generally it would be used with [OpenGlShader]'s uniform helpers like 18872 18873 ``` 18874 shader.uniforms.mouse = OGL.vec(mouseX, mouseY); // or OGL.vec2f if you want to be more specific 18875 ``` 18876 18877 This is still extremely experimental, not very useful at this point, and thus subject to change at random. 18878 18879 18880 History: 18881 Added December 7, 2021. Not yet stable. 18882 +/ 18883 final class OGL { 18884 static: 18885 18886 private template typeFromSpecifier(string specifier) { 18887 static if(specifier == "f") 18888 alias typeFromSpecifier = GLfloat; 18889 else static if(specifier == "i") 18890 alias typeFromSpecifier = GLint; 18891 else static if(specifier == "ui") 18892 alias typeFromSpecifier = GLuint; 18893 else static assert(0, "I don't know this ogl type suffix " ~ specifier); 18894 } 18895 18896 private template CommonType(T...) { 18897 static if(T.length == 1) 18898 alias CommonType = T[0]; 18899 else static if(is(typeof(true ? T[0].init : T[1].init) C)) 18900 alias CommonType = CommonType!(C, T[2 .. $]); 18901 } 18902 18903 private template typesToSpecifier(T...) { 18904 static if(is(CommonType!T == float)) 18905 enum typesToSpecifier = "f"; 18906 else static if(is(CommonType!T == int)) 18907 enum typesToSpecifier = "i"; 18908 else static if(is(CommonType!T == uint)) 18909 enum typesToSpecifier = "ui"; 18910 else static assert(0, "I can't find a gl type suffix for common type " ~ CommonType!T.stringof); 18911 } 18912 18913 private template genNames(size_t dim, size_t dim2 = 0) { 18914 string helper() { 18915 string s; 18916 if(dim2) { 18917 s ~= "type["~(dim + '0')~"]["~(dim2 + '0')~"] matrix;"; 18918 } else { 18919 if(dim > 0) s ~= "type x = 0;"; 18920 if(dim > 1) s ~= "type y = 0;"; 18921 if(dim > 2) s ~= "type z = 0;"; 18922 if(dim > 3) s ~= "type w = 0;"; 18923 } 18924 return s; 18925 } 18926 18927 enum genNames = helper(); 18928 } 18929 18930 // there's vec, arrays of vec, mat, and arrays of mat 18931 template opDispatch(string name) 18932 if(name.length > 4 && (name[0 .. 3] == "vec" || name[0 .. 3] == "mat")) 18933 { 18934 static if(name[4] == 'x') { 18935 enum dimX = cast(int) (name[3] - '0'); 18936 static assert(dimX > 0 && dimX <= 4, "Bad dimension for OGL X type " ~ name[3]); 18937 18938 enum dimY = cast(int) (name[5] - '0'); 18939 static assert(dimY > 0 && dimY <= 4, "Bad dimension for OGL Y type " ~ name[5]); 18940 18941 enum isArray = name[$ - 1] == 'v'; 18942 enum typeSpecifier = isArray ? name[6 .. $ - 1] : name[6 .. $]; 18943 alias type = typeFromSpecifier!typeSpecifier; 18944 } else { 18945 enum dim = cast(int) (name[3] - '0'); 18946 static assert(dim > 0 && dim <= 4, "Bad dimension for OGL type " ~ name[3]); 18947 enum isArray = name[$ - 1] == 'v'; 18948 enum typeSpecifier = isArray ? name[4 .. $ - 1] : name[4 .. $]; 18949 alias type = typeFromSpecifier!typeSpecifier; 18950 } 18951 18952 align(1) 18953 struct opDispatch { 18954 align(1): 18955 static if(name[4] == 'x') 18956 mixin(genNames!(dimX, dimY)); 18957 else 18958 mixin(genNames!dim); 18959 18960 private void glUniform(OpenGlShader.Uniform assignTo) { 18961 glUniform(assignTo.id); 18962 } 18963 private void glUniform(int assignTo) { 18964 static if(name[4] == 'x') { 18965 // FIXME 18966 pragma(msg, "This matrix uniform helper has never been tested!!!!"); 18967 mixin("glUniformMatrix" ~ name[3 .. $] ~ "v")(assignTo, dimX * dimY, false, this.matrix.ptr); 18968 } else 18969 mixin("glUniform" ~ name[3 .. $])(assignTo, this.tupleof); 18970 } 18971 } 18972 } 18973 18974 auto vec(T...)(T members) { 18975 return typeof(this).opDispatch!("vec" ~ toInternal!string(cast(int) T.length)~ typesToSpecifier!T)(members); 18976 } 18977 } 18978 } 18979 18980 version(linux) { 18981 version(with_eventloop) {} else { 18982 private int epollFd = -1; 18983 void prepareEventLoop() { 18984 if(epollFd != -1) 18985 return; // already initialized, no need to do it again 18986 import ep = core.sys.linux.epoll; 18987 18988 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 18989 if(epollFd == -1) 18990 throw new Exception("epoll create failure"); 18991 } 18992 } 18993 } else version(Posix) { 18994 void prepareEventLoop() {} 18995 } 18996 18997 version(X11) { 18998 import core.stdc.locale : LC_ALL; // rdmd fix 18999 __gshared bool sdx_isUTF8Locale; 19000 19001 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 19002 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 19003 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 19004 // anal magic is here. I (Ketmar) hope you like it. 19005 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 19006 // always return correct unicode symbols. The detection is here 'cause user can change locale 19007 // later. 19008 19009 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 19010 shared static this () { 19011 if(!librariesSuccessfullyLoaded) 19012 return; 19013 19014 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 19015 19016 // this doesn't hurt; it may add some locking, but the speed is still 19017 // allows doing 60 FPS videogames; also, ignore the result, as most 19018 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 19019 // never seen this failing). 19020 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 19021 19022 setlocale(LC_ALL, ""); 19023 // check if out locale is UTF-8 19024 auto lct = setlocale(LC_CTYPE, null); 19025 if (lct is null) { 19026 sdx_isUTF8Locale = false; 19027 } else { 19028 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 19029 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 19030 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 19031 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 19032 { 19033 sdx_isUTF8Locale = true; 19034 break; 19035 } 19036 } 19037 } 19038 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 19039 } 19040 } 19041 19042 class ExperimentalTextComponent2 { 19043 /+ 19044 Stage 1: get it working monospace 19045 Stage 2: use proportional font 19046 Stage 3: allow changes in inline style 19047 Stage 4: allow new fonts and sizes in the middle 19048 Stage 5: optimize gap buffer 19049 Stage 6: optimize layout 19050 Stage 7: word wrap 19051 Stage 8: justification 19052 Stage 9: editing, selection, etc. 19053 19054 Operations: 19055 insert text 19056 overstrike text 19057 select 19058 cut 19059 modify 19060 +/ 19061 19062 /++ 19063 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. 19064 +/ 19065 this(SimpleWindow window) { 19066 this.window = window; 19067 } 19068 19069 private SimpleWindow window; 19070 19071 19072 /++ 19073 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 19074 representing the internal parts. The first pass is focused on the x parameter, then the 19075 renderer is responsible for going back to the parts in the current line and calling 19076 adjustDownForAscent to change the y params. 19077 +/ 19078 static interface ComponentRenderHelper { 19079 19080 /+ 19081 When you do an edit, possibly stuff on the same line previously need to move (to adjust 19082 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 19083 to move (adjust y to make room for new line) until you get back to the same position, 19084 then you can stop - if one thing is unchanged, nothing after it is changed too. 19085 19086 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 19087 once you reach something that is unchanged, you can stop. 19088 +/ 19089 19090 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 19091 19092 int ascent() const; 19093 int descent() const; 19094 19095 int advance() const; 19096 19097 bool endsWithExplititLineBreak() const; 19098 } 19099 19100 static interface RenderResult { 19101 /++ 19102 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. 19103 +/ 19104 void popFront(); 19105 @property bool empty() const; 19106 @property ComponentRenderHelper front() const; 19107 19108 void repositionForNextLine(Point baseline, int availableWidth); 19109 } 19110 19111 static interface ComponentInFlow { 19112 void draw(ScreenPainter painter); 19113 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 19114 19115 bool startsWithExplicitLineBreak() const; 19116 } 19117 19118 static class TextFlowComponent : ComponentInFlow { 19119 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 19120 19121 Color foreground; 19122 Color background; 19123 19124 OperatingSystemFont font; // should NEVER be null 19125 19126 ubyte attributes; // underline, strike through, display on new block 19127 19128 version(Windows) 19129 const(wchar)[] content; 19130 else 19131 const(char)[] content; // this should NEVER have a newline, except at the end 19132 19133 RenderedComponent[] rendered; // entirely controlled by [rerender] 19134 19135 // could prolly put some spacing around it too like margin / padding 19136 19137 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 19138 in { assert(font !is null); 19139 assert(!font.isNull); } 19140 do 19141 { 19142 this.foreground = f; 19143 this.background = b; 19144 this.font = font; 19145 19146 this.attributes = attr; 19147 version(Windows) { 19148 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 19149 auto sz = sizeOfConvertedWstring(c, conversionFlags); 19150 auto buffer = new wchar[](sz); 19151 this.content = makeWindowsString(c, buffer, conversionFlags); 19152 } else { 19153 this.content = c.dup; 19154 } 19155 } 19156 19157 void draw(ScreenPainter painter) { 19158 painter.setFont(this.font); 19159 painter.outlineColor = this.foreground; 19160 painter.fillColor = Color.transparent; 19161 foreach(rendered; this.rendered) { 19162 // the component works in term of baseline, 19163 // but the painter works in term of upper left bounding box 19164 // so need to translate that 19165 19166 if(this.background.a) { 19167 painter.fillColor = this.background; 19168 painter.outlineColor = this.background; 19169 19170 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 19171 19172 painter.outlineColor = this.foreground; 19173 painter.fillColor = Color.transparent; 19174 } 19175 19176 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 19177 19178 // FIXME: strike through, underline, highlight selection, etc. 19179 } 19180 } 19181 } 19182 19183 // I could split the parts into words on render 19184 // for easier word-wrap, each one being an unbreakable "inline-block" 19185 private TextFlowComponent[] parts; 19186 private int needsRerenderFrom; 19187 19188 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 19189 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 19190 parts ~= new TextFlowComponent(f, b, font, attr, c); 19191 } 19192 19193 static struct RenderedComponent { 19194 int startX; 19195 int startY; 19196 short width; 19197 // 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! 19198 // for individual chars in here you've gotta process on demand 19199 version(Windows) 19200 const(wchar)[] slice; 19201 else 19202 const(char)[] slice; 19203 } 19204 19205 19206 void rerender(Rectangle boundingBox) { 19207 Point baseline = boundingBox.upperLeft; 19208 19209 this.boundingBox.left = boundingBox.left; 19210 this.boundingBox.top = boundingBox.top; 19211 19212 auto remainingParts = parts; 19213 19214 int largestX; 19215 19216 19217 foreach(part; parts) 19218 part.font.prepareContext(window); 19219 scope(exit) 19220 foreach(part; parts) 19221 part.font.releaseContext(); 19222 19223 calculateNextLine: 19224 19225 int nextLineHeight = 0; 19226 int nextBiggestDescent = 0; 19227 19228 foreach(part; remainingParts) { 19229 auto height = part.font.ascent; 19230 if(height > nextLineHeight) 19231 nextLineHeight = height; 19232 if(part.font.descent > nextBiggestDescent) 19233 nextBiggestDescent = part.font.descent; 19234 if(part.content.length && part.content[$-1] == '\n') 19235 break; 19236 } 19237 19238 baseline.y += nextLineHeight; 19239 auto lineStart = baseline; 19240 19241 while(remainingParts.length) { 19242 remainingParts[0].rendered = null; 19243 19244 bool eol; 19245 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 19246 eol = true; 19247 19248 // FIXME: word wrap 19249 auto font = remainingParts[0].font; 19250 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 19251 auto width = font.stringWidth(slice, window); 19252 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 19253 19254 remainingParts = remainingParts[1 .. $]; 19255 baseline.x += width; 19256 19257 if(eol) { 19258 baseline.y += nextBiggestDescent; 19259 if(baseline.x > largestX) 19260 largestX = baseline.x; 19261 baseline.x = lineStart.x; 19262 goto calculateNextLine; 19263 } 19264 } 19265 19266 if(baseline.x > largestX) 19267 largestX = baseline.x; 19268 19269 this.boundingBox.right = largestX; 19270 this.boundingBox.bottom = baseline.y; 19271 } 19272 19273 // you must call rerender first! 19274 void draw(ScreenPainter painter) { 19275 foreach(part; parts) { 19276 part.draw(painter); 19277 } 19278 } 19279 19280 struct IdentifyResult { 19281 TextFlowComponent part; 19282 int charIndexInPart; 19283 int totalCharIndex = -1; // if this is -1, it just means the end 19284 19285 Rectangle boundingBox; 19286 } 19287 19288 IdentifyResult identify(Point pt, bool exact = false) { 19289 if(parts.length == 0) 19290 return IdentifyResult(null, 0); 19291 19292 if(pt.y < boundingBox.top) { 19293 if(exact) 19294 return IdentifyResult(null, 1); 19295 return IdentifyResult(parts[0], 0); 19296 } 19297 if(pt.y > boundingBox.bottom) { 19298 if(exact) 19299 return IdentifyResult(null, 2); 19300 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 19301 } 19302 19303 int tci = 0; 19304 19305 // I should probably like binary search this or something... 19306 foreach(ref part; parts) { 19307 foreach(rendered; part.rendered) { 19308 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 19309 if(rect.contains(pt)) { 19310 auto x = pt.x - rendered.startX; 19311 auto estimatedIdx = x / part.font.averageWidth; 19312 19313 if(estimatedIdx < 0) 19314 estimatedIdx = 0; 19315 19316 if(estimatedIdx > rendered.slice.length) 19317 estimatedIdx = cast(int) rendered.slice.length; 19318 19319 int idx; 19320 int x1, x2; 19321 if(part.font.isMonospace) { 19322 auto w = part.font.averageWidth; 19323 if(!exact && x > (estimatedIdx + 1) * w) 19324 return IdentifyResult(null, 4); 19325 idx = estimatedIdx; 19326 x1 = idx * w; 19327 x2 = (idx + 1) * w; 19328 } else { 19329 idx = estimatedIdx; 19330 19331 part.font.prepareContext(window); 19332 scope(exit) part.font.releaseContext(); 19333 19334 // int iterations; 19335 19336 while(true) { 19337 // iterations++; 19338 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 19339 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 19340 19341 x1 += rendered.startX; 19342 x2 += rendered.startX; 19343 19344 if(pt.x < x1) { 19345 if(idx == 0) { 19346 if(exact) 19347 return IdentifyResult(null, 6); 19348 else 19349 break; 19350 } 19351 idx--; 19352 } else if(pt.x > x2) { 19353 idx++; 19354 if(idx > rendered.slice.length) { 19355 if(exact) 19356 return IdentifyResult(null, 5); 19357 else 19358 break; 19359 } 19360 } else if(pt.x >= x1 && pt.x <= x2) { 19361 if(idx) 19362 idx--; // point it at the original index 19363 break; // we fit 19364 } 19365 } 19366 19367 // import std.stdio; writeln(iterations) 19368 } 19369 19370 19371 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 19372 } 19373 } 19374 tci += cast(int) part.content.length; // FIXME: utf-8? 19375 } 19376 return IdentifyResult(null, 3); 19377 } 19378 19379 Rectangle boundingBox; // only set after [rerender] 19380 19381 // text will be positioned around the exclusion zone 19382 static struct ExclusionZone { 19383 19384 } 19385 19386 ExclusionZone[] exclusionZones; 19387 } 19388 19389 19390 // Don't use this yet. When I'm happy with it, I will move it to the 19391 // regular module namespace. 19392 mixin template ExperimentalTextComponent() { 19393 19394 static: 19395 19396 alias Rectangle = arsd.color.Rectangle; 19397 19398 struct ForegroundColor { 19399 Color color; 19400 alias color this; 19401 19402 this(Color c) { 19403 color = c; 19404 } 19405 19406 this(int r, int g, int b, int a = 255) { 19407 color = Color(r, g, b, a); 19408 } 19409 19410 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 19411 return ForegroundColor(mixin("Color." ~ s)); 19412 } 19413 } 19414 19415 struct BackgroundColor { 19416 Color color; 19417 alias color this; 19418 19419 this(Color c) { 19420 color = c; 19421 } 19422 19423 this(int r, int g, int b, int a = 255) { 19424 color = Color(r, g, b, a); 19425 } 19426 19427 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 19428 return BackgroundColor(mixin("Color." ~ s)); 19429 } 19430 } 19431 19432 static class InlineElement { 19433 string text; 19434 19435 BlockElement containingBlock; 19436 19437 Color color = Color.black; 19438 Color backgroundColor = Color.transparent; 19439 ushort styles; 19440 19441 string font; 19442 int fontSize; 19443 19444 int lineHeight; 19445 19446 void* identifier; 19447 19448 Rectangle boundingBox; 19449 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 19450 19451 bool isMergeCompatible(InlineElement other) { 19452 return 19453 containingBlock is other.containingBlock && 19454 color == other.color && 19455 backgroundColor == other.backgroundColor && 19456 styles == other.styles && 19457 font == other.font && 19458 fontSize == other.fontSize && 19459 lineHeight == other.lineHeight && 19460 true; 19461 } 19462 19463 int xOfIndex(size_t index) { 19464 if(index < letterXs.length) 19465 return letterXs[index]; 19466 else 19467 return boundingBox.right; 19468 } 19469 19470 InlineElement clone() { 19471 auto ie = new InlineElement(); 19472 ie.tupleof = this.tupleof; 19473 return ie; 19474 } 19475 19476 InlineElement getPreviousInlineElement() { 19477 InlineElement prev = null; 19478 foreach(ie; this.containingBlock.parts) { 19479 if(ie is this) 19480 break; 19481 prev = ie; 19482 } 19483 if(prev is null) { 19484 BlockElement pb; 19485 BlockElement cb = this.containingBlock; 19486 moar: 19487 foreach(ie; this.containingBlock.containingLayout.blocks) { 19488 if(ie is cb) 19489 break; 19490 pb = ie; 19491 } 19492 if(pb is null) 19493 return null; 19494 if(pb.parts.length == 0) { 19495 cb = pb; 19496 goto moar; 19497 } 19498 19499 prev = pb.parts[$-1]; 19500 19501 } 19502 return prev; 19503 } 19504 19505 InlineElement getNextInlineElement() { 19506 InlineElement next = null; 19507 foreach(idx, ie; this.containingBlock.parts) { 19508 if(ie is this) { 19509 if(idx + 1 < this.containingBlock.parts.length) 19510 next = this.containingBlock.parts[idx + 1]; 19511 break; 19512 } 19513 } 19514 if(next is null) { 19515 BlockElement n; 19516 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 19517 if(ie is this.containingBlock) { 19518 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 19519 n = this.containingBlock.containingLayout.blocks[idx + 1]; 19520 break; 19521 } 19522 } 19523 if(n is null) 19524 return null; 19525 19526 if(n.parts.length) 19527 next = n.parts[0]; 19528 else {} // FIXME 19529 19530 } 19531 return next; 19532 } 19533 19534 } 19535 19536 // Block elements are used entirely for positioning inline elements, 19537 // which are the things that are actually drawn. 19538 class BlockElement { 19539 InlineElement[] parts; 19540 uint alignment; 19541 19542 int whiteSpace; // pre, pre-wrap, wrap 19543 19544 TextLayout containingLayout; 19545 19546 // inputs 19547 Point where; 19548 Size minimumSize; 19549 Size maximumSize; 19550 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 19551 void* identifier; 19552 19553 Rectangle margin; 19554 Rectangle padding; 19555 19556 // outputs 19557 Rectangle[] boundingBoxes; 19558 } 19559 19560 struct TextIdentifyResult { 19561 InlineElement element; 19562 int offset; 19563 19564 private TextIdentifyResult fixupNewline() { 19565 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 19566 offset--; 19567 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 19568 offset--; 19569 } 19570 return this; 19571 } 19572 } 19573 19574 class TextLayout { 19575 BlockElement[] blocks; 19576 Rectangle boundingBox_; 19577 Rectangle boundingBox() { return boundingBox_; } 19578 void boundingBox(Rectangle r) { 19579 if(r != boundingBox_) { 19580 boundingBox_ = r; 19581 layoutInvalidated = true; 19582 } 19583 } 19584 19585 Rectangle contentBoundingBox() { 19586 Rectangle r; 19587 foreach(block; blocks) 19588 foreach(ie; block.parts) { 19589 if(ie.boundingBox.right > r.right) 19590 r.right = ie.boundingBox.right; 19591 if(ie.boundingBox.bottom > r.bottom) 19592 r.bottom = ie.boundingBox.bottom; 19593 } 19594 return r; 19595 } 19596 19597 BlockElement[] getBlocks() { 19598 return blocks; 19599 } 19600 19601 InlineElement[] getTexts() { 19602 InlineElement[] elements; 19603 foreach(block; blocks) 19604 elements ~= block.parts; 19605 return elements; 19606 } 19607 19608 string getPlainText() { 19609 string text; 19610 foreach(block; blocks) 19611 foreach(part; block.parts) 19612 text ~= part.text; 19613 return text; 19614 } 19615 19616 string getHtml() { 19617 return null; // FIXME 19618 } 19619 19620 this(Rectangle boundingBox) { 19621 this.boundingBox = boundingBox; 19622 } 19623 19624 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 19625 auto be = new BlockElement(); 19626 be.containingLayout = this; 19627 if(after is null) 19628 blocks ~= be; 19629 else { 19630 foreach(idx, b; blocks) { 19631 if(b is after.containingBlock) { 19632 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 19633 break; 19634 } 19635 } 19636 } 19637 return be; 19638 } 19639 19640 void clear() { 19641 blocks = null; 19642 selectionStart = selectionEnd = caret = Caret.init; 19643 } 19644 19645 void addText(Args...)(Args args) { 19646 if(blocks.length == 0) 19647 addBlock(); 19648 19649 InlineElement ie = new InlineElement(); 19650 foreach(idx, arg; args) { 19651 static if(is(typeof(arg) == ForegroundColor)) 19652 ie.color = arg; 19653 else static if(is(typeof(arg) == TextFormat)) { 19654 if(arg & 0x8000) // ~TextFormat.something turns it off 19655 ie.styles &= arg; 19656 else 19657 ie.styles |= arg; 19658 } else static if(is(typeof(arg) == string)) { 19659 static if(idx == 0 && args.length > 1) 19660 static assert(0, "Put styles before the string."); 19661 size_t lastLineIndex; 19662 foreach(cidx, char a; arg) { 19663 if(a == '\n') { 19664 ie.text = arg[lastLineIndex .. cidx + 1]; 19665 lastLineIndex = cidx + 1; 19666 ie.containingBlock = blocks[$-1]; 19667 blocks[$-1].parts ~= ie.clone; 19668 ie.text = null; 19669 } else { 19670 19671 } 19672 } 19673 19674 ie.text = arg[lastLineIndex .. $]; 19675 ie.containingBlock = blocks[$-1]; 19676 blocks[$-1].parts ~= ie.clone; 19677 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 19678 } 19679 } 19680 19681 invalidateLayout(); 19682 } 19683 19684 void tryMerge(InlineElement into, InlineElement what) { 19685 if(!into.isMergeCompatible(what)) { 19686 return; // cannot merge, different configs 19687 } 19688 19689 // cool, can merge, bring text together... 19690 into.text ~= what.text; 19691 19692 // and remove what 19693 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 19694 if(what.containingBlock.parts[a] is what) { 19695 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 19696 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 19697 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 19698 19699 } 19700 } 19701 19702 // FIXME: ensure no other carets have a reference to it 19703 } 19704 19705 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 19706 TextIdentifyResult identify(int x, int y, bool exact = false) { 19707 TextIdentifyResult inexactMatch; 19708 foreach(block; blocks) { 19709 foreach(part; block.parts) { 19710 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 19711 19712 // FIXME binary search 19713 int tidx; 19714 int lastX; 19715 foreach_reverse(idxo, lx; part.letterXs) { 19716 int idx = cast(int) idxo; 19717 if(lx <= x) { 19718 if(lastX && lastX - x < x - lx) 19719 tidx = idx + 1; 19720 else 19721 tidx = idx; 19722 break; 19723 } 19724 lastX = lx; 19725 } 19726 19727 return TextIdentifyResult(part, tidx).fixupNewline; 19728 } else if(!exact) { 19729 // we're not in the box, but are we on the same line? 19730 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 19731 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 19732 } 19733 } 19734 } 19735 19736 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 19737 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 19738 19739 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 19740 } 19741 19742 void moveCaretToPixelCoordinates(int x, int y) { 19743 auto result = identify(x, y); 19744 caret.inlineElement = result.element; 19745 caret.offset = result.offset; 19746 } 19747 19748 void selectToPixelCoordinates(int x, int y) { 19749 auto result = identify(x, y); 19750 19751 if(y < caretLastDrawnY1) { 19752 // on a previous line, carat is selectionEnd 19753 selectionEnd = caret; 19754 19755 selectionStart = Caret(this, result.element, result.offset); 19756 } else if(y > caretLastDrawnY2) { 19757 // on a later line 19758 selectionStart = caret; 19759 19760 selectionEnd = Caret(this, result.element, result.offset); 19761 } else { 19762 // on the same line... 19763 if(x <= caretLastDrawnX) { 19764 selectionEnd = caret; 19765 selectionStart = Caret(this, result.element, result.offset); 19766 } else { 19767 selectionStart = caret; 19768 selectionEnd = Caret(this, result.element, result.offset); 19769 } 19770 19771 } 19772 } 19773 19774 19775 /// Call this if the inputs change. It will reflow everything 19776 void redoLayout(ScreenPainter painter) { 19777 //painter.setClipRectangle(boundingBox); 19778 auto pos = Point(boundingBox.left, boundingBox.top); 19779 19780 int lastHeight; 19781 void nl() { 19782 pos.x = boundingBox.left; 19783 pos.y += lastHeight; 19784 } 19785 foreach(block; blocks) { 19786 nl(); 19787 foreach(part; block.parts) { 19788 part.letterXs = null; 19789 19790 auto size = painter.textSize(part.text); 19791 version(Windows) 19792 if(part.text.length && part.text[$-1] == '\n') 19793 size.height /= 2; // windows counts the new line at the end, but we don't want that 19794 19795 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 19796 19797 foreach(idx, char c; part.text) { 19798 // FIXME: unicode 19799 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 19800 } 19801 19802 pos.x += size.width; 19803 if(pos.x >= boundingBox.right) { 19804 pos.y += size.height; 19805 pos.x = boundingBox.left; 19806 lastHeight = 0; 19807 } else { 19808 lastHeight = size.height; 19809 } 19810 19811 if(part.text.length && part.text[$-1] == '\n') 19812 nl(); 19813 } 19814 } 19815 19816 layoutInvalidated = false; 19817 } 19818 19819 bool layoutInvalidated = true; 19820 void invalidateLayout() { 19821 layoutInvalidated = true; 19822 } 19823 19824 // FIXME: caret can remain sometimes when inserting 19825 // FIXME: inserting at the beginning once you already have something can eff it up. 19826 void drawInto(ScreenPainter painter, bool focused = false) { 19827 if(layoutInvalidated) 19828 redoLayout(painter); 19829 foreach(block; blocks) { 19830 foreach(part; block.parts) { 19831 painter.outlineColor = part.color; 19832 painter.fillColor = part.backgroundColor; 19833 19834 auto pos = part.boundingBox.upperLeft; 19835 auto size = part.boundingBox.size; 19836 19837 painter.drawText(pos, part.text); 19838 if(part.styles & TextFormat.underline) 19839 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 19840 if(part.styles & TextFormat.strikethrough) 19841 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 19842 } 19843 } 19844 19845 // on every redraw, I will force the caret to be 19846 // redrawn too, in order to eliminate perceived lag 19847 // when moving around with the mouse. 19848 eraseCaret(painter); 19849 19850 if(focused) { 19851 highlightSelection(painter); 19852 drawCaret(painter); 19853 } 19854 } 19855 19856 Color selectionXorColor = Color(255, 255, 127); 19857 19858 void highlightSelection(ScreenPainter painter) { 19859 if(selectionStart is selectionEnd) 19860 return; // no selection 19861 19862 if(selectionStart.inlineElement is null) return; 19863 if(selectionEnd.inlineElement is null) return; 19864 19865 assert(selectionStart.inlineElement !is null); 19866 assert(selectionEnd.inlineElement !is null); 19867 19868 painter.rasterOp = RasterOp.xor; 19869 painter.outlineColor = Color.transparent; 19870 painter.fillColor = selectionXorColor; 19871 19872 auto at = selectionStart.inlineElement; 19873 auto atOffset = selectionStart.offset; 19874 bool done; 19875 while(at) { 19876 auto box = at.boundingBox; 19877 if(atOffset < at.letterXs.length) 19878 box.left = at.letterXs[atOffset]; 19879 19880 if(at is selectionEnd.inlineElement) { 19881 if(selectionEnd.offset < at.letterXs.length) 19882 box.right = at.letterXs[selectionEnd.offset]; 19883 done = true; 19884 } 19885 19886 painter.drawRectangle(box.upperLeft, box.width, box.height); 19887 19888 if(done) 19889 break; 19890 19891 at = at.getNextInlineElement(); 19892 atOffset = 0; 19893 } 19894 } 19895 19896 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 19897 bool caretShowingOnScreen = false; 19898 void drawCaret(ScreenPainter painter) { 19899 //painter.setClipRectangle(boundingBox); 19900 int x, y1, y2; 19901 if(caret.inlineElement is null) { 19902 x = boundingBox.left; 19903 y1 = boundingBox.top + 2; 19904 y2 = boundingBox.top + painter.fontHeight; 19905 } else { 19906 x = caret.inlineElement.xOfIndex(caret.offset); 19907 y1 = caret.inlineElement.boundingBox.top + 2; 19908 y2 = caret.inlineElement.boundingBox.bottom - 2; 19909 } 19910 19911 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 19912 eraseCaret(painter); 19913 19914 painter.pen = Pen(Color.white, 1); 19915 painter.rasterOp = RasterOp.xor; 19916 painter.drawLine( 19917 Point(x, y1), 19918 Point(x, y2) 19919 ); 19920 painter.rasterOp = RasterOp.normal; 19921 caretShowingOnScreen = !caretShowingOnScreen; 19922 19923 if(caretShowingOnScreen) { 19924 caretLastDrawnX = x; 19925 caretLastDrawnY1 = y1; 19926 caretLastDrawnY2 = y2; 19927 } 19928 } 19929 19930 Rectangle caretBoundingBox() { 19931 int x, y1, y2; 19932 if(caret.inlineElement is null) { 19933 x = boundingBox.left; 19934 y1 = boundingBox.top + 2; 19935 y2 = boundingBox.top + 16; 19936 } else { 19937 x = caret.inlineElement.xOfIndex(caret.offset); 19938 y1 = caret.inlineElement.boundingBox.top + 2; 19939 y2 = caret.inlineElement.boundingBox.bottom - 2; 19940 } 19941 19942 return Rectangle(x, y1, x + 1, y2); 19943 } 19944 19945 void eraseCaret(ScreenPainter painter) { 19946 //painter.setClipRectangle(boundingBox); 19947 if(!caretShowingOnScreen) return; 19948 painter.pen = Pen(Color.white, 1); 19949 painter.rasterOp = RasterOp.xor; 19950 painter.drawLine( 19951 Point(caretLastDrawnX, caretLastDrawnY1), 19952 Point(caretLastDrawnX, caretLastDrawnY2) 19953 ); 19954 19955 caretShowingOnScreen = false; 19956 painter.rasterOp = RasterOp.normal; 19957 } 19958 19959 /// Caret movement api 19960 /// These should give the user a logical result based on what they see on screen... 19961 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 19962 void moveUp() { 19963 if(caret.inlineElement is null) return; 19964 auto x = caret.inlineElement.xOfIndex(caret.offset); 19965 auto y = caret.inlineElement.boundingBox.top + 2; 19966 19967 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 19968 if(y < 0) 19969 return; 19970 19971 auto i = identify(x, y); 19972 19973 if(i.element) { 19974 caret.inlineElement = i.element; 19975 caret.offset = i.offset; 19976 } 19977 } 19978 void moveDown() { 19979 if(caret.inlineElement is null) return; 19980 auto x = caret.inlineElement.xOfIndex(caret.offset); 19981 auto y = caret.inlineElement.boundingBox.bottom - 2; 19982 19983 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 19984 19985 auto i = identify(x, y); 19986 if(i.element) { 19987 caret.inlineElement = i.element; 19988 caret.offset = i.offset; 19989 } 19990 } 19991 void moveLeft() { 19992 if(caret.inlineElement is null) return; 19993 if(caret.offset) 19994 caret.offset--; 19995 else { 19996 auto p = caret.inlineElement.getPreviousInlineElement(); 19997 if(p) { 19998 caret.inlineElement = p; 19999 if(p.text.length && p.text[$-1] == '\n') 20000 caret.offset = cast(int) p.text.length - 1; 20001 else 20002 caret.offset = cast(int) p.text.length; 20003 } 20004 } 20005 } 20006 void moveRight() { 20007 if(caret.inlineElement is null) return; 20008 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 20009 caret.offset++; 20010 } else { 20011 auto p = caret.inlineElement.getNextInlineElement(); 20012 if(p) { 20013 caret.inlineElement = p; 20014 caret.offset = 0; 20015 } 20016 } 20017 } 20018 void moveHome() { 20019 if(caret.inlineElement is null) return; 20020 auto x = 0; 20021 auto y = caret.inlineElement.boundingBox.top + 2; 20022 20023 auto i = identify(x, y); 20024 20025 if(i.element) { 20026 caret.inlineElement = i.element; 20027 caret.offset = i.offset; 20028 } 20029 } 20030 void moveEnd() { 20031 if(caret.inlineElement is null) return; 20032 auto x = int.max; 20033 auto y = caret.inlineElement.boundingBox.top + 2; 20034 20035 auto i = identify(x, y); 20036 20037 if(i.element) { 20038 caret.inlineElement = i.element; 20039 caret.offset = i.offset; 20040 } 20041 20042 } 20043 void movePageUp(ref Caret caret) {} 20044 void movePageDown(ref Caret caret) {} 20045 20046 void moveDocumentStart(ref Caret caret) { 20047 if(blocks.length && blocks[0].parts.length) 20048 caret = Caret(this, blocks[0].parts[0], 0); 20049 else 20050 caret = Caret.init; 20051 } 20052 20053 void moveDocumentEnd(ref Caret caret) { 20054 if(blocks.length) { 20055 auto parts = blocks[$-1].parts; 20056 if(parts.length) { 20057 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 20058 } else { 20059 caret = Caret.init; 20060 } 20061 } else 20062 caret = Caret.init; 20063 } 20064 20065 void deleteSelection() { 20066 if(selectionStart is selectionEnd) 20067 return; 20068 20069 if(selectionStart.inlineElement is null) return; 20070 if(selectionEnd.inlineElement is null) return; 20071 20072 assert(selectionStart.inlineElement !is null); 20073 assert(selectionEnd.inlineElement !is null); 20074 20075 auto at = selectionStart.inlineElement; 20076 20077 if(selectionEnd.inlineElement is at) { 20078 // same element, need to chop out 20079 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 20080 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 20081 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 20082 } else { 20083 // different elements, we can do it with slicing 20084 at.text = at.text[0 .. selectionStart.offset]; 20085 if(selectionStart.offset < at.letterXs.length) 20086 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 20087 20088 at = at.getNextInlineElement(); 20089 20090 while(at) { 20091 if(at is selectionEnd.inlineElement) { 20092 at.text = at.text[selectionEnd.offset .. $]; 20093 if(selectionEnd.offset < at.letterXs.length) 20094 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 20095 selectionEnd.offset = 0; 20096 break; 20097 } else { 20098 auto cfd = at; 20099 cfd.text = null; // delete the whole thing 20100 20101 at = at.getNextInlineElement(); 20102 20103 if(cfd.text.length == 0) { 20104 // and remove cfd 20105 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 20106 if(cfd.containingBlock.parts[a] is cfd) { 20107 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 20108 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 20109 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 20110 20111 } 20112 } 20113 } 20114 } 20115 } 20116 } 20117 20118 caret = selectionEnd; 20119 selectNone(); 20120 20121 invalidateLayout(); 20122 20123 } 20124 20125 /// Plain text editing api. These work at the current caret inside the selected inline element. 20126 void insert(in char[] text) { 20127 foreach(dchar ch; text) 20128 insert(ch); 20129 } 20130 /// ditto 20131 void insert(dchar ch) { 20132 20133 bool selectionDeleted = false; 20134 if(selectionStart !is selectionEnd) { 20135 deleteSelection(); 20136 selectionDeleted = true; 20137 } 20138 20139 if(ch == 127) { 20140 delete_(); 20141 return; 20142 } 20143 if(ch == 8) { 20144 if(!selectionDeleted) 20145 backspace(); 20146 return; 20147 } 20148 20149 invalidateLayout(); 20150 20151 if(ch == 13) ch = 10; 20152 auto e = caret.inlineElement; 20153 if(e is null) { 20154 addText("" ~ cast(char) ch) ; // FIXME 20155 return; 20156 } 20157 20158 if(caret.offset == e.text.length) { 20159 e.text ~= cast(char) ch; // FIXME 20160 caret.offset++; 20161 if(ch == 10) { 20162 auto c = caret.inlineElement.clone; 20163 c.text = null; 20164 c.letterXs = null; 20165 insertPartAfter(c,e); 20166 caret = Caret(this, c, 0); 20167 } 20168 } else { 20169 // FIXME cast char sucks 20170 if(ch == 10) { 20171 auto c = caret.inlineElement.clone; 20172 c.text = e.text[caret.offset .. $]; 20173 if(caret.offset < c.letterXs.length) 20174 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 20175 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 20176 if(caret.offset <= e.letterXs.length) { 20177 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 20178 } 20179 insertPartAfter(c,e); 20180 caret = Caret(this, c, 0); 20181 } else { 20182 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 20183 caret.offset++; 20184 } 20185 } 20186 } 20187 20188 void insertPartAfter(InlineElement what, InlineElement where) { 20189 foreach(idx, p; where.containingBlock.parts) { 20190 if(p is where) { 20191 if(idx + 1 == where.containingBlock.parts.length) 20192 where.containingBlock.parts ~= what; 20193 else 20194 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 20195 return; 20196 } 20197 } 20198 } 20199 20200 void cleanupStructures() { 20201 for(size_t i = 0; i < blocks.length; i++) { 20202 auto block = blocks[i]; 20203 for(size_t a = 0; a < block.parts.length; a++) { 20204 auto part = block.parts[a]; 20205 if(part.text.length == 0) { 20206 for(size_t b = a; b < block.parts.length - 1; b++) 20207 block.parts[b] = block.parts[b+1]; 20208 block.parts = block.parts[0 .. $-1]; 20209 } 20210 } 20211 if(block.parts.length == 0) { 20212 for(size_t a = i; a < blocks.length - 1; a++) 20213 blocks[a] = blocks[a+1]; 20214 blocks = blocks[0 .. $-1]; 20215 } 20216 } 20217 } 20218 20219 void backspace() { 20220 try_again: 20221 auto e = caret.inlineElement; 20222 if(e is null) 20223 return; 20224 if(caret.offset == 0) { 20225 auto prev = e.getPreviousInlineElement(); 20226 if(prev is null) 20227 return; 20228 auto newOffset = cast(int) prev.text.length; 20229 tryMerge(prev, e); 20230 caret.inlineElement = prev; 20231 caret.offset = prev is null ? 0 : newOffset; 20232 20233 goto try_again; 20234 } else if(caret.offset == e.text.length) { 20235 e.text = e.text[0 .. $-1]; 20236 caret.offset--; 20237 } else { 20238 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 20239 caret.offset--; 20240 } 20241 //cleanupStructures(); 20242 20243 invalidateLayout(); 20244 } 20245 void delete_() { 20246 if(selectionStart !is selectionEnd) 20247 deleteSelection(); 20248 else { 20249 auto before = caret; 20250 moveRight(); 20251 if(caret != before) { 20252 backspace(); 20253 } 20254 } 20255 20256 invalidateLayout(); 20257 } 20258 void overstrike() {} 20259 20260 /// Selection API. See also: caret movement. 20261 void selectAll() { 20262 moveDocumentStart(selectionStart); 20263 moveDocumentEnd(selectionEnd); 20264 } 20265 bool selectNone() { 20266 if(selectionStart != selectionEnd) { 20267 selectionStart = selectionEnd = Caret.init; 20268 return true; 20269 } 20270 return false; 20271 } 20272 20273 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 20274 /// They will modify the current selection if there is one and will splice one in if needed. 20275 void changeAttributes() {} 20276 20277 20278 /// Text search api. They manipulate the selection and/or caret. 20279 void findText(string text) {} 20280 void findIndex(size_t textIndex) {} 20281 20282 // sample event handlers 20283 20284 void handleEvent(KeyEvent event) { 20285 //if(event.type == KeyEvent.Type.KeyPressed) { 20286 20287 //} 20288 } 20289 20290 void handleEvent(dchar ch) { 20291 20292 } 20293 20294 void handleEvent(MouseEvent event) { 20295 20296 } 20297 20298 bool contentEditable; // can it be edited? 20299 bool contentCaretable; // is there a caret/cursor that moves around in there? 20300 bool contentSelectable; // selectable? 20301 20302 Caret caret; 20303 Caret selectionStart; 20304 Caret selectionEnd; 20305 20306 bool insertMode; 20307 } 20308 20309 struct Caret { 20310 TextLayout layout; 20311 InlineElement inlineElement; 20312 int offset; 20313 } 20314 20315 enum TextFormat : ushort { 20316 // decorations 20317 underline = 1, 20318 strikethrough = 2, 20319 20320 // font selectors 20321 20322 bold = 0x4000 | 1, // weight 700 20323 light = 0x4000 | 2, // weight 300 20324 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 20325 // bold | light is really invalid but should give weight 500 20326 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 20327 20328 italic = 0x4000 | 8, 20329 smallcaps = 0x4000 | 16, 20330 } 20331 20332 void* findFont(string family, int weight, TextFormat formats) { 20333 return null; 20334 } 20335 20336 } 20337 20338 /++ 20339 $(PITFALL This is not yet stable and may break in future versions without notice.) 20340 20341 History: 20342 Added February 19, 2021 20343 +/ 20344 /// Group: drag_and_drop 20345 interface DropHandler { 20346 /++ 20347 Called when the drag enters the handler's area. 20348 +/ 20349 DragAndDropAction dragEnter(DropPackage*); 20350 /++ 20351 Called when the drag leaves the handler's area or is 20352 cancelled. You should free your resources when this is called. 20353 +/ 20354 void dragLeave(); 20355 /++ 20356 Called continually as the drag moves over the handler's area. 20357 20358 Returns: feedback to the dragger 20359 +/ 20360 DropParameters dragOver(Point pt); 20361 /++ 20362 The user dropped the data and you should process it now. You can 20363 access the data through the given [DropPackage]. 20364 +/ 20365 void drop(scope DropPackage*); 20366 /++ 20367 Called when the drop is complete. You should free whatever temporary 20368 resources you were using. It is often reasonable to simply forward 20369 this call to [dragLeave]. 20370 +/ 20371 void finish(); 20372 20373 /++ 20374 Parameters returned by [DropHandler.drop]. 20375 +/ 20376 static struct DropParameters { 20377 /++ 20378 Acceptable action over this area. 20379 +/ 20380 DragAndDropAction action; 20381 /++ 20382 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 20383 20384 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 20385 +/ 20386 Rectangle consistentWithin; 20387 } 20388 } 20389 20390 /++ 20391 History: 20392 Added February 19, 2021 20393 +/ 20394 /// Group: drag_and_drop 20395 enum DragAndDropAction { 20396 none = 0, 20397 copy, 20398 move, 20399 link, 20400 ask, 20401 custom 20402 } 20403 20404 /++ 20405 An opaque structure representing dropped data. It contains 20406 private, platform-specific data that your `drop` function 20407 should simply forward. 20408 20409 $(PITFALL This is not yet stable and may break in future versions without notice.) 20410 20411 History: 20412 Added February 19, 2021 20413 +/ 20414 /// Group: drag_and_drop 20415 struct DropPackage { 20416 /++ 20417 Lists the available formats as magic numbers. You should compare these 20418 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 20419 understand the passed data. 20420 +/ 20421 DraggableData.FormatId[] availableFormats() { 20422 version(X11) { 20423 return xFormats; 20424 } else version(Windows) { 20425 if(pDataObj is null) 20426 return null; 20427 20428 typeof(return) ret; 20429 20430 IEnumFORMATETC ef; 20431 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 20432 FORMATETC fmt; 20433 ULONG fetched; 20434 while(ef.Next(1, &fmt, &fetched) == S_OK) { 20435 if(fetched == 0) 20436 break; 20437 20438 if(fmt.lindex != -1) 20439 continue; 20440 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 20441 continue; 20442 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 20443 continue; 20444 20445 ret ~= fmt.cfFormat; 20446 } 20447 } 20448 20449 return ret; 20450 } 20451 } 20452 20453 /++ 20454 Gets data from the drop and optionally accepts it. 20455 20456 Returns: 20457 void because the data is fed asynchronously through the `dg` parameter. 20458 20459 Params: 20460 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. 20461 20462 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. 20463 20464 Calling `getData` again after accepting a drop is not permitted. 20465 20466 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 20467 20468 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. 20469 20470 Throws: 20471 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 20472 20473 History: 20474 Included in first release of [DropPackage]. 20475 +/ 20476 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 20477 version(X11) { 20478 20479 auto display = XDisplayConnection.get(); 20480 auto selectionAtom = GetAtom!"XdndSelection"(display); 20481 auto best = format; 20482 20483 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 20484 20485 XDisplay* display; 20486 Atom selectionAtom; 20487 DraggableData.FormatId best; 20488 DraggableData.FormatId format; 20489 void delegate(scope ubyte[] data) dg; 20490 DragAndDropAction acceptedAction; 20491 Window sourceWindow; 20492 SimpleWindow win; 20493 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 20494 this.display = display; 20495 this.win = win; 20496 this.sourceWindow = sourceWindow; 20497 this.format = format; 20498 this.selectionAtom = selectionAtom; 20499 this.best = best; 20500 this.dg = dg; 20501 this.acceptedAction = acceptedAction; 20502 } 20503 20504 20505 mixin X11GetSelectionHandler_Basics; 20506 20507 void handleData(Atom target, in ubyte[] data) { 20508 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 20509 20510 dg(cast(ubyte[]) data); 20511 20512 if(acceptedAction != DragAndDropAction.none) { 20513 auto display = XDisplayConnection.get; 20514 20515 XClientMessageEvent xclient; 20516 20517 xclient.type = EventType.ClientMessage; 20518 xclient.window = sourceWindow; 20519 xclient.message_type = GetAtom!"XdndFinished"(display); 20520 xclient.format = 32; 20521 xclient.data.l[0] = win.impl.window; 20522 xclient.data.l[1] = 1; // drop successful 20523 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 20524 20525 XSendEvent( 20526 display, 20527 sourceWindow, 20528 false, 20529 EventMask.NoEventMask, 20530 cast(XEvent*) &xclient 20531 ); 20532 20533 XFlush(display); 20534 } 20535 } 20536 20537 Atom findBestFormat(Atom[] answer) { 20538 Atom best = None; 20539 foreach(option; answer) { 20540 if(option == format) { 20541 best = option; 20542 break; 20543 } 20544 /* 20545 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 20546 best = option; 20547 break; 20548 } else if(option == XA_STRING) { 20549 best = option; 20550 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 20551 best = option; 20552 } 20553 */ 20554 } 20555 return best; 20556 } 20557 } 20558 20559 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 20560 20561 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 20562 20563 } else version(Windows) { 20564 20565 // clean up like DragLeave 20566 // pass effect back up 20567 20568 FORMATETC t; 20569 assert(format >= 0 && format <= ushort.max); 20570 t.cfFormat = cast(ushort) format; 20571 t.lindex = -1; 20572 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 20573 t.tymed = TYMED.TYMED_HGLOBAL; 20574 20575 STGMEDIUM m; 20576 20577 if(pDataObj.GetData(&t, &m) != S_OK) { 20578 // fail 20579 } else { 20580 // succeed, take the data and clean up 20581 20582 // FIXME: ensure it is legit HGLOBAL 20583 auto handle = m.hGlobal; 20584 20585 if(handle) { 20586 auto sz = GlobalSize(handle); 20587 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 20588 scope(exit) GlobalUnlock(handle); 20589 scope(exit) GlobalFree(handle); 20590 20591 auto data = ptr[0 .. sz]; 20592 20593 dg(data); 20594 } 20595 } 20596 } 20597 } 20598 } 20599 20600 private: 20601 20602 version(X11) { 20603 SimpleWindow win; 20604 Window sourceWindow; 20605 Time dataTimestamp; 20606 20607 Atom[] xFormats; 20608 } 20609 version(Windows) { 20610 IDataObject pDataObj; 20611 } 20612 } 20613 20614 /++ 20615 A generic helper base class for making a drop handler with a preference list of custom types. 20616 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 20617 droppers too. 20618 20619 It assumes the whole window it used, but you can subclass to change that. 20620 20621 $(PITFALL This is not yet stable and may break in future versions without notice.) 20622 20623 History: 20624 Added February 19, 2021 20625 +/ 20626 /// Group: drag_and_drop 20627 class GenericDropHandlerBase : DropHandler { 20628 // no fancy state here so no need to do anything here 20629 void finish() { } 20630 void dragLeave() { } 20631 20632 private DragAndDropAction acceptedAction; 20633 private DraggableData.FormatId acceptedFormat; 20634 private void delegate(scope ubyte[]) acceptedHandler; 20635 20636 struct FormatHandler { 20637 DraggableData.FormatId format; 20638 void delegate(scope ubyte[]) handler; 20639 } 20640 20641 protected abstract FormatHandler[] formatHandlers(); 20642 20643 DragAndDropAction dragEnter(DropPackage* pkg) { 20644 debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 20645 foreach(fmt; formatHandlers()) 20646 foreach(f; pkg.availableFormats()) 20647 if(f == fmt.format) { 20648 acceptedFormat = f; 20649 acceptedHandler = fmt.handler; 20650 return acceptedAction = DragAndDropAction.copy; 20651 } 20652 return acceptedAction = DragAndDropAction.none; 20653 } 20654 DropParameters dragOver(Point pt) { 20655 return DropParameters(acceptedAction); 20656 } 20657 20658 void drop(scope DropPackage* dropPackage) { 20659 if(!acceptedFormat || acceptedHandler is null) { 20660 debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 20661 return; // prolly shouldn't happen anyway... 20662 } 20663 20664 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 20665 } 20666 } 20667 20668 /++ 20669 A simple handler for making your window accept drops of plain text. 20670 20671 $(PITFALL This is not yet stable and may break in future versions without notice.) 20672 20673 History: 20674 Added February 22, 2021 20675 +/ 20676 /// Group: drag_and_drop 20677 class TextDropHandler : GenericDropHandlerBase { 20678 private void delegate(in char[] text) dg; 20679 20680 /++ 20681 20682 +/ 20683 this(void delegate(in char[] text) dg) { 20684 this.dg = dg; 20685 } 20686 20687 protected override FormatHandler[] formatHandlers() { 20688 version(X11) 20689 return [ 20690 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 20691 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 20692 ]; 20693 else version(Windows) 20694 return [ 20695 FormatHandler(CF_UNICODETEXT, &translator), 20696 ]; 20697 } 20698 20699 private void translator(scope ubyte[] data) { 20700 version(X11) 20701 dg(cast(char[]) data); 20702 else version(Windows) 20703 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 20704 } 20705 } 20706 20707 /++ 20708 A simple handler for making your window accept drops of files, issued to you as file names. 20709 20710 $(PITFALL This is not yet stable and may break in future versions without notice.) 20711 20712 History: 20713 Added February 22, 2021 20714 +/ 20715 /// Group: drag_and_drop 20716 20717 class FilesDropHandler : GenericDropHandlerBase { 20718 private void delegate(in char[][]) dg; 20719 20720 /++ 20721 20722 +/ 20723 this(void delegate(in char[][] fileNames) dg) { 20724 this.dg = dg; 20725 } 20726 20727 protected override FormatHandler[] formatHandlers() { 20728 version(X11) 20729 return [ 20730 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 20731 ]; 20732 else version(Windows) 20733 return [ 20734 FormatHandler(CF_HDROP, &translator), 20735 ]; 20736 } 20737 20738 private void translator(scope ubyte[] data) { 20739 version(X11) { 20740 char[] listString = cast(char[]) data; 20741 char[][16] buffer; 20742 int count; 20743 char[][] result = buffer[]; 20744 20745 void commit(char[] s) { 20746 if(count == result.length) 20747 result.length += 16; 20748 if(s.length > 7 && s[0 ..7] == "file://") 20749 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 20750 result[count++] = s; 20751 } 20752 20753 size_t last; 20754 foreach(idx, char c; listString) { 20755 if(c == '\n') { 20756 commit(listString[last .. idx - 1]); // a \r 20757 last = idx + 1; // a \n 20758 } 20759 } 20760 20761 if(last < listString.length) { 20762 commit(listString[last .. $]); 20763 } 20764 20765 // FIXME: they are uris now, should I translate it to local file names? 20766 // of course the host name is supposed to be there cuz of X rokking... 20767 20768 dg(result[0 .. count]); 20769 } else version(Windows) { 20770 20771 static struct DROPFILES { 20772 DWORD pFiles; 20773 POINT pt; 20774 BOOL fNC; 20775 BOOL fWide; 20776 } 20777 20778 20779 const(char)[][16] buffer; 20780 int count; 20781 const(char)[][] result = buffer[]; 20782 size_t last; 20783 20784 void commitA(in char[] stuff) { 20785 if(count == result.length) 20786 result.length += 16; 20787 result[count++] = stuff; 20788 } 20789 20790 void commitW(in wchar[] stuff) { 20791 commitA(makeUtf8StringFromWindowsString(stuff)); 20792 } 20793 20794 void magic(T)(T chars) { 20795 size_t idx; 20796 while(chars[idx]) { 20797 last = idx; 20798 while(chars[idx]) { 20799 idx++; 20800 } 20801 static if(is(T == char*)) 20802 commitA(chars[last .. idx]); 20803 else 20804 commitW(chars[last .. idx]); 20805 idx++; 20806 } 20807 } 20808 20809 auto df = cast(DROPFILES*) data.ptr; 20810 if(df.fWide) { 20811 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 20812 magic(chars); 20813 } else { 20814 char* chars = cast(char*) (data.ptr + df.pFiles); 20815 magic(chars); 20816 } 20817 dg(result[0 .. count]); 20818 } 20819 } 20820 } 20821 20822 /++ 20823 Interface to describe data being dragged. See also [draggable] helper function. 20824 20825 $(PITFALL This is not yet stable and may break in future versions without notice.) 20826 20827 History: 20828 Added February 19, 2021 20829 +/ 20830 interface DraggableData { 20831 version(X11) 20832 alias FormatId = Atom; 20833 else 20834 alias FormatId = uint; 20835 /++ 20836 Gets the platform-specific FormatId associated with the given named format. 20837 20838 This may be a MIME type, but may also be other various strings defined by the 20839 programs you want to interoperate with. 20840 20841 FIXME: sdpy needs to offer data adapter things that look for compatible formats 20842 and convert it to some particular type for you. 20843 +/ 20844 static FormatId getFormatId(string name)() { 20845 version(X11) 20846 return GetAtom!name(XDisplayConnection.get); 20847 else version(Windows) { 20848 static UINT cache; 20849 if(!cache) 20850 cache = RegisterClipboardFormatA(name); 20851 return cache; 20852 } else 20853 throw new NotYetImplementedException(); 20854 } 20855 20856 /++ 20857 Looks up a string to represent the name for the given format, if there is one. 20858 20859 You should avoid using this function because it is slow. It is provided more for 20860 debugging than for primary use. 20861 +/ 20862 static string getFormatName(FormatId format) { 20863 version(X11) { 20864 if(format == 0) 20865 return "None"; 20866 else 20867 return getAtomName(format, XDisplayConnection.get); 20868 } else version(Windows) { 20869 switch(format) { 20870 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 20871 case CF_DIBV5: return "CF_DIBV5"; 20872 case CF_RIFF: return "CF_RIFF"; 20873 case CF_WAVE: return "CF_WAVE"; 20874 case CF_HDROP: return "CF_HDROP"; 20875 default: 20876 char[1024] name; 20877 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 20878 return name[0 .. count].idup; 20879 } 20880 } 20881 } 20882 20883 FormatId[] availableFormats(); 20884 // Return the slice of data you filled, empty slice if done. 20885 // this is to support the incremental thing 20886 ubyte[] getData(FormatId format, return scope ubyte[] data); 20887 20888 size_t dataLength(FormatId format); 20889 } 20890 20891 /++ 20892 $(PITFALL This is not yet stable and may break in future versions without notice.) 20893 20894 History: 20895 Added February 19, 2021 20896 +/ 20897 DraggableData draggable(string s) { 20898 version(X11) 20899 return new class X11SetSelectionHandler_Text, DraggableData { 20900 this() { 20901 super(s); 20902 } 20903 20904 override FormatId[] availableFormats() { 20905 return X11SetSelectionHandler_Text.availableFormats(); 20906 } 20907 20908 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 20909 return X11SetSelectionHandler_Text.getData(format, data); 20910 } 20911 20912 size_t dataLength(FormatId format) { 20913 return s.length; 20914 } 20915 }; 20916 version(Windows) 20917 return new class DraggableData { 20918 FormatId[] availableFormats() { 20919 return [CF_UNICODETEXT]; 20920 } 20921 20922 ubyte[] getData(FormatId format, return scope ubyte[] data) { 20923 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 20924 } 20925 20926 size_t dataLength(FormatId format) { 20927 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 20928 } 20929 }; 20930 } 20931 20932 /++ 20933 $(PITFALL This is not yet stable and may break in future versions without notice.) 20934 20935 History: 20936 Added February 19, 2021 20937 +/ 20938 /// Group: drag_and_drop 20939 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 20940 in { 20941 assert(window !is null); 20942 assert(handler !is null); 20943 } 20944 do 20945 { 20946 version(X11) { 20947 auto sh = cast(X11SetSelectionHandler) handler; 20948 if(sh is null) { 20949 // gotta make my own adapter. 20950 sh = new class X11SetSelectionHandler { 20951 mixin X11SetSelectionHandler_Basics; 20952 20953 Atom[] availableFormats() { return handler.availableFormats(); } 20954 ubyte[] getData(Atom format, return scope ubyte[] data) { 20955 return handler.getData(format, data); 20956 } 20957 20958 // since the drop selection is only ever used once it isn't important 20959 // to reset it. 20960 void done() {} 20961 }; 20962 } 20963 return doDragDropX11(window, sh, action); 20964 } else version(Windows) { 20965 return doDragDropWindows(window, handler, action); 20966 } else throw new NotYetImplementedException(); 20967 } 20968 20969 version(Windows) 20970 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 20971 IDataObject obj = new class IDataObject { 20972 ULONG refCount; 20973 ULONG AddRef() { 20974 return ++refCount; 20975 } 20976 ULONG Release() { 20977 return --refCount; 20978 } 20979 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 20980 if (IID_IUnknown == *riid) { 20981 *ppv = cast(void*) cast(IUnknown) this; 20982 } 20983 else if (IID_IDataObject == *riid) { 20984 *ppv = cast(void*) cast(IDataObject) this; 20985 } 20986 else { 20987 *ppv = null; 20988 return E_NOINTERFACE; 20989 } 20990 20991 AddRef(); 20992 return NOERROR; 20993 } 20994 20995 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 20996 // import std.stdio; writeln("Advise"); 20997 return E_NOTIMPL; 20998 } 20999 HRESULT DUnadvise(DWORD dwConnection) { 21000 return E_NOTIMPL; 21001 } 21002 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 21003 // import std.stdio; writeln("EnumDAdvise"); 21004 return OLE_E_ADVISENOTSUPPORTED; 21005 } 21006 // tell what formats it supports 21007 21008 FORMATETC[] types; 21009 this() { 21010 FORMATETC t; 21011 foreach(ty; handler.availableFormats()) { 21012 assert(ty <= ushort.max && ty >= 0); 21013 t.cfFormat = cast(ushort) ty; 21014 t.lindex = -1; 21015 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 21016 t.tymed = TYMED.TYMED_HGLOBAL; 21017 } 21018 types ~= t; 21019 } 21020 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 21021 if(dwDirection == DATADIR.DATADIR_GET) { 21022 *ppenumFormatEtc = new class IEnumFORMATETC { 21023 ULONG refCount; 21024 ULONG AddRef() { 21025 return ++refCount; 21026 } 21027 ULONG Release() { 21028 return --refCount; 21029 } 21030 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21031 if (IID_IUnknown == *riid) { 21032 *ppv = cast(void*) cast(IUnknown) this; 21033 } 21034 else if (IID_IEnumFORMATETC == *riid) { 21035 *ppv = cast(void*) cast(IEnumFORMATETC) this; 21036 } 21037 else { 21038 *ppv = null; 21039 return E_NOINTERFACE; 21040 } 21041 21042 AddRef(); 21043 return NOERROR; 21044 } 21045 21046 21047 int pos; 21048 this() { 21049 pos = 0; 21050 } 21051 21052 HRESULT Clone(IEnumFORMATETC* ppenum) { 21053 // import std.stdio; writeln("clone"); 21054 return E_NOTIMPL; // FIXME 21055 } 21056 21057 // Caller is responsible for freeing memory 21058 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 21059 // fetched may be null if celt is one 21060 if(celt != 1) 21061 return E_NOTIMPL; // FIXME 21062 21063 if(celt + pos > types.length) 21064 return S_FALSE; 21065 21066 *rgelt = types[pos++]; 21067 21068 if(pceltFetched !is null) 21069 *pceltFetched = 1; 21070 21071 // import std.stdio; writeln("ok celt ", celt); 21072 return S_OK; 21073 } 21074 21075 HRESULT Reset() { 21076 pos = 0; 21077 return S_OK; 21078 } 21079 21080 HRESULT Skip(ULONG celt) { 21081 if(celt + pos <= types.length) { 21082 pos += celt; 21083 return S_OK; 21084 } 21085 return S_FALSE; 21086 } 21087 }; 21088 21089 return S_OK; 21090 } else 21091 return E_NOTIMPL; 21092 } 21093 // given a format, return the format you'd prefer to use cuz it is identical 21094 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 21095 // FIXME: prolly could be better but meh 21096 // import std.stdio; writeln("gcf: ", *pformatectIn); 21097 *pformatetcOut = *pformatectIn; 21098 return S_OK; 21099 } 21100 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21101 foreach(ty; types) { 21102 if(ty == *pformatetcIn) { 21103 auto format = ty.cfFormat; 21104 // import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty); 21105 STGMEDIUM medium; 21106 medium.tymed = TYMED.TYMED_HGLOBAL; 21107 21108 auto sz = handler.dataLength(format); 21109 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 21110 if(handle is null) throw new Exception("GlobalAlloc"); 21111 if(auto data = cast(wchar*) GlobalLock(handle)) { 21112 auto slice = data[0 .. sz]; 21113 scope(exit) 21114 GlobalUnlock(handle); 21115 21116 handler.getData(format, cast(ubyte[]) slice[]); 21117 } 21118 21119 21120 medium.hGlobal = handle; // FIXME 21121 *pmedium = medium; 21122 return S_OK; 21123 } 21124 } 21125 return DV_E_FORMATETC; 21126 } 21127 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 21128 // import std.stdio; writeln("GDH: ", *pformatetcIn); 21129 return E_NOTIMPL; // FIXME 21130 } 21131 HRESULT QueryGetData(FORMATETC* pformatetc) { 21132 auto search = *pformatetc; 21133 search.tymed &= TYMED.TYMED_HGLOBAL; 21134 foreach(ty; types) 21135 if(ty == search) { 21136 // import std.stdio; writeln("QueryGetData ", search, " ", types[0]); 21137 return S_OK; 21138 } 21139 if(pformatetc.cfFormat==CF_UNICODETEXT) { 21140 //import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]); 21141 } 21142 return S_FALSE; 21143 } 21144 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 21145 // import std.stdio; writeln("SetData: "); 21146 return E_NOTIMPL; 21147 } 21148 }; 21149 21150 21151 IDropSource src = new class IDropSource { 21152 ULONG refCount; 21153 ULONG AddRef() { 21154 return ++refCount; 21155 } 21156 ULONG Release() { 21157 return --refCount; 21158 } 21159 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21160 if (IID_IUnknown == *riid) { 21161 *ppv = cast(void*) cast(IUnknown) this; 21162 } 21163 else if (IID_IDropSource == *riid) { 21164 *ppv = cast(void*) cast(IDropSource) this; 21165 } 21166 else { 21167 *ppv = null; 21168 return E_NOINTERFACE; 21169 } 21170 21171 AddRef(); 21172 return NOERROR; 21173 } 21174 21175 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 21176 if(fEscapePressed) 21177 return DRAGDROP_S_CANCEL; 21178 if(!(grfKeyState & MK_LBUTTON)) 21179 return DRAGDROP_S_DROP; 21180 return S_OK; 21181 } 21182 21183 int GiveFeedback(uint dwEffect) { 21184 return DRAGDROP_S_USEDEFAULTCURSORS; 21185 } 21186 }; 21187 21188 DWORD effect; 21189 21190 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 21191 21192 DROPEFFECT de = win32DragAndDropAction(action); 21193 21194 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 21195 // but still prolly a FIXME 21196 21197 auto ret = DoDragDrop(obj, src, de, &effect); 21198 /+ 21199 import std.stdio; 21200 if(ret == DRAGDROP_S_DROP) 21201 writeln("drop ", effect); 21202 else if(ret == DRAGDROP_S_CANCEL) 21203 writeln("cancel"); 21204 else if(ret == S_OK) 21205 writeln("ok"); 21206 else writeln(ret); 21207 +/ 21208 21209 return ret; 21210 } 21211 21212 version(Windows) 21213 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 21214 DROPEFFECT de; 21215 21216 with(DragAndDropAction) 21217 with(DROPEFFECT) 21218 final switch(action) { 21219 case none: de = DROPEFFECT_NONE; break; 21220 case copy: de = DROPEFFECT_COPY; break; 21221 case move: de = DROPEFFECT_MOVE; break; 21222 case link: de = DROPEFFECT_LINK; break; 21223 case ask: throw new Exception("ask not implemented yet"); 21224 case custom: throw new Exception("custom not implemented yet"); 21225 } 21226 21227 return de; 21228 } 21229 21230 21231 /++ 21232 History: 21233 Added February 19, 2021 21234 +/ 21235 /// Group: drag_and_drop 21236 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 21237 version(X11) { 21238 auto display = XDisplayConnection.get; 21239 21240 Atom atom = 5; // right??? 21241 21242 XChangeProperty( 21243 display, 21244 window.impl.window, 21245 GetAtom!"XdndAware"(display), 21246 XA_ATOM, 21247 32 /* bits */, 21248 PropModeReplace, 21249 &atom, 21250 1); 21251 21252 window.dropHandler = handler; 21253 } else version(Windows) { 21254 21255 initDnd(); 21256 21257 auto dropTarget = new class (handler) IDropTarget { 21258 DropHandler handler; 21259 this(DropHandler handler) { 21260 this.handler = handler; 21261 } 21262 ULONG refCount; 21263 ULONG AddRef() { 21264 return ++refCount; 21265 } 21266 ULONG Release() { 21267 return --refCount; 21268 } 21269 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 21270 if (IID_IUnknown == *riid) { 21271 *ppv = cast(void*) cast(IUnknown) this; 21272 } 21273 else if (IID_IDropTarget == *riid) { 21274 *ppv = cast(void*) cast(IDropTarget) this; 21275 } 21276 else { 21277 *ppv = null; 21278 return E_NOINTERFACE; 21279 } 21280 21281 AddRef(); 21282 return NOERROR; 21283 } 21284 21285 21286 // /////////////////// 21287 21288 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21289 DropPackage dropPackage = DropPackage(pDataObj); 21290 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 21291 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 21292 } 21293 21294 HRESULT DragLeave() { 21295 handler.dragLeave(); 21296 // release the IDataObject if needed 21297 return S_OK; 21298 } 21299 21300 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21301 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 21302 21303 *pdwEffect = win32DragAndDropAction(res.action); 21304 // same as DragEnter basically 21305 return S_OK; 21306 } 21307 21308 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 21309 DropPackage pkg = DropPackage(pDataObj); 21310 handler.drop(&pkg); 21311 21312 return S_OK; 21313 } 21314 }; 21315 // Windows can hold on to the handler and try to call it 21316 // during which time the GC can't see it. so important to 21317 // manually manage this. At some point i'll FIXME and make 21318 // all my com instances manually managed since they supposed 21319 // to respect the refcount. 21320 import core.memory; 21321 GC.addRoot(cast(void*) dropTarget); 21322 21323 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 21324 throw new Exception("register"); 21325 21326 window.dropHandler = handler; 21327 } else throw new NotYetImplementedException(); 21328 } 21329 21330 21331 21332 static if(UsingSimpledisplayX11) { 21333 21334 enum _NET_WM_STATE_ADD = 1; 21335 enum _NET_WM_STATE_REMOVE = 0; 21336 enum _NET_WM_STATE_TOGGLE = 2; 21337 21338 /// X-specific. Use [SimpleWindow.requestAttention] instead for most cases. 21339 void demandAttention(SimpleWindow window, bool needs = true) { 21340 demandAttention(window.impl.window, needs); 21341 } 21342 21343 /// ditto 21344 void demandAttention(Window window, bool needs = true) { 21345 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 21346 } 21347 21348 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 21349 auto display = XDisplayConnection.get(); 21350 if(atom == None) 21351 return; // non-failure error 21352 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 21353 21354 XClientMessageEvent xclient; 21355 21356 xclient.type = EventType.ClientMessage; 21357 xclient.window = window; 21358 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 21359 xclient.format = 32; 21360 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 21361 xclient.data.l[1] = atom; 21362 xclient.data.l[2] = atom2; 21363 xclient.data.l[3] = 1; 21364 // [3] == source. 0 == unknown, 1 == app, 2 == else 21365 21366 XSendEvent( 21367 display, 21368 RootWindow(display, DefaultScreen(display)), 21369 false, 21370 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 21371 cast(XEvent*) &xclient 21372 ); 21373 21374 /+ 21375 XChangeProperty( 21376 display, 21377 window.impl.window, 21378 GetAtom!"_NET_WM_STATE"(display), 21379 XA_ATOM, 21380 32 /* bits */, 21381 PropModeAppend, 21382 &atom, 21383 1); 21384 +/ 21385 } 21386 21387 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 21388 Atom actionAtom; 21389 with(DragAndDropAction) 21390 final switch(action) { 21391 case none: actionAtom = None; break; 21392 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 21393 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 21394 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 21395 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 21396 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 21397 } 21398 21399 return actionAtom; 21400 } 21401 21402 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 21403 // FIXME: I need to show user feedback somehow. 21404 auto display = XDisplayConnection.get; 21405 21406 auto actionAtom = dndActionAtom(display, action); 21407 assert(actionAtom, "Don't use action none to accept a drop"); 21408 21409 setX11Selection!"XdndSelection"(window, handler, null); 21410 21411 auto oldKeyHandler = window.handleKeyEvent; 21412 scope(exit) window.handleKeyEvent = oldKeyHandler; 21413 21414 auto oldCharHandler = window.handleCharEvent; 21415 scope(exit) window.handleCharEvent = oldCharHandler; 21416 21417 auto oldMouseHandler = window.handleMouseEvent; 21418 scope(exit) window.handleMouseEvent = oldMouseHandler; 21419 21420 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 21421 21422 import core.sys.posix.sys.time; 21423 timeval tv; 21424 gettimeofday(&tv, null); 21425 21426 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 21427 21428 Time lastMouseTimestamp; 21429 21430 bool dnding = true; 21431 Window lastIn = None; 21432 21433 void leave() { 21434 if(lastIn == None) 21435 return; 21436 21437 XEvent ev; 21438 ev.xclient.type = EventType.ClientMessage; 21439 ev.xclient.window = lastIn; 21440 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 21441 ev.xclient.format = 32; 21442 ev.xclient.data.l[0] = window.impl.window; 21443 21444 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21445 XFlush(display); 21446 21447 lastIn = None; 21448 } 21449 21450 void enter(Window w) { 21451 assert(lastIn == None); 21452 21453 lastIn = w; 21454 21455 XEvent ev; 21456 ev.xclient.type = EventType.ClientMessage; 21457 ev.xclient.window = lastIn; 21458 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 21459 ev.xclient.format = 32; 21460 ev.xclient.data.l[0] = window.impl.window; 21461 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 21462 21463 auto types = handler.availableFormats(); 21464 assert(types.length > 0); 21465 21466 ev.xclient.data.l[2] = types[0]; 21467 if(types.length > 1) 21468 ev.xclient.data.l[3] = types[1]; 21469 if(types.length > 2) 21470 ev.xclient.data.l[4] = types[2]; 21471 21472 // FIXME: other types?!?!? and make sure we skip TARGETS 21473 21474 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21475 XFlush(display); 21476 } 21477 21478 void position(int rootX, int rootY) { 21479 assert(lastIn != None); 21480 21481 XEvent ev; 21482 ev.xclient.type = EventType.ClientMessage; 21483 ev.xclient.window = lastIn; 21484 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 21485 ev.xclient.format = 32; 21486 ev.xclient.data.l[0] = window.impl.window; 21487 ev.xclient.data.l[1] = 0; // reserved 21488 ev.xclient.data.l[2] = (rootX << 16) | rootY; 21489 ev.xclient.data.l[3] = dataTimestamp; 21490 ev.xclient.data.l[4] = actionAtom; 21491 21492 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21493 XFlush(display); 21494 21495 } 21496 21497 void drop() { 21498 XEvent ev; 21499 ev.xclient.type = EventType.ClientMessage; 21500 ev.xclient.window = lastIn; 21501 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 21502 ev.xclient.format = 32; 21503 ev.xclient.data.l[0] = window.impl.window; 21504 ev.xclient.data.l[1] = 0; // reserved 21505 ev.xclient.data.l[2] = dataTimestamp; 21506 21507 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 21508 XFlush(display); 21509 21510 lastIn = None; 21511 dnding = false; 21512 } 21513 21514 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 21515 // but idk if i should... 21516 21517 window.setEventHandlers( 21518 delegate(KeyEvent ev) { 21519 if(ev.pressed == true && ev.key == Key.Escape) { 21520 // cancel 21521 dnding = false; 21522 } 21523 }, 21524 delegate(MouseEvent ev) { 21525 if(ev.timestamp < lastMouseTimestamp) 21526 return; 21527 21528 lastMouseTimestamp = ev.timestamp; 21529 21530 if(ev.type == MouseEventType.motion) { 21531 auto display = XDisplayConnection.get; 21532 auto root = RootWindow(display, DefaultScreen(display)); 21533 21534 Window topWindow; 21535 int rootX, rootY; 21536 21537 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 21538 21539 if(topWindow == None) 21540 return; 21541 21542 top: 21543 if(auto result = topWindow in eligibility) { 21544 auto dropWindow = *result; 21545 if(dropWindow == None) { 21546 leave(); 21547 return; 21548 } 21549 21550 if(dropWindow != lastIn) { 21551 leave(); 21552 enter(dropWindow); 21553 position(rootX, rootY); 21554 } else { 21555 position(rootX, rootY); 21556 } 21557 } else { 21558 // determine eligibility 21559 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 21560 if(data.length == 1) { 21561 // in case there is no WM or it isn't reparenting 21562 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 21563 } else { 21564 21565 Window tryScanChildren(Window search, int maxRecurse) { 21566 // could be reparenting window manager, so gotta check the next few children too 21567 Window child; 21568 int x; 21569 int y; 21570 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 21571 21572 if(child == None) 21573 return None; 21574 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 21575 if(data.length == 1) { 21576 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 21577 } else { 21578 if(maxRecurse) 21579 return tryScanChildren(child, maxRecurse - 1); 21580 else 21581 return None; 21582 } 21583 21584 } 21585 21586 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 21587 auto topResult = tryScanChildren(topWindow, 3); 21588 // it is easy to have a false negative due to the mouse going over a WM 21589 // child window like the close button if separate from the frame... so I 21590 // can't really cache negatives, :( 21591 if(topResult != None) { 21592 eligibility[topWindow] = topResult; 21593 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 21594 } 21595 } 21596 21597 } 21598 21599 } else if(ev.type == MouseEventType.buttonReleased) { 21600 drop(); 21601 dnding = false; 21602 } 21603 } 21604 ); 21605 21606 window.grabInput(); 21607 scope(exit) 21608 window.releaseInputGrab(); 21609 21610 21611 EventLoop.get.run(() => dnding); 21612 21613 return 0; 21614 } 21615 21616 /// X-specific 21617 TrueColorImage getWindowNetWmIcon(Window window) { 21618 try { 21619 auto display = XDisplayConnection.get; 21620 21621 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 21622 21623 if (data.length > arch_ulong.sizeof * 2) { 21624 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 21625 // these are an array of rgba images that we have to convert into pixmaps ourself 21626 21627 int width = cast(int) meta[0]; 21628 int height = cast(int) meta[1]; 21629 21630 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 21631 21632 static if(arch_ulong.sizeof == 4) { 21633 bytes = bytes[0 .. width * height * 4]; 21634 alias imageData = bytes; 21635 } else static if(arch_ulong.sizeof == 8) { 21636 bytes = bytes[0 .. width * height * 8]; 21637 auto imageData = new ubyte[](4 * width * height); 21638 } else static assert(0); 21639 21640 21641 21642 // this returns ARGB. Remember it is little-endian so 21643 // we have BGRA 21644 // our thing uses RGBA, which in little endian, is ABGR 21645 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 21646 auto r = bytes[idx + 2]; 21647 auto g = bytes[idx + 1]; 21648 auto b = bytes[idx + 0]; 21649 auto a = bytes[idx + 3]; 21650 21651 imageData[idx2 + 0] = r; 21652 imageData[idx2 + 1] = g; 21653 imageData[idx2 + 2] = b; 21654 imageData[idx2 + 3] = a; 21655 } 21656 21657 return new TrueColorImage(width, height, imageData); 21658 } 21659 21660 return null; 21661 } catch(Exception e) { 21662 return null; 21663 } 21664 } 21665 21666 } /* UsingSimpledisplayX11 */ 21667 21668 21669 void loadBinNameToWindowClassName () { 21670 import core.stdc.stdlib : realloc; 21671 version(linux) { 21672 // args[0] MAY be empty, so we'll just use this 21673 import core.sys.posix.unistd : readlink; 21674 char[1024] ebuf = void; // 1KB should be enough for everyone! 21675 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 21676 if (len < 1) return; 21677 } else /*version(Windows)*/ { 21678 import core.runtime : Runtime; 21679 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 21680 auto ebuf = Runtime.args[0]; 21681 auto len = ebuf.length; 21682 } 21683 auto pos = len; 21684 while (pos > 0 && ebuf[pos-1] != '/') --pos; 21685 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 21686 if (sdpyWindowClassStr is null) return; // oops 21687 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 21688 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 21689 } 21690 21691 /++ 21692 An interface representing a font that is drawn with custom facilities. 21693 21694 You might want [OperatingSystemFont] instead, which represents 21695 a font loaded and drawn by functions native to the operating system. 21696 21697 WARNING: I might still change this. 21698 +/ 21699 interface DrawableFont : MeasurableFont { 21700 /++ 21701 Please note the point is upperLeft, NOT baseline! This is the point of a bounding box of the string. 21702 21703 Implementations must use the painter's fillColor to draw a rectangle behind the string, 21704 then use the outlineColor to draw the string. It might alpha composite if there's a transparent 21705 fill color, but that's up to the implementation. 21706 +/ 21707 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 21708 21709 /++ 21710 Requests that the given string is added to the image cache. You should only do this rarely, but 21711 if you have a string that you know will be used over and over again, adding it to a cache can 21712 improve things (assuming the implementation actually has a cache; it is also valid for an implementation 21713 to implement this as a do-nothing method). 21714 +/ 21715 void cacheString(SimpleWindow window, Color foreground, Color background, string text); 21716 } 21717 21718 /++ 21719 Loads a true type font using [arsd.ttf] that can be drawn as images on windows 21720 through a [ScreenPainter]. That module must be compiled in if you choose to use this function. 21721 21722 You should also consider [OperatingSystemFont], which loads and draws a font with 21723 facilities native to the user's operating system. You might also consider 21724 [arsd.ttf.OpenGlLimitedFont] or using [arsd.nanovega] if you are making some kind 21725 of game, as they have their own ways to draw text too. 21726 21727 Be warned: this can be slow, especially on remote connections to the X server, since 21728 it needs to create and transfer bitmaps instead of just text. The [DrawableFont] interface 21729 offers [DrawableFont.cacheString] which can help with this, sometimes. You might want to 21730 experiment in your specific case. 21731 21732 Please note that the return type of [DrawableFont] also includes an implementation of 21733 [MeasurableFont]. 21734 +/ 21735 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 21736 import arsd.ttf; 21737 static class ArsdTtfFont : DrawableFont { 21738 TtfFont font; 21739 int size; 21740 this(in ubyte[] data, int size) { 21741 font = TtfFont(data); 21742 this.size = size; 21743 21744 21745 auto scale = stbtt_ScaleForPixelHeight(&font.font, size); 21746 int ascent_, descent_, line_gap; 21747 stbtt_GetFontVMetrics(&font.font, &ascent_, &descent_, &line_gap); 21748 21749 int advance, lsb; 21750 stbtt_GetCodepointHMetrics(&font.font, 'x', &advance, &lsb); 21751 xWidth = cast(int) (advance * scale); 21752 stbtt_GetCodepointHMetrics(&font.font, 'M', &advance, &lsb); 21753 MWidth = cast(int) (advance * scale); 21754 } 21755 21756 private int ascent_; 21757 private int descent_; 21758 private int xWidth; 21759 private int MWidth; 21760 21761 bool isMonospace() { 21762 return xWidth == MWidth; 21763 } 21764 int averageWidth() { 21765 return xWidth; 21766 } 21767 int height() { 21768 return size; 21769 } 21770 int ascent() { 21771 return ascent_; 21772 } 21773 int descent() { 21774 return descent_; 21775 } 21776 21777 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 21778 int width, height; 21779 font.getStringSize(s, size, width, height); 21780 return width; 21781 } 21782 21783 21784 21785 Sprite[string] cache; 21786 21787 void cacheString(SimpleWindow window, Color foreground, Color background, string text) { 21788 auto sprite = new Sprite(window, stringToImage(foreground, background, text)); 21789 cache[text] = sprite; 21790 } 21791 21792 Image stringToImage(Color fg, Color bg, in char[] text) { 21793 int width, height; 21794 auto data = font.renderString(text, size, width, height); 21795 auto image = new TrueColorImage(width, height); 21796 int pos = 0; 21797 foreach(y; 0 .. height) 21798 foreach(x; 0 .. width) { 21799 fg.a = data[0]; 21800 bg.a = 255; 21801 auto color = alphaBlend(fg, bg); 21802 image.imageData.bytes[pos++] = color.r; 21803 image.imageData.bytes[pos++] = color.g; 21804 image.imageData.bytes[pos++] = color.b; 21805 image.imageData.bytes[pos++] = data[0]; 21806 data = data[1 .. $]; 21807 } 21808 assert(data.length == 0); 21809 21810 return Image.fromMemoryImage(image); 21811 } 21812 21813 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 21814 Sprite sprite = (text in cache) ? *(text in cache) : null; 21815 21816 auto fg = painter.impl._outlineColor; 21817 auto bg = painter.impl._fillColor; 21818 21819 if(sprite !is null) { 21820 auto w = cast(SimpleWindow) painter.window; 21821 assert(w !is null); 21822 21823 sprite.drawAt(painter, upperLeft); 21824 } else { 21825 painter.drawImage(upperLeft, stringToImage(fg, bg, text)); 21826 } 21827 } 21828 } 21829 21830 return new ArsdTtfFont(data, size); 21831 } 21832 21833 class NotYetImplementedException : Exception { 21834 this(string file = __FILE__, size_t line = __LINE__) { 21835 super("Not yet implemented", file, line); 21836 } 21837 } 21838 21839 /// 21840 __gshared bool librariesSuccessfullyLoaded = true; 21841 /// 21842 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 21843 21844 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 21845 mixin(staticForeachReplacement!Iface); 21846 21847 void loadDynamicLibrary() @nogc { 21848 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 21849 } 21850 21851 void loadDynamicLibraryForReal() { 21852 foreach(name; __traits(derivedMembers, Iface)) { 21853 mixin("alias tmp = " ~ name ~ ";"); 21854 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 21855 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 21856 } 21857 } 21858 } 21859 21860 private const(char)[] staticForeachReplacement(Iface)() pure { 21861 /* 21862 // just this for gdc 9.... 21863 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 21864 21865 static foreach(name; __traits(derivedMembers, Iface)) 21866 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 21867 */ 21868 21869 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 21870 size_t pos; 21871 21872 void append(in char[] what) { 21873 if(pos + what.length > code.length) 21874 code.length = (code.length * 3) / 2; 21875 code[pos .. pos + what.length] = what[]; 21876 pos += what.length; 21877 } 21878 21879 foreach(name; __traits(derivedMembers, Iface)) { 21880 append(`__gshared typeof(&__traits(getMember, Iface, "`); 21881 append(name); 21882 append(`")) `); 21883 append(name); 21884 append(";"); 21885 } 21886 21887 return code[0 .. pos]; 21888 } 21889 21890 private mixin template DynamicLoad(Iface, string library, int majorVersion, alias success) { 21891 mixin(staticForeachReplacement!Iface); 21892 21893 private __gshared void* libHandle; 21894 private __gshared bool attempted; 21895 21896 void loadDynamicLibrary() @nogc { 21897 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 21898 } 21899 21900 bool loadAttempted() { 21901 return attempted; 21902 } 21903 bool loadSuccessful() { 21904 return libHandle !is null; 21905 } 21906 21907 void loadDynamicLibraryForReal() { 21908 attempted = true; 21909 version(Posix) { 21910 import core.sys.posix.dlfcn; 21911 version(OSX) { 21912 version(X11) 21913 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 21914 else 21915 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 21916 } else { 21917 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 21918 if(libHandle is null) 21919 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 21920 } 21921 21922 static void* loadsym(void* l, const char* name) { 21923 import core.stdc.stdlib; 21924 if(l is null) 21925 return &abort; 21926 return dlsym(l, name); 21927 } 21928 } else version(Windows) { 21929 import core.sys.windows.winbase; 21930 libHandle = LoadLibrary(library ~ ".dll"); 21931 static void* loadsym(void* l, const char* name) { 21932 import core.stdc.stdlib; 21933 if(l is null) 21934 return &abort; 21935 return GetProcAddress(l, name); 21936 } 21937 } 21938 if(libHandle is null) { 21939 success = false; 21940 //throw new Exception("load failure of library " ~ library); 21941 } 21942 foreach(name; __traits(derivedMembers, Iface)) { 21943 mixin("alias tmp = " ~ name ~ ";"); 21944 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 21945 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 21946 } 21947 } 21948 21949 void unloadDynamicLibrary() { 21950 version(Posix) { 21951 import core.sys.posix.dlfcn; 21952 dlclose(libHandle); 21953 } else version(Windows) { 21954 import core.sys.windows.winbase; 21955 FreeLibrary(libHandle); 21956 } 21957 foreach(name; __traits(derivedMembers, Iface)) 21958 mixin(name ~ " = null;"); 21959 } 21960 } 21961 21962 /+ 21963 The GC can be called from any thread, and a lot of cleanup must be done 21964 on the gui thread. Since the GC can interrupt any locks - including being 21965 triggered inside a critical section - it is vital to avoid deadlocks to get 21966 these functions called from the right place. 21967 21968 If the buffer overflows, things are going to get leaked. I'm kinda ok with that 21969 right now. 21970 21971 The cleanup function is run when the event loop gets around to it, which is just 21972 whenever there's something there after it has been woken up for other work. It does 21973 NOT wake up the loop itself - can't risk doing that from inside the GC in another thread. 21974 (Well actually it might be ok but i don't wanna mess with it right now.) 21975 +/ 21976 private struct CleanupQueue { 21977 import core.stdc.stdlib; 21978 21979 void queue(alias func, T...)(T args) { 21980 static struct Args { 21981 T args; 21982 } 21983 static struct RealJob { 21984 Job j; 21985 Args a; 21986 } 21987 static void call(Job* data) { 21988 auto rj = cast(RealJob*) data; 21989 func(rj.a.args); 21990 } 21991 21992 RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof); 21993 thing.j.call = &call; 21994 thing.a.args = args; 21995 21996 buffer[tail++] = cast(Job*) thing; 21997 21998 // FIXME: set overflowed 21999 } 22000 22001 void process() { 22002 const tail = this.tail; 22003 22004 while(tail != head) { 22005 Job* job = cast(Job*) buffer[head++]; 22006 job.call(job); 22007 free(job); 22008 } 22009 22010 if(overflowed) 22011 throw new Exception("cleanup overflowed"); 22012 } 22013 22014 private: 22015 22016 ubyte tail; // must ONLY be written by queue 22017 ubyte head; // must ONLY be written by process 22018 bool overflowed; 22019 22020 static struct Job { 22021 void function(Job*) call; 22022 } 22023 22024 void*[256] buffer; 22025 } 22026 private __gshared CleanupQueue cleanupQueue; 22027 22028 version(X11) 22029 /++ 22030 Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. 22031 22032 $(WARNING 22033 This function is exempted from stability guarantees. 22034 ) 22035 +/ 22036 float customScalingFactorForMonitor(int monitorNumber) { 22037 import core.stdc.stdlib; 22038 auto val = getenv("ARSD_SCALING_FACTOR"); 22039 22040 if(val is null) 22041 return 1.0; 22042 22043 char[16] buffer = 0; 22044 int pos; 22045 22046 const(char)* at = val; 22047 22048 foreach(item; 0 .. monitorNumber + 1) { 22049 if(*at == 0) 22050 break; // reuse the last number when we at the end of the string 22051 pos = 0; 22052 while(pos + 1 < buffer.length && *at && *at != ';') { 22053 buffer[pos++] = *at; 22054 at++; 22055 } 22056 if(*at) 22057 at++; // skip the semicolon 22058 buffer[pos] = 0; 22059 } 22060 22061 //sdpyPrintDebugString(buffer[0 .. pos]); 22062 22063 import core.stdc.math; 22064 auto f = atof(buffer.ptr); 22065 22066 if(f <= 0.0 || isnan(f) || isinf(f)) 22067 return 1.0; 22068 22069 return f; 22070 } 22071 22072 void guiAbortProcess(string msg) { 22073 import core.stdc.stdlib; 22074 version(Windows) { 22075 WCharzBuffer t = WCharzBuffer(msg); 22076 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 22077 } else { 22078 import core.stdc.stdio; 22079 fwrite(msg.ptr, 1, msg.length, stderr); 22080 msg = "\n"; 22081 fwrite(msg.ptr, 1, msg.length, stderr); 22082 fflush(stderr); 22083 } 22084 22085 abort(); 22086 } 22087 22088 private int minInternal(int a, int b) { 22089 return (a < b) ? a : b; 22090 } 22091 22092 private alias scriptable = arsd_jsvar_compatible;