1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 // https://freedesktop.org/wiki/Specifications/XDND/ 4 5 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format 6 7 // https://www.x.org/releases/X11R7.7/doc/libXext/dbelib.html 8 // https://www.x.org/releases/X11R7.6/doc/libXext/synclib.html 9 10 11 // on Mac with X11: -L-L/usr/X11/lib 12 13 /+ 14 15 * I might need to set modal hints too _NET_WM_STATE_MODAL and make sure that TRANSIENT_FOR legit works 16 17 Progress bar in taskbar 18 - i can probably just set a property on the window... 19 it sets that prop to an integer 0 .. 100. Taskbar 20 deletes it or window deletes it when it is handled. 21 - prolly display it as a nice little line at the bottom. 22 23 24 from gtk: 25 26 #define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" 27 #define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" 28 29 >+ if (cardinal > 0) 30 >+ { 31 >+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display), 32 >+ xid, 33 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name), 34 >+ XA_CARDINAL, 32, 35 >+ PropModeReplace, 36 >+ (guchar *) &cardinal, 1); 37 >+ } 38 >+ else 39 >+ { 40 >+ XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), 41 >+ xid, 42 >+ gdk_x11_get_xatom_by_name_for_display (display, atom_name)); 43 >+ } 44 45 from Windows: 46 47 see: https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3 48 49 interface 50 CoCreateInstance( CLSID_TaskbarList, nullptr, CLSCTX_ALL, __uuidof(ITaskbarList3), (LPVOID*)&m_pTL3 ); 51 auto msg = RegisterWindowMessage(TEXT(“TaskbarButtonCreated”)); 52 listen for msg, return TRUE 53 interface->SetProgressState(hwnd, TBPF_NORMAL); 54 interface->SetProgressValue(hwnd, 40, 100); 55 56 57 My new notification system. 58 - use a unix socket? or a x property? or a udp port? 59 - could of course also get on the dbus train but ugh. 60 - it could also reply with the info as a string for easy remote examination. 61 62 +/ 63 64 /* 65 Event Loop would be nices: 66 67 * add on idle - runs when nothing else happens 68 * which can specify how long to yield for 69 * send messages without a recipient window 70 * setTimeout 71 * setInterval 72 */ 73 74 /* 75 Classic games I want to add: 76 * my tetris clone 77 * pac man 78 */ 79 80 /* 81 Text layout needs a lot of work. Plain drawText is useful but too 82 limited. It will need some kind of text context thing which it will 83 update and you can pass it on and get more details out of it. 84 85 It will need a bounding box, a current cursor location that is updated 86 as drawing continues, and various changable facts (which can also be 87 changed on the painter i guess) like font, color, size, background, 88 etc. 89 90 We can also fetch the caret location from it somehow. 91 92 Should prolly be an overload of drawText 93 94 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 95 96 WS_EX_NOACTIVATE 97 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 98 full screen windows. Can just set the atom on X. Windows will be harder. 99 100 moving windows. resizing windows. 101 102 hide cursor, capture cursor, change cursor. 103 104 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 105 sure the pieces are there to do its job easily and make other jobs possible. 106 */ 107 108 /++ 109 simpledisplay.d (often abbreviated to "sdpy") provides basic cross-platform GUI-related functionality, 110 including creating windows, drawing on them, working with the clipboard, 111 timers, OpenGL, and more. However, it does NOT provide high level GUI 112 widgets. See my minigui.d, an extension to this module, for that 113 functionality. 114 115 simpledisplay provides cross-platform wrapping for Windows and Linux 116 (and perhaps other OSes that use X11), but also does not prevent you 117 from using the underlying facilities if you need them. It has a goal 118 of working efficiently over a remote X link (at least as far as Xlib 119 reasonably allows.) 120 121 simpledisplay depends on [arsd.color|color.d], which should be available from the 122 same place where you got this file. Other than that, however, it has 123 very few dependencies and ones that don't come with the OS and/or the 124 compiler are all opt-in. 125 126 simpledisplay.d's home base is on my arsd repo on Github. The file is: 127 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 128 129 simpledisplay is basically stable. I plan to refactor the internals, 130 and may add new features and fix bugs, but It do not expect to 131 significantly change the API. It has been stable a few years already now. 132 133 Installation_instructions: 134 135 `simpledisplay.d` does not have any dependencies outside the 136 operating system and `color.d`, so it should just work most the 137 time, but there are a few caveats on some systems: 138 139 Please note when compiling on Win64, you need to explicitly list 140 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 141 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 142 143 If using ldc instead of dmd, use `-L/entry:wmainCRTStartup` instead of `mainCRTStartup`; 144 note the "w". 145 146 On Win32, you can pass `-L/subsystem:windows` if you don't want a 147 console to be automatically allocated. 148 149 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. 150 151 On Ubuntu, you might need to install X11 development libraries to 152 successfully link. 153 154 $(CONSOLE 155 $ sudo apt-get install libglc-dev 156 $ sudo apt-get install libx11-dev 157 ) 158 159 160 Jump_list: 161 162 Don't worry, you don't have to read this whole documentation file! 163 164 Check out the [#event-example] and [#Pong-example] to get started quickly. 165 166 The main classes you may want to create are [SimpleWindow], [Timer], 167 [Image], and [Sprite]. 168 169 The main functions you'll want are [setClipboardText] and [getClipboardText]. 170 171 There are also platform-specific functions available such as [XDisplayConnection] 172 and [GetAtom] for X11, among others. 173 174 See the examples and topics list below to learn more. 175 176 $(WARNING 177 There should only be one GUI thread per application, 178 and all windows should be created in it and your 179 event loop should run there. 180 181 To do otherwise is undefined behavior and has no 182 cross platform guarantees. 183 ) 184 185 $(H2 About this documentation) 186 187 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. 188 189 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! 190 191 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. 192 193 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. 194 195 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. 196 197 At points, I will talk about implementation details in the documentation. These are sometimes 198 subject to change, but nevertheless useful to understand what is really going on. You can learn 199 more about some of the referenced things by searching the web for info about using them from C. 200 You can always look at the source of simpledisplay.d too for the most authoritative source on 201 its specific implementation. If you disagree with how I did something, please contact me so we 202 can discuss it! 203 204 $(H2 Using with fibers) 205 206 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). 207 208 $(H2 Topics) 209 210 $(H3 $(ID topic-windows) Windows) 211 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 212 window on the user's screen. 213 214 You may create multiple windows, if the underlying platform supports it. You may check 215 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 216 SimpleWindow's constructor at runtime to handle those cases. 217 218 A single running event loop will handle as many windows as needed. 219 220 setEventHandlers function 221 eventLoop function 222 draw function 223 title property 224 225 $(H3 $(ID topic-event-loops) Event loops) 226 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 227 228 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: 229 230 --- 231 // dmd example.d simpledisplay.d color.d 232 import arsd.simpledisplay; 233 void main() { 234 auto window = new SimpleWindow(200, 200); 235 window.eventLoop(0, 236 delegate (dchar) { /* got a character key press */ } 237 ); 238 } 239 --- 240 241 $(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.) 242 243 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. 244 245 On Linux, simpledisplay also supports my (deprecated) [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 246 247 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 248 249 You can also run the event loop independently of a window, with [EventLoop.run|EventLoop.get.run], though since it will automatic terminate when there's no open windows, you will want to have one anyway. 250 251 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 252 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. 253 254 See the [NotificationAreaIcon] class. 255 256 $(H3 $(ID topic-input-handling) Input handling) 257 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 258 259 See [SimpleWindow.handleCharEvent], [SimpleWindow.handleKeyEvent], [SimpleWindow.handleMouseEvent]. 260 261 $(H3 $(ID topic-2d-drawing) 2d Drawing) 262 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 263 264 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: 265 266 --- 267 // dmd example.d simpledisplay.d color.d 268 import arsd.simpledisplay; 269 void main() { 270 auto window = new SimpleWindow(200, 200); 271 { // introduce sub-scope 272 auto painter = window.draw(); // begin drawing 273 /* draw here */ 274 painter.outlineColor = Color.red; 275 painter.fillColor = Color.black; 276 painter.drawRectangle(Point(0, 0), 200, 200); 277 } // end scope, calling `painter`'s destructor, drawing to the screen. 278 window.eventLoop(0); // handle events 279 } 280 --- 281 282 Painting is done based on two color properties, a pen and a brush. 283 284 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. 285 286 FIXME add example of 2d opengl drawing here 287 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 288 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 289 290 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. 291 292 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 293 294 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 295 296 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]. 297 298 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. 299 300 This example program will draw a rectangle on your window using old-style OpenGL with a pulsating color: 301 302 --- 303 import arsd.simpledisplay; 304 305 void main() { 306 auto window = new SimpleWindow(800, 600, "opengl 1", OpenGlOptions.yes, Resizability.allowResizing); 307 308 float otherColor = 0.0; 309 float colorDelta = 0.05; 310 311 window.redrawOpenGlScene = delegate() { 312 glLoadIdentity(); 313 glBegin(GL_QUADS); 314 315 glColor3f(1.0, otherColor, 0); 316 glVertex3f(-0.8, -0.8, 0); 317 318 glColor3f(1.0, otherColor, 1.0); 319 glVertex3f(0.8, -0.8, 0); 320 321 glColor3f(0, 1.0, otherColor); 322 glVertex3f(0.8, 0.8, 0); 323 324 glColor3f(otherColor, 0, 1.0); 325 glVertex3f(-0.8, 0.8, 0); 326 327 glEnd(); 328 }; 329 330 window.eventLoop(50, () { 331 otherColor += colorDelta; 332 if(otherColor > 1.0) { 333 otherColor = 1.0; 334 colorDelta = -0.05; 335 } 336 if(otherColor < 0) { 337 otherColor = 0; 338 colorDelta = 0.05; 339 } 340 // at the end of the timer, we have to request a redraw 341 // or we won't see the changes. 342 window.redrawOpenGlSceneSoon(); 343 }); 344 } 345 --- 346 347 My [arsd.game] module has some helpers for using old-style opengl to make 2D windows too. See: [arsd.game.create2dWindow]. 348 $(H3 $(ID topic-modern-opengl) Modern OpenGL) 349 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 providers helpers for this too. 350 351 This example program shows how you can set up a shader to draw a rectangle: 352 353 --- 354 module opengl3test; 355 import arsd.simpledisplay; 356 357 // based on https://learnopengl.com/Getting-started/Hello-Triangle 358 359 void main() { 360 // First thing we do, before creating the window, is declare what version we want. 361 setOpenGLContextVersion(3, 3); 362 // turning off legacy compat is required to use version 3.3 and newer 363 openGLContextCompatible = false; 364 365 uint VAO; 366 OpenGlShader shader; 367 368 // then we can create the window. 369 auto window = new SimpleWindow(800, 600, "opengl 3", OpenGlOptions.yes, Resizability.allowResizing); 370 371 // additional setup needs to be done when it is visible, simpledisplay offers a property 372 // for exactly that: 373 window.visibleForTheFirstTime = delegate() { 374 // now with the window loaded, we can start loading the modern opengl functions. 375 376 // you MUST set the context first. 377 window.setAsCurrentOpenGlContext; 378 // then load the remainder of the library 379 gl3.loadDynamicLibrary(); 380 381 // now you can create the shaders, etc. 382 shader = new OpenGlShader( 383 OpenGlShader.Source(GL_VERTEX_SHADER, ` 384 #version 330 core 385 layout (location = 0) in vec3 aPos; 386 void main() { 387 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); 388 } 389 `), 390 OpenGlShader.Source(GL_FRAGMENT_SHADER, ` 391 #version 330 core 392 out vec4 FragColor; 393 uniform vec4 mycolor; 394 void main() { 395 FragColor = mycolor; 396 } 397 `), 398 ); 399 400 // and do whatever other setup you want. 401 402 float[] vertices = [ 403 0.5f, 0.5f, 0.0f, // top right 404 0.5f, -0.5f, 0.0f, // bottom right 405 -0.5f, -0.5f, 0.0f, // bottom left 406 -0.5f, 0.5f, 0.0f // top left 407 ]; 408 uint[] indices = [ // note that we start from 0! 409 0, 1, 3, // first Triangle 410 1, 2, 3 // second Triangle 411 ]; 412 uint VBO, EBO; 413 glGenVertexArrays(1, &VAO); 414 // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 415 glBindVertexArray(VAO); 416 417 glGenBuffers(1, &VBO); 418 glGenBuffers(1, &EBO); 419 420 glBindBuffer(GL_ARRAY_BUFFER, VBO); 421 glBufferDataSlice(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); 422 423 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 424 glBufferDataSlice(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); 425 426 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null); 427 glEnableVertexAttribArray(0); 428 429 // the library will set the initial viewport and trigger our first draw, 430 // so these next two lines are NOT needed. they are just here as comments 431 // to show what would happen next. 432 433 // glViewport(0, 0, window.width, window.height); 434 // window.redrawOpenGlSceneNow(); 435 }; 436 437 // this delegate is called any time the window needs to be redrawn or if you call `window.redrawOpenGlSceneNow;` 438 // it is our render method. 439 window.redrawOpenGlScene = delegate() { 440 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 441 glClear(GL_COLOR_BUFFER_BIT); 442 443 glUseProgram(shader.shaderProgram); 444 445 // the shader helper class has methods to set uniforms too 446 shader.uniforms.mycolor.opAssign(1.0, 1.0, 0, 1.0); 447 448 glBindVertexArray(VAO); 449 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null); 450 }; 451 452 window.eventLoop(0); 453 } 454 --- 455 456 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. 457 458 459 $(H3 $(ID topic-images) Displaying images) 460 You can also load PNG images using [arsd.png]. 461 462 --- 463 // dmd example.d simpledisplay.d color.d png.d 464 import arsd.simpledisplay; 465 import arsd.png; 466 467 void main() { 468 auto image = Image.fromMemoryImage(readPng("image.png")); 469 displayImage(image); 470 } 471 --- 472 473 Compile with `dmd example.d simpledisplay.d png.d`. 474 475 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. 476 477 $(H3 $(ID topic-sprites) Sprites) 478 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. 479 480 [Sprite] is also the only facility that currently supports alpha blending without using OpenGL . 481 482 $(H3 $(ID topic-clipboard) Clipboard) 483 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 484 485 It also has helpers for handling X-specific events. 486 487 $(H3 $(ID topic-dnd) Drag and Drop) 488 See [enableDragAndDrop] and [draggable]. 489 490 $(H3 $(ID topic-timers) Timers) 491 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]. 492 493 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. 494 495 --- 496 import arsd.simpledisplay; 497 498 void main() { 499 auto window = new SimpleWindow(400, 400); 500 // every 100 ms, it will draw a random line 501 // on the window. 502 window.eventLoop(100, { 503 auto painter = window.draw(); 504 505 import std.random; 506 // random color 507 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 508 // random line 509 painter.drawLine( 510 Point(uniform(0, window.width), uniform(0, window.height)), 511 Point(uniform(0, window.width), uniform(0, window.height))); 512 513 }); 514 } 515 --- 516 517 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. 518 519 The pulse timer and instances of the [Timer] class may be combined at will. 520 521 --- 522 import arsd.simpledisplay; 523 524 void main() { 525 auto window = new SimpleWindow(400, 400); 526 auto timer = new Timer(1000, delegate { 527 auto painter = window.draw(); 528 painter.clear(); 529 }); 530 531 window.eventLoop(0); 532 } 533 --- 534 535 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 536 537 $(H3 $(ID topic-os-helpers) OS-specific helpers) 538 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. 539 540 See also: `xwindows.d` from my github. 541 542 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 543 `handleNativeEvent` and `handleNativeGlobalEvent`. 544 545 $(H3 $(ID topic-integration) Integration with other libraries) 546 Integration with a third-party event loop is possible. 547 548 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. 549 550 $(H3 $(ID topic-guis) GUI widgets) 551 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! 552 553 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. 554 555 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.) 556 557 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 558 559 $(H2 Platform-specific tips and tricks) 560 561 Windows_tips: 562 563 You can add icons or manifest files to your exe using a resource file. 564 565 To create a Windows .ico file, use the gimp or something. I'll write a helper 566 program later. 567 568 Create `yourapp.rc`: 569 570 ```rc 571 1 ICON filename.ico 572 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 573 ``` 574 575 And `yourapp.exe.manifest`: 576 577 ```xml 578 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 579 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 580 <assemblyIdentity 581 version="1.0.0.0" 582 processorArchitecture="*" 583 name="CompanyName.ProductName.YourApplication" 584 type="win32" 585 /> 586 <description>Your application description here.</description> 587 <dependency> 588 <dependentAssembly> 589 <assemblyIdentity 590 type="win32" 591 name="Microsoft.Windows.Common-Controls" 592 version="6.0.0.0" 593 processorArchitecture="*" 594 publicKeyToken="6595b64144ccf1df" 595 language="*" 596 /> 597 </dependentAssembly> 598 </dependency> 599 </assembly> 600 ``` 601 602 $(H2 Tips) 603 604 $(H3 Name conflicts) 605 606 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: 607 608 --- 609 static import sdpy = arsd.simpledisplay; 610 import arsd.simpledisplay : SimpleWindow; 611 612 void main() { 613 auto window = new SimpleWindow(); 614 sdpy.EventLoop.get.run(); 615 } 616 --- 617 618 $(H2 $(ID developer-notes) Developer notes) 619 620 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 621 implementation though. 622 623 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 624 suck. If I was rewriting it, I wouldn't do it that way again. 625 626 This file must not have any more required dependencies. If you need bindings, add 627 them right to this file. Once it gets into druntime and is there for a while, remove 628 bindings from here to avoid conflicts (or put them in an appropriate version block 629 so it continues to just work on old dmd), but wait a couple releases before making the 630 transition so this module remains usable with older versions of dmd. 631 632 You may have optional dependencies if needed by putting them in version blocks or 633 template functions. You may also extend the module with other modules with UFCS without 634 actually editing this - that is nice to do if you can. 635 636 Try to make functions work the same way across operating systems. I typically make 637 it thinly wrap Windows, then emulate that on Linux. 638 639 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 640 Phobos! So try to avoid it. 641 642 See more comments throughout the source. 643 644 I realize this file is fairly large, but over half that is just bindings at the bottom 645 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 646 to understand. I suggest you jump around the source by looking for a particular 647 declaration you're interested in, like `class SimpleWindow` using your editor's search 648 function, then look at one piece at a time. 649 650 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 651 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode and you can 652 ping me, adam_d_ruppe, and I'll usually see it if I'm around. 653 654 I live in the eastern United States, so I will most likely not be around at night in 655 that US east timezone. 656 657 License: Copyright Adam D. Ruppe, 2011-2021. Released under the Boost Software License. 658 659 Building documentation: use my adrdox generator, `dub run adrdox`. 660 661 Examples: 662 663 $(DIV $(ID Event-example)) 664 $(H3 $(ID event-example) Event example) 665 This program creates a window and draws events inside them as they 666 happen, scrolling the text in the window as needed. Run this program 667 and experiment to get a feel for where basic input events take place 668 in the library. 669 670 --- 671 // dmd example.d simpledisplay.d color.d 672 import arsd.simpledisplay; 673 import std.conv; 674 675 void main() { 676 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 677 678 int y = 0; 679 680 void addLine(string text) { 681 auto painter = window.draw(); 682 683 if(y + painter.fontHeight >= window.height) { 684 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 685 y -= painter.fontHeight; 686 } 687 688 painter.outlineColor = Color.red; 689 painter.fillColor = Color.black; 690 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 691 692 painter.outlineColor = Color.white; 693 694 painter.drawText(Point(10, y), text); 695 696 y += painter.fontHeight; 697 } 698 699 window.eventLoop(1000, 700 () { 701 addLine("Timer went off!"); 702 }, 703 (KeyEvent event) { 704 addLine(to!string(event)); 705 }, 706 (MouseEvent event) { 707 addLine(to!string(event)); 708 }, 709 (dchar ch) { 710 addLine(to!string(ch)); 711 } 712 ); 713 } 714 --- 715 716 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. 717 718 $(COMMENT 719 This program displays a pie chart. Clicking on a color will increase its share of the pie. 720 721 --- 722 723 --- 724 ) 725 726 727 +/ 728 module arsd.simpledisplay; 729 730 // FIXME: tetris demo 731 // FIXME: space invaders demo 732 // FIXME: asteroids demo 733 734 /++ $(ID Pong-example) 735 $(H3 Pong) 736 737 This program creates a little Pong-like game. Player one is controlled 738 with the keyboard. Player two is controlled with the mouse. It demos 739 the pulse timer, event handling, and some basic drawing. 740 +/ 741 version(demos) 742 unittest { 743 // dmd example.d simpledisplay.d color.d 744 import arsd.simpledisplay; 745 746 enum paddleMovementSpeed = 8; 747 enum paddleHeight = 48; 748 749 void main() { 750 auto window = new SimpleWindow(600, 400, "Pong game!"); 751 752 int playerOnePosition, playerTwoPosition; 753 int playerOneMovement, playerTwoMovement; 754 int playerOneScore, playerTwoScore; 755 756 int ballX, ballY; 757 int ballDx, ballDy; 758 759 void serve() { 760 import std.random; 761 762 ballX = window.width / 2; 763 ballY = window.height / 2; 764 ballDx = uniform(-4, 4) * 3; 765 ballDy = uniform(-4, 4) * 3; 766 if(ballDx == 0) 767 ballDx = uniform(0, 2) == 0 ? 3 : -3; 768 } 769 770 serve(); 771 772 window.eventLoop(50, // set a 50 ms timer pulls 773 // This runs once per timer pulse 774 delegate () { 775 auto painter = window.draw(); 776 777 painter.clear(); 778 779 // Update everyone's motion 780 playerOnePosition += playerOneMovement; 781 playerTwoPosition += playerTwoMovement; 782 783 ballX += ballDx; 784 ballY += ballDy; 785 786 // Bounce off the top and bottom edges of the window 787 if(ballY + 7 >= window.height) 788 ballDy = -ballDy; 789 if(ballY - 8 <= 0) 790 ballDy = -ballDy; 791 792 // Bounce off the paddle, if it is in position 793 if(ballX - 8 <= 16) { 794 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 795 ballDx = -ballDx + 1; // add some speed to keep it interesting 796 ballDy += playerOneMovement; // and y movement based on your controls too 797 ballX = 24; // move it past the paddle so it doesn't wiggle inside 798 } else { 799 // Missed it 800 playerTwoScore ++; 801 serve(); 802 } 803 } 804 805 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 806 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 807 ballDx = -ballDx - 1; 808 ballDy += playerTwoMovement; 809 ballX = window.width - 24; 810 } else { 811 // Missed it 812 playerOneScore ++; 813 serve(); 814 } 815 } 816 817 // Draw the paddles 818 painter.outlineColor = Color.black; 819 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 820 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 821 822 // Draw the ball 823 painter.fillColor = Color.red; 824 painter.outlineColor = Color.yellow; 825 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 826 827 // Draw the score 828 painter.outlineColor = Color.blue; 829 import std.conv; 830 painter.drawText(Point(64, 4), to!string(playerOneScore)); 831 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 832 833 }, 834 delegate (KeyEvent event) { 835 // Player 1's controls are the arrow keys on the keyboard 836 if(event.key == Key.Down) 837 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 838 if(event.key == Key.Up) 839 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 840 841 }, 842 delegate (MouseEvent event) { 843 // Player 2's controls are mouse movement while the left button is held down 844 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 845 if(event.dy > 0) 846 playerTwoMovement = paddleMovementSpeed; 847 else if(event.dy < 0) 848 playerTwoMovement = -paddleMovementSpeed; 849 } else { 850 playerTwoMovement = 0; 851 } 852 } 853 ); 854 } 855 } 856 857 /++ $(H3 $(ID example-minesweeper) Minesweeper) 858 859 This minesweeper demo shows how we can implement another classic 860 game with simpledisplay and shows some mouse input and basic output 861 code. 862 +/ 863 version(demos) 864 unittest { 865 import arsd.simpledisplay; 866 867 enum GameSquare { 868 mine = 0, 869 clear, 870 m1, m2, m3, m4, m5, m6, m7, m8 871 } 872 873 enum UserSquare { 874 unknown, 875 revealed, 876 flagged, 877 questioned 878 } 879 880 enum GameState { 881 inProgress, 882 lose, 883 win 884 } 885 886 GameSquare[] board; 887 UserSquare[] userState; 888 GameState gameState; 889 int boardWidth; 890 int boardHeight; 891 892 bool isMine(int x, int y) { 893 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 894 return false; 895 return board[y * boardWidth + x] == GameSquare.mine; 896 } 897 898 GameState reveal(int x, int y) { 899 if(board[y * boardWidth + x] == GameSquare.clear) { 900 floodFill(userState, boardWidth, boardHeight, 901 UserSquare.unknown, UserSquare.revealed, 902 x, y, 903 (x, y) { 904 if(board[y * boardWidth + x] == GameSquare.clear) 905 return true; 906 else { 907 userState[y * boardWidth + x] = UserSquare.revealed; 908 return false; 909 } 910 }); 911 } else { 912 userState[y * boardWidth + x] = UserSquare.revealed; 913 if(isMine(x, y)) 914 return GameState.lose; 915 } 916 917 foreach(state; userState) { 918 if(state == UserSquare.unknown || state == UserSquare.questioned) 919 return GameState.inProgress; 920 } 921 922 return GameState.win; 923 } 924 925 void initializeBoard(int width, int height, int numberOfMines) { 926 boardWidth = width; 927 boardHeight = height; 928 board.length = width * height; 929 930 userState.length = width * height; 931 userState[] = UserSquare.unknown; 932 933 import std.algorithm, std.random, std.range; 934 935 board[] = GameSquare.clear; 936 937 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 938 board[minePosition] = GameSquare.mine; 939 940 int x; 941 int y; 942 foreach(idx, ref square; board) { 943 if(square == GameSquare.clear) { 944 int danger = 0; 945 danger += isMine(x-1, y-1)?1:0; 946 danger += isMine(x-1, y)?1:0; 947 danger += isMine(x-1, y+1)?1:0; 948 danger += isMine(x, y-1)?1:0; 949 danger += isMine(x, y+1)?1:0; 950 danger += isMine(x+1, y-1)?1:0; 951 danger += isMine(x+1, y)?1:0; 952 danger += isMine(x+1, y+1)?1:0; 953 954 square = cast(GameSquare) (danger + 1); 955 } 956 957 x++; 958 if(x == width) { 959 x = 0; 960 y++; 961 } 962 } 963 } 964 965 void redraw(SimpleWindow window) { 966 import std.conv; 967 968 auto painter = window.draw(); 969 970 painter.clear(); 971 972 final switch(gameState) with(GameState) { 973 case inProgress: 974 break; 975 case win: 976 painter.fillColor = Color.green; 977 painter.drawRectangle(Point(0, 0), window.width, window.height); 978 return; 979 case lose: 980 painter.fillColor = Color.red; 981 painter.drawRectangle(Point(0, 0), window.width, window.height); 982 return; 983 } 984 985 int x = 0; 986 int y = 0; 987 988 foreach(idx, square; board) { 989 auto state = userState[idx]; 990 991 final switch(state) with(UserSquare) { 992 case unknown: 993 painter.outlineColor = Color.black; 994 painter.fillColor = Color(128,128,128); 995 996 painter.drawRectangle( 997 Point(x * 20, y * 20), 998 20, 20 999 ); 1000 break; 1001 case revealed: 1002 if(square == GameSquare.clear) { 1003 painter.outlineColor = Color.white; 1004 painter.fillColor = Color.white; 1005 1006 painter.drawRectangle( 1007 Point(x * 20, y * 20), 1008 20, 20 1009 ); 1010 } else { 1011 painter.outlineColor = Color.black; 1012 painter.fillColor = Color.white; 1013 1014 painter.drawText( 1015 Point(x * 20, y * 20), 1016 to!string(square)[1..2], 1017 Point(x * 20 + 20, y * 20 + 20), 1018 TextAlignment.Center | TextAlignment.VerticalCenter); 1019 } 1020 break; 1021 case flagged: 1022 painter.outlineColor = Color.black; 1023 painter.fillColor = Color.red; 1024 painter.drawRectangle( 1025 Point(x * 20, y * 20), 1026 20, 20 1027 ); 1028 break; 1029 case questioned: 1030 painter.outlineColor = Color.black; 1031 painter.fillColor = Color.yellow; 1032 painter.drawRectangle( 1033 Point(x * 20, y * 20), 1034 20, 20 1035 ); 1036 break; 1037 } 1038 1039 x++; 1040 if(x == boardWidth) { 1041 x = 0; 1042 y++; 1043 } 1044 } 1045 1046 } 1047 1048 void main() { 1049 auto window = new SimpleWindow(200, 200); 1050 1051 initializeBoard(10, 10, 10); 1052 1053 redraw(window); 1054 window.eventLoop(0, 1055 delegate (MouseEvent me) { 1056 if(me.type != MouseEventType.buttonPressed) 1057 return; 1058 auto x = me.x / 20; 1059 auto y = me.y / 20; 1060 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 1061 if(me.button == MouseButton.left) { 1062 gameState = reveal(x, y); 1063 } else { 1064 userState[y*boardWidth+x] = UserSquare.flagged; 1065 } 1066 redraw(window); 1067 } 1068 } 1069 ); 1070 } 1071 } 1072 1073 /* 1074 version(OSX) { 1075 version=without_opengl; 1076 version=allow_unimplemented_features; 1077 version=OSXCocoa; 1078 pragma(linkerDirective, "-framework Cocoa"); 1079 } 1080 */ 1081 1082 version(without_opengl) { 1083 enum SdpyIsUsingIVGLBinds = false; 1084 } else /*version(Posix)*/ { 1085 static if (__traits(compiles, (){import iv.glbinds;})) { 1086 enum SdpyIsUsingIVGLBinds = true; 1087 public import iv.glbinds; 1088 //pragma(msg, "SDPY: using iv.glbinds"); 1089 } else { 1090 enum SdpyIsUsingIVGLBinds = false; 1091 } 1092 //} else { 1093 // enum SdpyIsUsingIVGLBinds = false; 1094 } 1095 1096 1097 version(Windows) { 1098 //import core.sys.windows.windows; 1099 import core.sys.windows.winnls; 1100 import core.sys.windows.windef; 1101 import core.sys.windows.basetyps; 1102 import core.sys.windows.winbase; 1103 import core.sys.windows.winuser; 1104 import core.sys.windows.shellapi; 1105 import core.sys.windows.wingdi; 1106 static import gdi = core.sys.windows.wingdi; // so i 1107 1108 pragma(lib, "gdi32"); 1109 pragma(lib, "user32"); 1110 1111 // for AlphaBlend... a breaking change.... 1112 version(CRuntime_DigitalMars) { } else 1113 pragma(lib, "msimg32"); 1114 } else version (linux) { 1115 //k8: this is hack for rdmd. sorry. 1116 static import core.sys.linux.epoll; 1117 static import core.sys.linux.timerfd; 1118 } 1119 1120 1121 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 1122 1123 // http://wiki.dlang.org/Simpledisplay.d 1124 1125 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 1126 1127 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 1128 // but can i control the scroll lock led 1129 1130 1131 // Note: if you are using Image on X, you might want to do: 1132 /* 1133 static if(UsingSimpledisplayX11) { 1134 if(!Image.impl.xshmAvailable) { 1135 // the images will use the slower XPutImage, you might 1136 // want to consider an alternative method to get better speed 1137 } 1138 } 1139 1140 If the shared memory extension is available though, simpledisplay uses it 1141 for a significant speed boost whenever you draw large Images. 1142 */ 1143 1144 // 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. 1145 1146 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 1147 1148 /* 1149 Biggest FIXME: 1150 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 1151 1152 clean up opengl contexts when their windows close 1153 1154 fix resizing the bitmaps/pixmaps 1155 */ 1156 1157 // BTW on Windows: 1158 // -L/SUBSYSTEM:WINDOWS:5.0 1159 // to dmd will make a nice windows binary w/o a console if you want that. 1160 1161 /* 1162 Stuff to add: 1163 1164 use multibyte functions everywhere we can 1165 1166 OpenGL windows 1167 more event stuff 1168 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 1169 1170 1171 resizeEvent 1172 and make the windows non-resizable by default, 1173 or perhaps stretched (if I can find something in X like StretchBlt) 1174 1175 take a screenshot function! 1176 1177 Pens and brushes? 1178 Maybe a global event loop? 1179 1180 Mouse deltas 1181 Key items 1182 */ 1183 1184 /* 1185 From MSDN: 1186 1187 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 1188 1189 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. 1190 1191 */ 1192 1193 version(linux) { 1194 version = X11; 1195 version(without_libnotify) { 1196 // we cool 1197 } 1198 else 1199 version = libnotify; 1200 } 1201 1202 version(libnotify) { 1203 pragma(lib, "dl"); 1204 import core.sys.posix.dlfcn; 1205 1206 void delegate()[int] libnotify_action_delegates; 1207 int libnotify_action_delegates_count; 1208 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 1209 auto idx = cast(int) user_data; 1210 if(auto dgptr = idx in libnotify_action_delegates) { 1211 (*dgptr)(); 1212 libnotify_action_delegates.remove(idx); 1213 } 1214 } 1215 1216 struct C_DynamicLibrary { 1217 void* handle; 1218 this(string name) { 1219 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 1220 if(handle is null) 1221 throw new Exception("dlopen"); 1222 } 1223 1224 void close() { 1225 dlclose(handle); 1226 } 1227 1228 ~this() { 1229 // close 1230 } 1231 1232 // FIXME: this looks up by name every time.... 1233 template call(string func, Ret, Args...) { 1234 extern(C) Ret function(Args) fptr; 1235 typeof(fptr) call() { 1236 fptr = cast(typeof(fptr)) dlsym(handle, func); 1237 return fptr; 1238 } 1239 } 1240 } 1241 1242 C_DynamicLibrary* libnotify; 1243 } 1244 1245 version(OSX) { 1246 version(OSXCocoa) {} 1247 else { version = X11; } 1248 } 1249 //version = OSXCocoa; // this was written by KennyTM 1250 version(FreeBSD) 1251 version = X11; 1252 version(Solaris) 1253 version = X11; 1254 1255 version(X11) { 1256 version(without_xft) {} 1257 else version=with_xft; 1258 } 1259 1260 void featureNotImplemented()() { 1261 version(allow_unimplemented_features) 1262 throw new NotYetImplementedException(); 1263 else 1264 static assert(0); 1265 } 1266 1267 // these are so the static asserts don't trigger unless you want to 1268 // add support to it for an OS 1269 version(Windows) 1270 version = with_timer; 1271 version(linux) 1272 version = with_timer; 1273 1274 version(with_timer) 1275 enum bool SimpledisplayTimerAvailable = true; 1276 else 1277 enum bool SimpledisplayTimerAvailable = false; 1278 1279 /// 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. 1280 version(Windows) 1281 enum bool UsingSimpledisplayWindows = true; 1282 else 1283 enum bool UsingSimpledisplayWindows = false; 1284 1285 /// 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. 1286 version(X11) 1287 enum bool UsingSimpledisplayX11 = true; 1288 else 1289 enum bool UsingSimpledisplayX11 = false; 1290 1291 /// 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. 1292 version(OSXCocoa) 1293 enum bool UsingSimpledisplayCocoa = true; 1294 else 1295 enum bool UsingSimpledisplayCocoa = false; 1296 1297 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1298 version(Windows) 1299 enum multipleWindowsSupported = true; 1300 else version(X11) 1301 enum multipleWindowsSupported = true; 1302 else version(OSXCocoa) 1303 enum multipleWindowsSupported = true; 1304 else 1305 static assert(0); 1306 1307 version(without_opengl) 1308 enum bool OpenGlEnabled = false; 1309 else 1310 enum bool OpenGlEnabled = true; 1311 1312 1313 /++ 1314 After selecting a type from [WindowTypes], you may further customize 1315 its behavior by setting one or more of these flags. 1316 1317 1318 The different window types have different meanings of `normal`. If the 1319 window type already is a good match for what you want to do, you should 1320 just use [WindowFlags.normal], the default, which will do the right thing 1321 for your users. 1322 1323 The window flags will not always be honored by the operating system 1324 and window managers; they are hints, not commands. 1325 +/ 1326 enum WindowFlags : int { 1327 normal = 0, /// 1328 skipTaskbar = 1, /// 1329 alwaysOnTop = 2, /// 1330 alwaysOnBottom = 4, /// 1331 cannotBeActivated = 8, /// 1332 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. 1333 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. 1334 /++ 1335 Sets the window as a short-lived child of its parent, but unlike an ordinary child, 1336 it is still a top-level window. This should NOT be set separately for most window types. 1337 1338 A transient window will not keep the application open if its main window closes. 1339 1340 $(PITFALL This may not be correctly implemented and its behavior is subject to change.) 1341 1342 1343 From the ICCM: 1344 1345 $(BLOCKQUOTE 1346 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. 1347 1348 $(CITE https://tronche.com/gui/x/icccm/sec-4.html) 1349 ) 1350 1351 So if you are using a window type that already describes this like [WindowTypes.dropdownMenu] etc., you should not use this flag. 1352 1353 History: 1354 Added February 23, 2021 but not yet stabilized. 1355 +/ 1356 transient = 64, 1357 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1358 } 1359 1360 /++ 1361 When creating a window, you can pass a type to SimpleWindow's constructor, 1362 then further customize the window by changing `WindowFlags`. 1363 1364 1365 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1366 use. The others are there to build a foundation for a higher level GUI toolkit, 1367 but are themselves not as high level as you might think from their names. 1368 1369 This list is based on the EMWH spec for X11. 1370 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1371 +/ 1372 enum WindowTypes : int { 1373 /// An ordinary application window. 1374 normal, 1375 /// 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. 1376 undecorated, 1377 /// 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. 1378 eventOnly, 1379 /// A drop down menu, such as from a menu bar 1380 dropdownMenu, 1381 /// A popup menu, such as from a right click 1382 popupMenu, 1383 /// A popup bubble notification 1384 notification, 1385 /* 1386 menu, /// a tearable menu bar 1387 splashScreen, /// a loading splash screen for your application 1388 tooltip, /// A tiny window showing temporary help text or something. 1389 comboBoxDropdown, 1390 dialog, 1391 toolbar 1392 */ 1393 /// a child nested inside the parent. You must pass a parent window to the ctor 1394 nestedChild, 1395 } 1396 1397 1398 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1399 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1400 private __gshared char* sdpyWindowClassStr = null; 1401 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1402 1403 /** 1404 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1405 You may want to change context version if you want to use advanced shaders or 1406 other modern OpenGL techinques. This setting doesn't affect already created 1407 windows. You may use version 2.1 as your default, which should be supported 1408 by any box since 2006, so seems to be a reasonable choice. 1409 1410 Note that by default version is set to `0`, which forces SimpleDisplay to use 1411 old context creation code without any version specified. This is the safest 1412 way to init OpenGL, but it may not give you access to advanced features. 1413 1414 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1415 */ 1416 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1417 1418 /** 1419 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1420 pipeline functions, and without "compatible" mode you won't be able to use 1421 your old non-shader-based code with such contexts. By default SimpleDisplay 1422 creates compatible context, so you can gradually upgrade your OpenGL code if 1423 you want to (or leave it as is, as it should "just work"). 1424 */ 1425 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1426 1427 /** 1428 Set to `true` to allow creating OpenGL context with lower version than requested 1429 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1430 `openGLContextFallbackActivated()` will return `true`. 1431 */ 1432 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1433 1434 /** 1435 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1436 */ 1437 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1438 1439 1440 /** 1441 Set window class name for all following `new SimpleWindow()` calls. 1442 1443 WARNING! For Windows, you should set your class name before creating any 1444 window, and NEVER change it after that! 1445 */ 1446 void sdpyWindowClass (const(char)[] v) { 1447 import core.stdc.stdlib : realloc; 1448 if (v.length == 0) v = "SimpleDisplayWindow"; 1449 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1450 if (sdpyWindowClassStr is null) return; // oops 1451 sdpyWindowClassStr[0..v.length+1] = 0; 1452 sdpyWindowClassStr[0..v.length] = v[]; 1453 } 1454 1455 /** 1456 Get current window class name. 1457 */ 1458 string sdpyWindowClass () { 1459 if (sdpyWindowClassStr is null) return null; 1460 foreach (immutable idx; 0..size_t.max-1) { 1461 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1462 } 1463 return null; 1464 } 1465 1466 /++ 1467 Returns the DPI of the default monitor. [0] is width, [1] is height (they are usually the same though). You may wish to round the numbers off. 1468 +/ 1469 float[2] getDpi() { 1470 float[2] dpi; 1471 version(Windows) { 1472 HDC screen = GetDC(null); 1473 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1474 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1475 } else version(X11) { 1476 auto display = XDisplayConnection.get; 1477 auto screen = DefaultScreen(display); 1478 1479 void fallback() { 1480 // 25.4 millimeters in an inch... 1481 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1482 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1483 } 1484 1485 char* resourceString = XResourceManagerString(display); 1486 XrmInitialize(); 1487 1488 auto db = XrmGetStringDatabase(resourceString); 1489 1490 if (resourceString) { 1491 XrmValue value; 1492 char* type; 1493 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1494 if (value.addr) { 1495 import core.stdc.stdlib; 1496 dpi[0] = atof(cast(char*) value.addr); 1497 dpi[1] = dpi[0]; 1498 } else { 1499 fallback(); 1500 } 1501 } else { 1502 fallback(); 1503 } 1504 } else { 1505 fallback(); 1506 } 1507 } 1508 1509 return dpi; 1510 } 1511 1512 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width, int height) { 1513 TrueColorImage got; 1514 version(X11) { 1515 auto display = XDisplayConnection.get; 1516 auto image = XGetImage(display, handle, 0, 0, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ImageFormat.ZPixmap); 1517 1518 // https://github.com/adamdruppe/arsd/issues/98 1519 1520 auto i = new Image(image); 1521 got = i.toTrueColorImage(); 1522 1523 XDestroyImage(image); 1524 } else version(Windows) { 1525 // I just need to BitBlt that shit... BUT WAIT IT IS ALREADY IN A DIB!!!!!!! 1526 1527 auto hdc = GetDC(handle); 1528 scope(exit) ReleaseDC(handle, hdc); 1529 auto i = new Image(width, height); 1530 HDC hdcMem = CreateCompatibleDC(hdc); 1531 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 1532 BitBlt(hdcMem, 0, 0, width, height, hdc, 0, 0, SRCCOPY); 1533 SelectObject(hdcMem, hbmOld); 1534 DeleteDC(hdcMem); 1535 1536 got = i.toTrueColorImage(); 1537 } else featureNotImplemented(); 1538 1539 return got; 1540 } 1541 1542 /++ 1543 The flagship window class. 1544 1545 1546 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1547 out of more advanced or complex features of the underlying windowing system. 1548 1549 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1550 and get a suitable window to work with. 1551 1552 From there, you can opt into additional features, like custom resizability and OpenGL support 1553 with the next two constructor arguments. Or, if you need even more, you can set a window type 1554 and customization flags with the final two constructor arguments. 1555 1556 If none of that works for you, you can also create a window using native function calls, then 1557 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1558 though, if you do this, managing the window is still your own responsibility! Notably, you 1559 will need to destroy it yourself. 1560 +/ 1561 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1562 1563 /++ 1564 Copies the window's current state into a [TrueColorImage]. 1565 1566 Be warned: this can be a very slow operation 1567 1568 History: 1569 Actually implemented on March 14, 2021 1570 +/ 1571 TrueColorImage takeScreenshot() { 1572 version(Windows) 1573 return trueColorImageFromNativeHandle(impl.hwnd, width, height); 1574 else version(OSXCocoa) 1575 throw new NotYetImplementedException(); 1576 else 1577 return trueColorImageFromNativeHandle(impl.window, width, height); 1578 } 1579 1580 version(X11) { 1581 void recreateAfterDisconnect() { 1582 if(!stateDiscarded) return; 1583 1584 if(_parent !is null && _parent.stateDiscarded) 1585 _parent.recreateAfterDisconnect(); 1586 1587 bool wasHidden = hidden; 1588 1589 activeScreenPainter = null; // should already be done but just to confirm 1590 1591 impl.createWindow(_width, _height, _title, openglMode, _parent); 1592 1593 if(auto dh = dropHandler) { 1594 dropHandler = null; 1595 enableDragAndDrop(this, dh); 1596 } 1597 1598 if(recreateAdditionalConnectionState) 1599 recreateAdditionalConnectionState(); 1600 1601 hidden = wasHidden; 1602 stateDiscarded = false; 1603 } 1604 1605 bool stateDiscarded; 1606 void discardConnectionState() { 1607 if(XDisplayConnection.display) 1608 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 1609 if(discardAdditionalConnectionState) 1610 discardAdditionalConnectionState(); 1611 stateDiscarded = true; 1612 } 1613 1614 void delegate() discardAdditionalConnectionState; 1615 void delegate() recreateAdditionalConnectionState; 1616 1617 } 1618 1619 private DropHandler dropHandler; 1620 1621 SimpleWindow _parent; 1622 bool beingOpenKeepsAppOpen = true; 1623 /++ 1624 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. 1625 1626 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 1627 1628 Params: 1629 1630 width = the width of the window's client area, in pixels 1631 height = the height of the window's client area, in pixels 1632 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 1633 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 1634 resizable = [Resizability] has three options: 1635 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 1636 $(P `fixedSize` will not allow the user to resize the window.) 1637 $(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.) 1638 windowType = The type of window you want to make. 1639 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. 1640 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". 1641 +/ 1642 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) { 1643 claimGuiThread(); 1644 version(sdpy_thread_checks) assert(thisIsGuiThread); 1645 this._width = width; 1646 this._height = height; 1647 this.openglMode = opengl; 1648 this.resizability = resizable; 1649 this.windowType = windowType; 1650 this.customizationFlags = customizationFlags; 1651 this._title = (title is null ? "D Application" : title); 1652 this._parent = parent; 1653 impl.createWindow(width, height, this._title, opengl, parent); 1654 1655 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild || (customizationFlags & WindowFlags.transient)) 1656 beingOpenKeepsAppOpen = false; 1657 } 1658 1659 /// ditto 1660 this(int width, int height, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { 1661 this(width, height, title, opengl, resizable, windowType, customizationFlags, parent); 1662 } 1663 1664 /// Same as above, except using the `Size` struct instead of separate width and height. 1665 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 1666 this(size.width, size.height, title, opengl, resizable); 1667 } 1668 1669 /// ditto 1670 this(Size size, string title, Resizability resizable, OpenGlOptions opengl = OpenGlOptions.no) { 1671 this(size, title, opengl, resizable); 1672 } 1673 1674 1675 /++ 1676 Creates a window based on the given [Image]. It's client area 1677 width and height is equal to the image. (A window's client area 1678 is the drawable space inside; it excludes the title bar, etc.) 1679 1680 Windows based on images will not be resizable and do not use OpenGL. 1681 1682 It will draw the image in upon creation, but this will be overwritten 1683 upon any draws, including the initial window visible event. 1684 1685 You probably do not want to use this and it may be removed from 1686 the library eventually, or I might change it to be a "permanent" 1687 background image; one that is automatically drawn on it before any 1688 other drawing event. idk. 1689 +/ 1690 this(Image image, string title = null) { 1691 this(image.width, image.height, title); 1692 this.image = image; 1693 } 1694 1695 /++ 1696 Wraps a native window handle with very little additional processing - notably no destruction 1697 this is incomplete so don't use it for much right now. The purpose of this is to make native 1698 windows created through the low level API (so you can use platform-specific options and 1699 other details SimpleWindow does not expose) available to the event loop wrappers. 1700 +/ 1701 this(NativeWindowHandle nativeWindow) { 1702 version(Windows) 1703 impl.hwnd = nativeWindow; 1704 else version(X11) { 1705 impl.window = nativeWindow; 1706 display = XDisplayConnection.get(); // get initial display to not segfault 1707 } else version(OSXCocoa) 1708 throw new NotYetImplementedException(); 1709 else featureNotImplemented(); 1710 // FIXME: set the size correctly 1711 _width = 1; 1712 _height = 1; 1713 nativeMapping[nativeWindow] = this; 1714 1715 beingOpenKeepsAppOpen = false; 1716 1717 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 1718 _suppressDestruction = true; // so it doesn't try to close 1719 } 1720 1721 /// Experimental, do not use yet 1722 /++ 1723 Grabs exclusive input from the user until you release it with 1724 [releaseInputGrab]. 1725 1726 1727 Note: it is extremely rude to do this without good reason. 1728 Reasons may include doing some kind of mouse drag operation 1729 or popping up a temporary menu that should get events and will 1730 be dismissed at ease by the user clicking away. 1731 1732 Params: 1733 keyboard = do you want to grab keyboard input? 1734 mouse = grab mouse input? 1735 confine = confine the mouse cursor to inside this window? 1736 1737 History: 1738 Prior to March 11, 2021, grabbing the keyboard would always also 1739 set the X input focus. Now, it only focuses if it is a non-transient 1740 window and otherwise manages the input direction internally. 1741 1742 This means spurious focus/blur events will no longer be sent and the 1743 application will not steal focus from other applications (which the 1744 window manager may have rejected anyway). 1745 +/ 1746 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 1747 static if(UsingSimpledisplayX11) { 1748 XSync(XDisplayConnection.get, 0); 1749 if(keyboard) { 1750 if(isTransient && _parent) { 1751 /* 1752 FIXME: 1753 setting the keyboard focus is not actually that helpful, what I more likely want 1754 is the events from the parent window to be sent over here if we're transient. 1755 */ 1756 1757 _parent.inputProxy = this; 1758 } else { 1759 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 1760 } 1761 } 1762 if(mouse) { 1763 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 1764 EventMask.PointerMotionMask // FIXME: not efficient 1765 | EventMask.ButtonPressMask 1766 | EventMask.ButtonReleaseMask 1767 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 1768 ) 1769 { 1770 XSync(XDisplayConnection.get, 0); 1771 import core.stdc.stdio; 1772 printf("Grab input failed %d\n", res); 1773 //throw new Exception("Grab input failed"); 1774 } else { 1775 // cool 1776 } 1777 } 1778 1779 } else version(Windows) { 1780 // FIXME: keyboard? 1781 SetCapture(impl.hwnd); 1782 if(confine) { 1783 RECT rcClip; 1784 //RECT rcOldClip; 1785 //GetClipCursor(&rcOldClip); 1786 GetWindowRect(hwnd, &rcClip); 1787 ClipCursor(&rcClip); 1788 } 1789 } else version(OSXCocoa) { 1790 throw new NotYetImplementedException(); 1791 } else static assert(0); 1792 } 1793 1794 private bool isTransient() { 1795 with(WindowTypes) 1796 final switch(windowType) { 1797 case normal, undecorated, eventOnly: 1798 case nestedChild: 1799 return (customizationFlags & WindowFlags.transient) ? true : false; 1800 case dropdownMenu, popupMenu, notification: 1801 return true; 1802 } 1803 } 1804 1805 private SimpleWindow inputProxy; 1806 1807 /++ 1808 Releases the grab acquired by [grabInput]. 1809 +/ 1810 void releaseInputGrab() { 1811 static if(UsingSimpledisplayX11) { 1812 XUngrabPointer(XDisplayConnection.get, CurrentTime); 1813 if(_parent) 1814 _parent.inputProxy = null; 1815 } else version(Windows) { 1816 ReleaseCapture(); 1817 ClipCursor(null); 1818 } else version(OSXCocoa) { 1819 throw new NotYetImplementedException(); 1820 } else static assert(0); 1821 } 1822 1823 /++ 1824 Sets the input focus to this window. 1825 1826 You shouldn't call this very often - please let the user control the input focus. 1827 +/ 1828 void focus() { 1829 static if(UsingSimpledisplayX11) { 1830 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 1831 } else version(Windows) { 1832 SetFocus(this.impl.hwnd); 1833 } else version(OSXCocoa) { 1834 throw new NotYetImplementedException(); 1835 } else static assert(0); 1836 } 1837 1838 /++ 1839 Requests attention from the user for this window. 1840 1841 1842 The typical result of this function is to change the color 1843 of the taskbar icon, though it may be tweaked on specific 1844 platforms. 1845 1846 It is meant to unobtrusively tell the user that something 1847 relevant to them happened in the background and they should 1848 check the window when they get a chance. Upon receiving the 1849 keyboard focus, the window will automatically return to its 1850 natural state. 1851 1852 If the window already has the keyboard focus, this function 1853 may do nothing, because the user is presumed to already be 1854 giving the window attention. 1855 1856 Implementation_note: 1857 1858 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 1859 atom on X11 and the FlashWindow function on Windows. 1860 +/ 1861 void requestAttention() { 1862 if(_focused) 1863 return; 1864 1865 version(Windows) { 1866 FLASHWINFO info; 1867 info.cbSize = info.sizeof; 1868 info.hwnd = impl.hwnd; 1869 info.dwFlags = FLASHW_TRAY; 1870 info.uCount = 1; 1871 1872 FlashWindowEx(&info); 1873 1874 } else version(X11) { 1875 demandingAttention = true; 1876 demandAttention(this, true); 1877 } else version(OSXCocoa) { 1878 throw new NotYetImplementedException(); 1879 } else static assert(0); 1880 } 1881 1882 private bool _focused; 1883 1884 version(X11) private bool demandingAttention; 1885 1886 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 1887 /// You'll have to call `close()` manually if you set this delegate. 1888 void delegate () closeQuery; 1889 1890 /// This will be called when window visibility was changed. 1891 void delegate (bool becomesVisible) visibilityChanged; 1892 1893 /// This will be called when window becomes visible for the first time. 1894 /// You can do OpenGL initialization here. Note that in X11 you can't call 1895 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 1896 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 1897 private bool _visibleForTheFirstTimeCalled; 1898 void delegate () visibleForTheFirstTime; 1899 1900 /// Returns true if the window has been closed. 1901 final @property bool closed() { return _closed; } 1902 1903 /// Returns true if the window is focused. 1904 final @property bool focused() { return _focused; } 1905 1906 private bool _visible; 1907 /// Returns true if the window is visible (mapped). 1908 final @property bool visible() { return _visible; } 1909 1910 /// Closes the window. If there are no more open windows, the event loop will terminate. 1911 void close() { 1912 if (!_closed) { 1913 runInGuiThread( { 1914 if(_closed) return; // another thread got to it first. this isn't a big deal, it just means our message was queued 1915 if (onClosing !is null) onClosing(); 1916 impl.closeWindow(); 1917 _closed = true; 1918 } ); 1919 } 1920 } 1921 1922 /++ 1923 `close` is one of the few methods that can be called from other threads. This `shared` overload reflects that. 1924 1925 History: 1926 Overload added on March 7, 2021. 1927 +/ 1928 void close() shared { 1929 (cast() this).close(); 1930 } 1931 1932 /++ 1933 1934 +/ 1935 void maximize() { 1936 version(Windows) 1937 ShowWindow(impl.hwnd, SW_MAXIMIZE); 1938 else version(X11) { 1939 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_MAXIMIZED_VERT", false)(XDisplayConnection.get), true, GetAtom!("_NET_WM_STATE_MAXIMIZED_HORZ", false)(XDisplayConnection.get)); 1940 1941 // also note _NET_WM_STATE_FULLSCREEN 1942 } 1943 1944 } 1945 1946 private bool _fullscreen; 1947 1948 /// not fully implemented but planned for a future release 1949 void fullscreen(bool yes) { 1950 version(X11) 1951 setNetWmStateAtom(this.impl.window, GetAtom!("_NET_WM_STATE_FULLSCREEN", false)(XDisplayConnection.get), yes); 1952 1953 _fullscreen = yes; 1954 1955 } 1956 1957 bool fullscreen() { 1958 return _fullscreen; 1959 } 1960 1961 /++ 1962 Note: only implemented on Windows. No-op on other platforms. You may want to use [hide] instead. 1963 1964 +/ 1965 void minimize() { 1966 version(Windows) 1967 ShowWindow(impl.hwnd, SW_MINIMIZE); 1968 //else version(X11) 1969 //setNetWmStateAtom(this, GetAtom!("_NET_WM_STATE_MINIMIZED", false)(XDisplayConnection.get), true); 1970 } 1971 1972 /// Alias for `hidden = false` 1973 void show() { 1974 hidden = false; 1975 } 1976 1977 /// Alias for `hidden = true` 1978 void hide() { 1979 hidden = true; 1980 } 1981 1982 /// Hide cursor when it enters the window. 1983 void hideCursor() { 1984 version(OSXCocoa) throw new NotYetImplementedException(); else 1985 if (!_closed) impl.hideCursor(); 1986 } 1987 1988 /// Don't hide cursor when it enters the window. 1989 void showCursor() { 1990 version(OSXCocoa) throw new NotYetImplementedException(); else 1991 if (!_closed) impl.showCursor(); 1992 } 1993 1994 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 1995 * 1996 * Please remember that the cursor is a shared resource that should usually be left to the user's 1997 * control. Try to think for other approaches before using this function. 1998 * 1999 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 2000 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 2001 * receive "mouse moved here" event. 2002 */ 2003 bool warpMouse (int x, int y) { 2004 version(X11) { 2005 if (!_closed) { impl.warpMouse(x, y); return true; } 2006 } else version(Windows) { 2007 if (!_closed) { 2008 POINT point; 2009 point.x = x; 2010 point.y = y; 2011 if(ClientToScreen(impl.hwnd, &point)) { 2012 SetCursorPos(point.x, point.y); 2013 return true; 2014 } 2015 } 2016 } 2017 return false; 2018 } 2019 2020 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 2021 void sendDummyEvent () { 2022 version(X11) { 2023 if (!_closed) { impl.sendDummyEvent(); } 2024 } 2025 } 2026 2027 /// Set window minimal size. 2028 void setMinSize (int minwidth, int minheight) { 2029 version(OSXCocoa) throw new NotYetImplementedException(); else 2030 if (!_closed) impl.setMinSize(minwidth, minheight); 2031 } 2032 2033 /// Set window maximal size. 2034 void setMaxSize (int maxwidth, int maxheight) { 2035 version(OSXCocoa) throw new NotYetImplementedException(); else 2036 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 2037 } 2038 2039 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 2040 /// Currently only supported on X11. 2041 void setResizeGranularity (int granx, int grany) { 2042 version(OSXCocoa) throw new NotYetImplementedException(); else 2043 if (!_closed) impl.setResizeGranularity(granx, grany); 2044 } 2045 2046 /// Move window. 2047 void move(int x, int y) { 2048 version(OSXCocoa) throw new NotYetImplementedException(); else 2049 if (!_closed) impl.move(x, y); 2050 } 2051 2052 /// ditto 2053 void move(Point p) { 2054 version(OSXCocoa) throw new NotYetImplementedException(); else 2055 if (!_closed) impl.move(p.x, p.y); 2056 } 2057 2058 /++ 2059 Resize window. 2060 2061 Note that the width and height of the window are NOT instantly 2062 updated - it waits for the window manager to approve the resize 2063 request, which means you must return to the event loop before the 2064 width and height are actually changed. 2065 +/ 2066 void resize(int w, int h) { 2067 if(!_closed && _fullscreen) fullscreen = false; 2068 version(OSXCocoa) throw new NotYetImplementedException(); else 2069 if (!_closed) impl.resize(w, h); 2070 } 2071 2072 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 2073 void moveResize (int x, int y, int w, int h) { 2074 if(!_closed && _fullscreen) fullscreen = false; 2075 version(OSXCocoa) throw new NotYetImplementedException(); else 2076 if (!_closed) impl.moveResize(x, y, w, h); 2077 } 2078 2079 private bool _hidden; 2080 2081 /// Returns true if the window is hidden. 2082 final @property bool hidden() { 2083 return _hidden; 2084 } 2085 2086 /// Shows or hides the window based on the bool argument. 2087 final @property void hidden(bool b) { 2088 _hidden = b; 2089 version(Windows) { 2090 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 2091 } else version(X11) { 2092 if(b) 2093 //XUnmapWindow(impl.display, impl.window); 2094 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 2095 else 2096 XMapWindow(impl.display, impl.window); 2097 } else version(OSXCocoa) { 2098 throw new NotYetImplementedException(); 2099 } else static assert(0); 2100 } 2101 2102 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 2103 void opacity(double opacity) @property 2104 in { 2105 assert(opacity >= 0 && opacity <= 1); 2106 } do { 2107 version (Windows) { 2108 impl.setOpacity(cast(ubyte)(255 * opacity)); 2109 } else version (X11) { 2110 impl.setOpacity(cast(uint)(uint.max * opacity)); 2111 } else throw new NotYetImplementedException(); 2112 } 2113 2114 /++ 2115 Sets your event handlers, without entering the event loop. Useful if you 2116 have multiple windows - set the handlers on each window, then only do eventLoop on your main window. 2117 +/ 2118 void setEventHandlers(T...)(T eventHandlers) { 2119 // FIXME: add more events 2120 foreach(handler; eventHandlers) { 2121 static if(__traits(compiles, handleKeyEvent = handler)) { 2122 handleKeyEvent = handler; 2123 } else static if(__traits(compiles, handleCharEvent = handler)) { 2124 handleCharEvent = handler; 2125 } else static if(__traits(compiles, handlePulse = handler)) { 2126 handlePulse = handler; 2127 } else static if(__traits(compiles, handleMouseEvent = handler)) { 2128 handleMouseEvent = handler; 2129 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 2130 } 2131 } 2132 2133 /// The event loop automatically returns when the window is closed 2134 /// pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 2135 /// pulse timer is created. The event loop will block until an event 2136 /// arrives or the pulse timer goes off. 2137 final int eventLoop(T...)( 2138 long pulseTimeout, /// set to zero if you don't want a pulse. 2139 T eventHandlers) /// delegate list like std.concurrency.receive 2140 { 2141 setEventHandlers(eventHandlers); 2142 2143 version(with_eventloop) { 2144 // delegates event loop to my other module 2145 version(X11) 2146 XFlush(display); 2147 2148 import arsd.eventloop; 2149 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 2150 scope(exit) clearInterval(handle); 2151 2152 loop(); 2153 return 0; 2154 } else version(OSXCocoa) { 2155 // FIXME 2156 if (handlePulse !is null && pulseTimeout != 0) { 2157 timer = scheduledTimer(pulseTimeout*1e-3, 2158 view, sel_registerName("simpledisplay_pulse"), 2159 null, true); 2160 } 2161 2162 setNeedsDisplay(view, true); 2163 run(NSApp); 2164 return 0; 2165 } else { 2166 EventLoop el = EventLoop(pulseTimeout, handlePulse); 2167 return el.run(); 2168 } 2169 } 2170 2171 /++ 2172 This lets you draw on the window (or its backing buffer) using basic 2173 2D primitives. 2174 2175 Be sure to call this in a limited scope because your changes will not 2176 actually appear on the window until ScreenPainter's destructor runs. 2177 2178 Returns: an instance of [ScreenPainter], which has the drawing methods 2179 on it to draw on this window. 2180 +/ 2181 ScreenPainter draw() { 2182 return impl.getPainter(); 2183 } 2184 2185 // This is here to implement the interface we use for various native handlers. 2186 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 2187 2188 // maps native window handles to SimpleWindow instances, if there are any 2189 // you shouldn't need this, but it is public in case you do in a native event handler or something 2190 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 2191 2192 /// Width of the window's drawable client area, in pixels. 2193 @scriptable 2194 final @property int width() const pure nothrow @safe @nogc { return _width; } 2195 2196 /// Height of the window's drawable client area, in pixels. 2197 @scriptable 2198 final @property int height() const pure nothrow @safe @nogc { return _height; } 2199 2200 private int _width; 2201 private int _height; 2202 2203 // HACK: making the best of some copy constructor woes with refcounting 2204 private ScreenPainterImplementation* activeScreenPainter_; 2205 2206 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 2207 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 2208 2209 private OpenGlOptions openglMode; 2210 private Resizability resizability; 2211 private WindowTypes windowType; 2212 private int customizationFlags; 2213 2214 /// `true` if OpenGL was initialized for this window. 2215 @property bool isOpenGL () const pure nothrow @safe @nogc { 2216 version(without_opengl) 2217 return false; 2218 else 2219 return (openglMode == OpenGlOptions.yes); 2220 } 2221 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 2222 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 2223 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 2224 2225 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2226 /// to call this, as it's not recommended to share window between threads. 2227 void mtLock () { 2228 version(X11) { 2229 XLockDisplay(this.display); 2230 } 2231 } 2232 2233 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2234 /// to call this, as it's not recommended to share window between threads. 2235 void mtUnlock () { 2236 version(X11) { 2237 XUnlockDisplay(this.display); 2238 } 2239 } 2240 2241 /// Emit a beep to get user's attention. 2242 void beep () { 2243 version(X11) { 2244 XBell(this.display, 100); 2245 } else version(Windows) { 2246 MessageBeep(0xFFFFFFFF); 2247 } 2248 } 2249 2250 2251 2252 version(without_opengl) {} else { 2253 2254 /// 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`. 2255 void delegate() redrawOpenGlScene; 2256 2257 /// This will allow you to change OpenGL vsync state. 2258 final @property void vsync (bool wait) { 2259 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2260 version(X11) { 2261 setAsCurrentOpenGlContext(); 2262 glxSetVSync(display, impl.window, wait); 2263 } else version(Windows) { 2264 setAsCurrentOpenGlContext(); 2265 wglSetVSync(wait); 2266 } 2267 } 2268 2269 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 2270 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 2271 /// enough without waiting 'em to finish their frame bussiness. 2272 bool useGLFinish = true; 2273 2274 // FIXME: it should schedule it for the end of the current iteration of the event loop... 2275 /// 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. 2276 void redrawOpenGlSceneNow() { 2277 version(X11) if (!this._visible) return; // no need to do this if window is invisible 2278 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2279 if(redrawOpenGlScene is null) 2280 return; 2281 2282 this.mtLock(); 2283 scope(exit) this.mtUnlock(); 2284 2285 this.setAsCurrentOpenGlContext(); 2286 2287 redrawOpenGlScene(); 2288 2289 this.swapOpenGlBuffers(); 2290 // 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. 2291 if (useGLFinish) glFinish(); 2292 } 2293 2294 private bool redrawOpenGlSceneSoonSet = false; 2295 private static class RedrawOpenGlSceneEvent { 2296 SimpleWindow w; 2297 this(SimpleWindow w) { this.w = w; } 2298 } 2299 private RedrawOpenGlSceneEvent redrawOpenGlSceneEvent; 2300 void redrawOpenGlSceneSoon() { 2301 if(!redrawOpenGlSceneSoonSet) { 2302 redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this); 2303 this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); }); 2304 redrawOpenGlSceneSoonSet = true; 2305 } 2306 this.postEvent(redrawOpenGlSceneEvent, true); 2307 } 2308 2309 2310 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2311 void setAsCurrentOpenGlContext() { 2312 assert(openglMode == OpenGlOptions.yes); 2313 version(X11) { 2314 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 2315 throw new Exception("glXMakeCurrent"); 2316 } else version(Windows) { 2317 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2318 if (!wglMakeCurrent(ghDC, ghRC)) 2319 throw new Exception("wglMakeCurrent"); // let windows users suffer too 2320 } 2321 } 2322 2323 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 2324 /// This doesn't throw, returning success flag instead. 2325 bool setAsCurrentOpenGlContextNT() nothrow { 2326 assert(openglMode == OpenGlOptions.yes); 2327 version(X11) { 2328 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 2329 } else version(Windows) { 2330 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2331 return wglMakeCurrent(ghDC, ghRC) ? true : false; 2332 } 2333 } 2334 2335 /// 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. 2336 /// This doesn't throw, returning success flag instead. 2337 bool releaseCurrentOpenGlContext() nothrow { 2338 assert(openglMode == OpenGlOptions.yes); 2339 version(X11) { 2340 return (glXMakeCurrent(display, 0, null) != 0); 2341 } else version(Windows) { 2342 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 2343 return wglMakeCurrent(ghDC, null) ? true : false; 2344 } 2345 } 2346 2347 /++ 2348 simpledisplay always uses double buffering, usually automatically. This 2349 manually swaps the OpenGL buffers. 2350 2351 2352 You should not need to call this yourself because simpledisplay will do it 2353 for you after calling your `redrawOpenGlScene`. 2354 2355 Remember that this may throw an exception, which you can catch in a multithreaded 2356 application to keep your thread from dying from an unhandled exception. 2357 +/ 2358 void swapOpenGlBuffers() { 2359 assert(openglMode == OpenGlOptions.yes); 2360 version(X11) { 2361 if (!this._visible) return; // no need to do this if window is invisible 2362 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 2363 glXSwapBuffers(display, impl.window); 2364 } else version(Windows) { 2365 SwapBuffers(ghDC); 2366 } 2367 } 2368 } 2369 2370 /++ 2371 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 2372 2373 2374 --- 2375 auto window = new SimpleWindow(100, 100, "First title"); 2376 window.title = "A new title"; 2377 --- 2378 2379 You may call this function at any time. 2380 +/ 2381 @property void title(string title) { 2382 _title = title; 2383 version(OSXCocoa) throw new NotYetImplementedException(); else 2384 impl.setTitle(title); 2385 } 2386 2387 private string _title; 2388 2389 /// Gets the title 2390 @property string title() { 2391 if(_title is null) 2392 _title = getRealTitle(); 2393 return _title; 2394 } 2395 2396 /++ 2397 Get the title as set by the window manager. 2398 May not match what you attempted to set. 2399 +/ 2400 string getRealTitle() { 2401 static if(is(typeof(impl.getTitle()))) 2402 return impl.getTitle(); 2403 else 2404 return null; 2405 } 2406 2407 // don't use this generally it is not yet really released 2408 version(X11) 2409 @property Image secret_icon() { 2410 return secret_icon_inner; 2411 } 2412 private Image secret_icon_inner; 2413 2414 2415 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. 2416 @property void icon(MemoryImage icon) { 2417 auto tci = icon.getAsTrueColorImage(); 2418 version(Windows) { 2419 winIcon = new WindowsIcon(icon); 2420 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 2421 } else version(X11) { 2422 secret_icon_inner = Image.fromMemoryImage(icon); 2423 // FIXME: ensure this is correct 2424 auto display = XDisplayConnection.get; 2425 arch_ulong[] buffer; 2426 buffer ~= icon.width; 2427 buffer ~= icon.height; 2428 foreach(c; tci.imageData.colors) { 2429 arch_ulong b; 2430 b |= c.a << 24; 2431 b |= c.r << 16; 2432 b |= c.g << 8; 2433 b |= c.b; 2434 buffer ~= b; 2435 } 2436 2437 XChangeProperty( 2438 display, 2439 impl.window, 2440 GetAtom!("_NET_WM_ICON", true)(display), 2441 GetAtom!"CARDINAL"(display), 2442 32 /* bits */, 2443 0 /*PropModeReplace*/, 2444 buffer.ptr, 2445 cast(int) buffer.length); 2446 } else version(OSXCocoa) { 2447 throw new NotYetImplementedException(); 2448 } else static assert(0); 2449 } 2450 2451 version(Windows) 2452 private WindowsIcon winIcon; 2453 2454 bool _suppressDestruction; 2455 2456 ~this() { 2457 if(_suppressDestruction) 2458 return; 2459 impl.dispose(); 2460 } 2461 2462 private bool _closed; 2463 2464 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 2465 /* 2466 ScreenPainter drawTransiently() { 2467 return impl.getPainter(); 2468 } 2469 */ 2470 2471 /// Draws an image on the window. This is meant to provide quick look 2472 /// of a static image generated elsewhere. 2473 @property void image(Image i) { 2474 version(Windows) { 2475 BITMAP bm; 2476 HDC hdc = GetDC(hwnd); 2477 HDC hdcMem = CreateCompatibleDC(hdc); 2478 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 2479 2480 GetObject(i.handle, bm.sizeof, &bm); 2481 2482 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 2483 2484 SelectObject(hdcMem, hbmOld); 2485 DeleteDC(hdcMem); 2486 ReleaseDC(hwnd, hdc); 2487 2488 /* 2489 RECT r; 2490 r.right = i.width; 2491 r.bottom = i.height; 2492 InvalidateRect(hwnd, &r, false); 2493 */ 2494 } else 2495 version(X11) { 2496 if(!destroyed) { 2497 if(i.usingXshm) 2498 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 2499 else 2500 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 2501 } 2502 } else 2503 version(OSXCocoa) { 2504 draw().drawImage(Point(0, 0), i); 2505 setNeedsDisplay(view, true); 2506 } else static assert(0); 2507 } 2508 2509 /++ 2510 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 2511 2512 --- 2513 window.cursor = GenericCursor.Help; 2514 // now the window mouse cursor is set to a generic help 2515 --- 2516 2517 +/ 2518 @property void cursor(MouseCursor cursor) { 2519 version(OSXCocoa) 2520 featureNotImplemented(); 2521 else 2522 if(this.impl.curHidden <= 0) { 2523 static if(UsingSimpledisplayX11) { 2524 auto ch = cursor.cursorHandle; 2525 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 2526 } else version(Windows) { 2527 auto ch = cursor.cursorHandle; 2528 impl.currentCursor = ch; 2529 SetCursor(ch); // redraw without waiting for mouse movement to update 2530 } else featureNotImplemented(); 2531 } 2532 2533 } 2534 2535 /// What follows are the event handlers. These are set automatically 2536 /// by the eventLoop function, but are still public so you can change 2537 /// them later. wasPressed == true means key down. false == key up. 2538 2539 /// Handles a low-level keyboard event. Settable through setEventHandlers. 2540 void delegate(KeyEvent ke) handleKeyEvent; 2541 2542 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 2543 void delegate(dchar c) handleCharEvent; 2544 2545 /// Handles a timer pulse. Settable through setEventHandlers. 2546 void delegate() handlePulse; 2547 2548 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 2549 void delegate(bool) onFocusChange; 2550 2551 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 2552 * Sometimes it is easier to setup the delegate instead of subclassing. */ 2553 void delegate() onClosing; 2554 2555 /** Called when we received destroy notification. At this stage we cannot do much with our window 2556 * (as it is already dead, and it's native handle cannot be used), but we still can do some 2557 * last minute cleanup. */ 2558 void delegate() onDestroyed; 2559 2560 static if (UsingSimpledisplayX11) 2561 /** Called when Expose event comes. See Xlib manual to understand the arguments. 2562 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 2563 * You will probably never need to setup this handler, it is for very low-level stuff. 2564 * 2565 * WARNING! Xlib is multithread-locked when this handles is called! */ 2566 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 2567 2568 //version(Windows) 2569 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 2570 2571 private { 2572 int lastMouseX = int.min; 2573 int lastMouseY = int.min; 2574 void mdx(ref MouseEvent ev) { 2575 if(lastMouseX == int.min || lastMouseY == int.min) { 2576 ev.dx = 0; 2577 ev.dy = 0; 2578 } else { 2579 ev.dx = ev.x - lastMouseX; 2580 ev.dy = ev.y - lastMouseY; 2581 } 2582 2583 lastMouseX = ev.x; 2584 lastMouseY = ev.y; 2585 } 2586 } 2587 2588 /// Mouse event handler. Settable through setEventHandlers. 2589 void delegate(MouseEvent) handleMouseEvent; 2590 2591 /// use to redraw child widgets if you use system apis to add stuff 2592 void delegate() paintingFinished; 2593 2594 void delegate() paintingFinishedDg() { 2595 return paintingFinished; 2596 } 2597 2598 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 2599 /// for this to ever happen. 2600 void delegate(int width, int height) windowResized; 2601 2602 /** Platform specific - handle any native messages this window gets. 2603 * 2604 * Note: this is called *in addition to* other event handlers, unless you return zero indicating that you handled it. 2605 2606 * On Windows, it takes the form of int delegate(HWND,UINT, WPARAM, LPARAM). 2607 2608 * On X11, it takes the form of int delegate(XEvent). 2609 2610 * IMPORTANT: it used to be static in old versions of simpledisplay.d, but I always used 2611 * it as if it wasn't static... so now I just fixed it so it isn't anymore. 2612 **/ 2613 NativeEventHandler handleNativeEvent; 2614 2615 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 2616 /// If you used to use handleNativeEvent depending on it being static, just change it to use 2617 /// this instead and it will work the same way. 2618 __gshared NativeEventHandler handleNativeGlobalEvent; 2619 2620 // private: 2621 /// The native implementation is available, but you shouldn't use it unless you are 2622 /// familiar with the underlying operating system, don't mind depending on it, and 2623 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 2624 /// do what you need to do with handleNativeEvent instead. 2625 /// 2626 /// This is likely to eventually change to be just a struct holding platform-specific 2627 /// handles instead of a template mixin at some point because I'm not happy with the 2628 /// code duplication here (ironically). 2629 mixin NativeSimpleWindowImplementation!() impl; 2630 2631 /** 2632 This is in-process one-way (from anything to window) event sending mechanics. 2633 It is thread-safe, so it can be used in multi-threaded applications to send, 2634 for example, "wake up and repaint" events when thread completed some operation. 2635 This will allow to avoid using timer pulse to check events with synchronization, 2636 'cause event handler will be called in UI thread. You can stop guessing which 2637 pulse frequency will be enough for your app. 2638 Note that events handlers may be called in arbitrary order, i.e. last registered 2639 handler can be called first, and vice versa. 2640 */ 2641 public: 2642 /** Is our custom event queue empty? Can be used in simple cases to prevent 2643 * "spamming" window with events it can't cope with. 2644 * It is safe to call this from non-UI threads. 2645 */ 2646 @property bool eventQueueEmpty() () { 2647 synchronized(this) { 2648 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 2649 } 2650 return true; 2651 } 2652 2653 /** Does our custom event queue contains at least one with the given type? 2654 * Can be used in simple cases to prevent "spamming" window with events 2655 * it can't cope with. 2656 * It is safe to call this from non-UI threads. 2657 */ 2658 @property bool eventQueued(ET:Object) () { 2659 synchronized(this) { 2660 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 2661 if (!o.doProcess) { 2662 if (cast(ET)(o.evt)) return true; 2663 } 2664 } 2665 } 2666 return false; 2667 } 2668 2669 /++ 2670 Event listeners added with [addEventListener] have their exceptions swallowed by the event loop. This delegate can handle them again before it proceeds. 2671 2672 History: 2673 Added May 12, 2021 2674 +/ 2675 void delegate(Exception e) nothrow eventUncaughtException; 2676 2677 /** Add listener for custom event. Can be used like this: 2678 * 2679 * --------------------- 2680 * auto eid = win.addEventListener((MyStruct evt) { ... }); 2681 * ... 2682 * win.removeEventListener(eid); 2683 * --------------------- 2684 * 2685 * Returns: 0 on failure (should never happen, so ignore it) 2686 * 2687 * $(WARNING Don't use this method in object destructors!) 2688 * 2689 * $(WARNING It is better to register all event handlers and don't remove 'em, 2690 * 'cause if event handler id counter will overflow, you won't be able 2691 * to register any more events.) 2692 */ 2693 uint addEventListener(ET:Object) (void delegate (ET) dg) { 2694 if (dg is null) return 0; // ignore empty handlers 2695 synchronized(this) { 2696 //FIXME: abort on overflow? 2697 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 2698 EventHandlerEntry e; 2699 e.dg = delegate (Object o) { 2700 if (auto co = cast(ET)o) { 2701 try { 2702 dg(co); 2703 } catch (Exception e) { 2704 // sorry! 2705 if(eventUncaughtException) 2706 eventUncaughtException(e); 2707 } 2708 return true; 2709 } 2710 return false; 2711 }; 2712 e.id = lastUsedHandlerId; 2713 auto optr = eventHandlers.ptr; 2714 eventHandlers ~= e; 2715 if (eventHandlers.ptr !is optr) { 2716 import core.memory : GC; 2717 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 2718 } 2719 return lastUsedHandlerId; 2720 } 2721 } 2722 2723 /// Remove event listener. It is safe to pass invalid event id here. 2724 /// $(WARNING Don't use this method in object destructors!) 2725 void removeEventListener() (uint id) { 2726 if (id == 0 || id > lastUsedHandlerId) return; 2727 synchronized(this) { 2728 foreach (immutable idx; 0..eventHandlers.length) { 2729 if (eventHandlers[idx].id == id) { 2730 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 2731 eventHandlers[$-1].dg = null; 2732 eventHandlers.length -= 1; 2733 eventHandlers.assumeSafeAppend; 2734 return; 2735 } 2736 } 2737 } 2738 } 2739 2740 /// Post event to queue. It is safe to call this from non-UI threads. 2741 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 2742 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 2743 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 2744 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 2745 if (this.closed) return false; // closed windows can't handle events 2746 2747 // remove all events of type `ET` 2748 void removeAllET () { 2749 uint eidx = 0, ec = eventQueueUsed; 2750 auto eptr = eventQueue.ptr; 2751 while (eidx < ec) { 2752 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 2753 if (cast(ET)eptr.evt !is null) { 2754 // i found her! 2755 if (inCustomEventProcessor) { 2756 // if we're in custom event processing loop, processor will clear it for us 2757 eptr.evt = null; 2758 ++eidx; 2759 ++eptr; 2760 } else { 2761 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 2762 ec = --eventQueueUsed; 2763 // clear last event (it is already copied) 2764 eventQueue.ptr[ec].evt = null; 2765 } 2766 } else { 2767 ++eidx; 2768 ++eptr; 2769 } 2770 } 2771 } 2772 2773 if (evt is null) { 2774 if (replace) { synchronized(this) removeAllET(); } 2775 // ignore empty events, they can't be handled anyway 2776 return false; 2777 } 2778 2779 // add events even if no event FD/event object created yet 2780 synchronized(this) { 2781 if (replace) removeAllET(); 2782 if (eventQueueUsed == uint.max) return false; // just in case 2783 if (eventQueueUsed < eventQueue.length) { 2784 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 2785 } else { 2786 if (eventQueue.capacity == eventQueue.length) { 2787 // need to reallocate; do a trick to ensure that old array is cleared 2788 auto oarr = eventQueue; 2789 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 2790 // just in case, do yet another check 2791 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 2792 import core.memory : GC; 2793 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 2794 } else { 2795 auto optr = eventQueue.ptr; 2796 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 2797 assert(eventQueue.ptr is optr); 2798 } 2799 ++eventQueueUsed; 2800 assert(eventQueueUsed == eventQueue.length); 2801 } 2802 if (!eventWakeUp()) { 2803 // can't wake up event processor, so there is no reason to keep the event 2804 assert(eventQueueUsed > 0); 2805 eventQueue[--eventQueueUsed].evt = null; 2806 return false; 2807 } 2808 return true; 2809 } 2810 } 2811 2812 /// Post event to queue. It is safe to call this from non-UI threads. 2813 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 2814 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 2815 bool postEvent(ET:Object) (ET evt, bool replace=false) { 2816 return postTimeout!ET(evt, 0, replace); 2817 } 2818 2819 private: 2820 private import core.time : MonoTime; 2821 2822 version(Posix) { 2823 __gshared int customEventFDRead = -1; 2824 __gshared int customEventFDWrite = -1; 2825 __gshared int customSignalFD = -1; 2826 } else version(Windows) { 2827 __gshared HANDLE customEventH = null; 2828 } 2829 2830 // wake up event processor 2831 static bool eventWakeUp () { 2832 version(X11) { 2833 import core.sys.posix.unistd : write; 2834 ulong n = 1; 2835 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 2836 return true; 2837 } else version(Windows) { 2838 if (customEventH !is null) SetEvent(customEventH); 2839 return true; 2840 } else { 2841 // not implemented for other OSes 2842 return false; 2843 } 2844 } 2845 2846 static struct QueuedEvent { 2847 Object evt; 2848 bool timed = false; 2849 MonoTime hittime = MonoTime.zero; 2850 bool doProcess = false; // process event at the current iteration (internal flag) 2851 2852 this (Object aevt, uint toutmsecs) { 2853 evt = aevt; 2854 if (toutmsecs > 0) { 2855 import core.time : msecs; 2856 timed = true; 2857 hittime = MonoTime.currTime+toutmsecs.msecs; 2858 } 2859 } 2860 } 2861 2862 alias CustomEventHandler = bool delegate (Object o) nothrow; 2863 static struct EventHandlerEntry { 2864 CustomEventHandler dg; 2865 uint id; 2866 } 2867 2868 uint lastUsedHandlerId; 2869 EventHandlerEntry[] eventHandlers; 2870 QueuedEvent[] eventQueue = null; 2871 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 2872 bool inCustomEventProcessor = false; // required to properly remove events 2873 2874 // process queued events and call custom event handlers 2875 // this will not process events posted from called handlers (such events are postponed for the next iteration) 2876 void processCustomEvents () { 2877 bool hasSomethingToDo = false; 2878 uint ecount; 2879 bool ocep; 2880 synchronized(this) { 2881 ocep = inCustomEventProcessor; 2882 inCustomEventProcessor = true; 2883 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 2884 auto ctt = MonoTime.currTime; 2885 bool hasEmpty = false; 2886 // mark events to process (this is required for `eventQueued()`) 2887 foreach (ref qe; eventQueue[0..ecount]) { 2888 if (qe.evt is null) { hasEmpty = true; continue; } 2889 if (qe.timed) { 2890 qe.doProcess = (qe.hittime <= ctt); 2891 } else { 2892 qe.doProcess = true; 2893 } 2894 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 2895 } 2896 if (!hasSomethingToDo) { 2897 // remove empty events 2898 if (hasEmpty) { 2899 uint eidx = 0, ec = eventQueueUsed; 2900 auto eptr = eventQueue.ptr; 2901 while (eidx < ec) { 2902 if (eptr.evt is null) { 2903 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 2904 ec = --eventQueueUsed; 2905 eventQueue.ptr[ec].evt = null; // make GC life easier 2906 } else { 2907 ++eidx; 2908 ++eptr; 2909 } 2910 } 2911 } 2912 inCustomEventProcessor = ocep; 2913 return; 2914 } 2915 } 2916 // process marked events 2917 uint efree = 0; // non-processed events will be put at this index 2918 EventHandlerEntry[] eh; 2919 Object evt; 2920 foreach (immutable eidx; 0..ecount) { 2921 synchronized(this) { 2922 if (!eventQueue[eidx].doProcess) { 2923 // skip this event 2924 assert(efree <= eidx); 2925 if (efree != eidx) { 2926 // copy this event to queue start 2927 eventQueue[efree] = eventQueue[eidx]; 2928 eventQueue[eidx].evt = null; // just in case 2929 } 2930 ++efree; 2931 continue; 2932 } 2933 evt = eventQueue[eidx].evt; 2934 eventQueue[eidx].evt = null; // in case event handler will hit GC 2935 if (evt is null) continue; // just in case 2936 // try all handlers; this can be slow, but meh... 2937 eh = eventHandlers; 2938 } 2939 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 2940 evt = null; 2941 eh = null; 2942 } 2943 synchronized(this) { 2944 // move all unprocessed events to queue top; efree holds first "free index" 2945 foreach (immutable eidx; ecount..eventQueueUsed) { 2946 assert(efree <= eidx); 2947 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 2948 ++efree; 2949 } 2950 eventQueueUsed = efree; 2951 // wake up event processor on next event loop iteration if we have more queued events 2952 // also, remove empty events 2953 bool awaken = false; 2954 uint eidx = 0, ec = eventQueueUsed; 2955 auto eptr = eventQueue.ptr; 2956 while (eidx < ec) { 2957 if (eptr.evt is null) { 2958 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 2959 ec = --eventQueueUsed; 2960 eventQueue.ptr[ec].evt = null; // make GC life easier 2961 } else { 2962 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 2963 ++eidx; 2964 ++eptr; 2965 } 2966 } 2967 inCustomEventProcessor = ocep; 2968 } 2969 } 2970 2971 // for all windows in nativeMapping 2972 static void processAllCustomEvents () { 2973 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 2974 if (sw is null || sw.closed) continue; 2975 sw.processCustomEvents(); 2976 } 2977 2978 runPendingRunInGuiThreadDelegates(); 2979 } 2980 2981 // 0: infinite (i.e. no scheduled events in queue) 2982 uint eventQueueTimeoutMSecs () { 2983 synchronized(this) { 2984 if (eventQueueUsed == 0) return 0; 2985 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 2986 uint res = int.max; 2987 auto ctt = MonoTime.currTime; 2988 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 2989 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 2990 if (qe.doProcess) continue; // just in case 2991 if (!qe.timed) return 1; // minimal 2992 if (qe.hittime <= ctt) return 1; // minimal 2993 auto tms = (qe.hittime-ctt).total!"msecs"; 2994 if (tms < 1) tms = 1; // safety net 2995 if (tms >= int.max) tms = int.max-1; // and another safety net 2996 if (res > tms) res = cast(uint)tms; 2997 } 2998 return (res >= int.max ? 0 : res); 2999 } 3000 } 3001 3002 // for all windows in nativeMapping 3003 static uint eventAllQueueTimeoutMSecs () { 3004 uint res = uint.max; 3005 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 3006 if (sw is null || sw.closed) continue; 3007 uint to = sw.eventQueueTimeoutMSecs(); 3008 if (to && to < res) { 3009 res = to; 3010 if (to == 1) break; // can't have less than this 3011 } 3012 } 3013 return (res >= int.max ? 0 : res); 3014 } 3015 } 3016 3017 /* Drag and drop support { */ 3018 version(X11) { 3019 3020 } else version(Windows) { 3021 import core.sys.windows.uuid; 3022 import core.sys.windows.ole2; 3023 import core.sys.windows.oleidl; 3024 import core.sys.windows.objidl; 3025 import core.sys.windows.wtypes; 3026 3027 pragma(lib, "ole32"); 3028 void initDnd() { 3029 auto err = OleInitialize(null); 3030 if(err != S_OK && err != S_FALSE) 3031 throw new Exception("init");//err); 3032 } 3033 } 3034 /* } End drag and drop support */ 3035 3036 3037 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 3038 /// See [GenericCursor] 3039 class MouseCursor { 3040 int osId; 3041 bool isStockCursor; 3042 private this(int osId) { 3043 this.osId = osId; 3044 this.isStockCursor = true; 3045 } 3046 3047 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 3048 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 3049 3050 version(Windows) { 3051 HCURSOR cursor_; 3052 HCURSOR cursorHandle() { 3053 if(cursor_ is null) 3054 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 3055 return cursor_; 3056 } 3057 3058 } else static if(UsingSimpledisplayX11) { 3059 Cursor cursor_ = None; 3060 int xDisplaySequence; 3061 3062 Cursor cursorHandle() { 3063 if(this.osId == None) 3064 return None; 3065 3066 // we need to reload if we on a new X connection 3067 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 3068 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 3069 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 3070 } 3071 return cursor_; 3072 } 3073 } 3074 } 3075 3076 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 3077 // https://tronche.com/gui/x/xlib/appendix/b/ 3078 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 3079 /// 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 3080 enum GenericCursorType { 3081 Default, /// The default arrow pointer. 3082 Wait, /// A cursor indicating something is loading and the user must wait. 3083 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 3084 Help, /// A cursor indicating the user can get help about the pointer location. 3085 Cross, /// A crosshair. 3086 Text, /// An i-beam shape, typically used to indicate text selection is possible. 3087 Move, /// Pointer indicating movement is possible. May also be used as SizeAll 3088 UpArrow, /// An arrow pointing straight up. 3089 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 3090 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 3091 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator) 3092 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator) 3093 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator) 3094 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator) 3095 3096 } 3097 3098 /* 3099 X_plus == css cell == Windows ? 3100 */ 3101 3102 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 3103 static struct GenericCursor { 3104 static: 3105 /// 3106 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 3107 static MouseCursor mc; 3108 3109 auto type = __traits(getMember, GenericCursorType, str); 3110 3111 if(mc is null) { 3112 3113 version(Windows) { 3114 int osId; 3115 final switch(type) { 3116 case GenericCursorType.Default: osId = IDC_ARROW; break; 3117 case GenericCursorType.Wait: osId = IDC_WAIT; break; 3118 case GenericCursorType.Hand: osId = IDC_HAND; break; 3119 case GenericCursorType.Help: osId = IDC_HELP; break; 3120 case GenericCursorType.Cross: osId = IDC_CROSS; break; 3121 case GenericCursorType.Text: osId = IDC_IBEAM; break; 3122 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 3123 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 3124 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 3125 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 3126 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 3127 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 3128 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 3129 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 3130 } 3131 } else static if(UsingSimpledisplayX11) { 3132 int osId; 3133 final switch(type) { 3134 case GenericCursorType.Default: osId = None; break; 3135 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 3136 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 3137 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 3138 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 3139 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 3140 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 3141 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 3142 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 3143 3144 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 3145 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 3146 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 3147 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 3148 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 3149 } 3150 3151 } else featureNotImplemented(); 3152 3153 mc = new MouseCursor(osId); 3154 } 3155 return mc; 3156 } 3157 } 3158 3159 3160 /++ 3161 If you want to get more control over the event loop, you can use this. 3162 3163 Typically though, you can just call [SimpleWindow.eventLoop]. 3164 +/ 3165 struct EventLoop { 3166 @disable this(); 3167 3168 /// Gets a reference to an existing event loop 3169 static EventLoop get() { 3170 return EventLoop(0, null); 3171 } 3172 3173 __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 3174 3175 /// Construct an application-global event loop for yourself 3176 /// See_Also: [SimpleWindow.setEventHandlers] 3177 this(long pulseTimeout, void delegate() handlePulse) { 3178 synchronized(monitor) { 3179 if(impl is null) { 3180 claimGuiThread(); 3181 version(sdpy_thread_checks) assert(thisIsGuiThread); 3182 impl = new EventLoopImpl(pulseTimeout, handlePulse); 3183 } else { 3184 if(pulseTimeout) { 3185 impl.pulseTimeout = pulseTimeout; 3186 impl.handlePulse = handlePulse; 3187 } 3188 } 3189 impl.refcount++; 3190 } 3191 } 3192 3193 ~this() { 3194 if(impl is null) 3195 return; 3196 impl.refcount--; 3197 if(impl.refcount == 0) { 3198 impl.dispose(); 3199 if(thisIsGuiThread) 3200 guiThreadFinalize(); 3201 } 3202 3203 } 3204 3205 this(this) { 3206 if(impl is null) 3207 return; 3208 impl.refcount++; 3209 } 3210 3211 /// Runs the event loop until the whileCondition, if present, returns false 3212 int run(bool delegate() whileCondition = null) { 3213 assert(impl !is null); 3214 impl.notExited = true; 3215 return impl.run(whileCondition); 3216 } 3217 3218 /// Exits the event loop 3219 void exit() { 3220 assert(impl !is null); 3221 impl.notExited = false; 3222 } 3223 3224 version(linux) 3225 ref void delegate(int) signalHandler() { 3226 assert(impl !is null); 3227 return impl.signalHandler; 3228 } 3229 3230 __gshared static EventLoopImpl* impl; 3231 } 3232 3233 version(linux) 3234 void delegate(int, int) globalHupHandler; 3235 3236 version(Posix) 3237 void makeNonBlocking(int fd) { 3238 import fcntl = core.sys.posix.fcntl; 3239 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 3240 if(flags == -1) 3241 throw new Exception("fcntl get"); 3242 flags |= fcntl.O_NONBLOCK; 3243 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 3244 if(s == -1) 3245 throw new Exception("fcntl set"); 3246 } 3247 3248 struct EventLoopImpl { 3249 int refcount; 3250 3251 bool notExited = true; 3252 3253 version(linux) { 3254 static import ep = core.sys.linux.epoll; 3255 static import unix = core.sys.posix.unistd; 3256 static import err = core.stdc.errno; 3257 import core.sys.linux.timerfd; 3258 3259 void delegate(int) signalHandler; 3260 } 3261 3262 version(X11) { 3263 int pulseFd = -1; 3264 version(linux) ep.epoll_event[16] events = void; 3265 } else version(Windows) { 3266 Timer pulser; 3267 HANDLE[] handles; 3268 } 3269 3270 3271 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 3272 /// to call this, as it's not recommended to share window between threads. 3273 void mtLock () { 3274 version(X11) { 3275 XLockDisplay(this.display); 3276 } 3277 } 3278 3279 version(X11) 3280 auto display() { return XDisplayConnection.get; } 3281 3282 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 3283 /// to call this, as it's not recommended to share window between threads. 3284 void mtUnlock () { 3285 version(X11) { 3286 XUnlockDisplay(this.display); 3287 } 3288 } 3289 3290 version(with_eventloop) 3291 void initialize(long pulseTimeout) {} 3292 else 3293 void initialize(long pulseTimeout) { 3294 version(Windows) { 3295 if(pulseTimeout && handlePulse !is null) 3296 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 3297 3298 if (customEventH is null) { 3299 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 3300 if (customEventH !is null) { 3301 handles ~= customEventH; 3302 } else { 3303 // this is something that should not be; better be safe than sorry 3304 throw new Exception("can't create eventfd for custom event processing"); 3305 } 3306 } 3307 3308 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 3309 } 3310 3311 version(linux) { 3312 prepareEventLoop(); 3313 { 3314 auto display = XDisplayConnection.get; 3315 // adding Xlib file 3316 ep.epoll_event ev = void; 3317 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3318 ev.events = ep.EPOLLIN; 3319 ev.data.fd = display.fd; 3320 //import std.conv; 3321 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 3322 throw new Exception("add x fd");// ~ to!string(epollFd)); 3323 displayFd = display.fd; 3324 } 3325 3326 if(pulseTimeout && handlePulse !is null) { 3327 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 3328 if(pulseFd == -1) 3329 throw new Exception("pulse timer create failed"); 3330 3331 itimerspec value; 3332 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 3333 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 3334 3335 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 3336 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 3337 3338 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 3339 throw new Exception("couldn't make pulse timer"); 3340 3341 ep.epoll_event ev = void; 3342 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3343 ev.events = ep.EPOLLIN; 3344 ev.data.fd = pulseFd; 3345 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 3346 } 3347 3348 // eventfd for custom events 3349 if (customEventFDWrite == -1) { 3350 customEventFDWrite = eventfd(0, 0); 3351 customEventFDRead = customEventFDWrite; 3352 if (customEventFDRead >= 0) { 3353 ep.epoll_event ev = void; 3354 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3355 ev.events = ep.EPOLLIN; 3356 ev.data.fd = customEventFDRead; 3357 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 3358 } else { 3359 // this is something that should not be; better be safe than sorry 3360 throw new Exception("can't create eventfd for custom event processing"); 3361 } 3362 } 3363 3364 if (customSignalFD == -1) { 3365 import core.sys.linux.sys.signalfd; 3366 3367 sigset_t sigset; 3368 auto err = sigemptyset(&sigset); 3369 assert(!err); 3370 err = sigaddset(&sigset, SIGINT); 3371 assert(!err); 3372 err = sigaddset(&sigset, SIGHUP); 3373 assert(!err); 3374 err = sigprocmask(SIG_BLOCK, &sigset, null); 3375 assert(!err); 3376 3377 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 3378 assert(customSignalFD != -1); 3379 3380 ep.epoll_event ev = void; 3381 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3382 ev.events = ep.EPOLLIN; 3383 ev.data.fd = customSignalFD; 3384 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 3385 } 3386 } else version(Posix) { 3387 prepareEventLoop(); 3388 if (customEventFDRead == -1) { 3389 int[2] bfr; 3390 import core.sys.posix.unistd; 3391 auto ret = pipe(bfr); 3392 if(ret == -1) throw new Exception("pipe"); 3393 customEventFDRead = bfr[0]; 3394 customEventFDWrite = bfr[1]; 3395 } 3396 3397 } 3398 3399 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 3400 3401 version(linux) { 3402 this.mtLock(); 3403 scope(exit) this.mtUnlock(); 3404 XPending(display); // no, really 3405 } 3406 3407 disposed = false; 3408 } 3409 3410 bool disposed = true; 3411 version(X11) 3412 int displayFd = -1; 3413 3414 version(with_eventloop) 3415 void dispose() {} 3416 else 3417 void dispose() { 3418 disposed = true; 3419 version(X11) { 3420 if(pulseFd != -1) { 3421 import unix = core.sys.posix.unistd; 3422 unix.close(pulseFd); 3423 pulseFd = -1; 3424 } 3425 3426 version(linux) 3427 if(displayFd != -1) { 3428 // 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 3429 ep.epoll_event ev = void; 3430 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3431 ev.events = ep.EPOLLIN; 3432 ev.data.fd = displayFd; 3433 //import std.conv; 3434 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 3435 displayFd = -1; 3436 } 3437 3438 } else version(Windows) { 3439 if(pulser !is null) { 3440 pulser.destroy(); 3441 pulser = null; 3442 } 3443 if (customEventH !is null) { 3444 CloseHandle(customEventH); 3445 customEventH = null; 3446 } 3447 } 3448 } 3449 3450 this(long pulseTimeout, void delegate() handlePulse) { 3451 this.pulseTimeout = pulseTimeout; 3452 this.handlePulse = handlePulse; 3453 initialize(pulseTimeout); 3454 } 3455 3456 private long pulseTimeout; 3457 void delegate() handlePulse; 3458 3459 ~this() { 3460 dispose(); 3461 } 3462 3463 version(Posix) 3464 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 3465 version(Posix) 3466 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 3467 version(linux) 3468 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 3469 version(Windows) 3470 ref auto customEventH() { return SimpleWindow.customEventH; } 3471 3472 version(with_eventloop) { 3473 int loopHelper(bool delegate() whileCondition) { 3474 // FIXME: whileCondition 3475 import arsd.eventloop; 3476 loop(); 3477 return 0; 3478 } 3479 } else 3480 int loopHelper(bool delegate() whileCondition) { 3481 version(X11) { 3482 bool done = false; 3483 3484 XFlush(display); 3485 insideXEventLoop = true; 3486 scope(exit) insideXEventLoop = false; 3487 3488 version(linux) { 3489 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 3490 bool forceXPending = false; 3491 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 3492 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 3493 { 3494 this.mtLock(); 3495 scope(exit) this.mtUnlock(); 3496 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 3497 } 3498 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 3499 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 3500 if(nfds == -1) { 3501 if(err.errno == err.EINTR) { 3502 //if(forceXPending) goto xpending; 3503 continue; // interrupted by signal, just try again 3504 } 3505 throw new Exception("epoll wait failure"); 3506 } 3507 3508 SimpleWindow.processAllCustomEvents(); // anyway 3509 //version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 3510 foreach(idx; 0 .. nfds) { 3511 if(done) break; 3512 auto fd = events[idx].data.fd; 3513 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 3514 auto flags = events[idx].events; 3515 if(flags & ep.EPOLLIN) { 3516 if (fd == customSignalFD) { 3517 version(linux) { 3518 import core.sys.linux.sys.signalfd; 3519 import core.sys.posix.unistd : read; 3520 signalfd_siginfo info; 3521 read(customSignalFD, &info, info.sizeof); 3522 3523 auto sig = info.ssi_signo; 3524 3525 if(EventLoop.get.signalHandler !is null) { 3526 EventLoop.get.signalHandler()(sig); 3527 } else { 3528 EventLoop.get.exit(); 3529 } 3530 } 3531 } else if(fd == display.fd) { 3532 version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); } 3533 this.mtLock(); 3534 scope(exit) this.mtUnlock(); 3535 while(!done && XPending(display)) { 3536 done = doXNextEvent(this.display); 3537 } 3538 forceXPending = false; 3539 } else if(fd == pulseFd) { 3540 long expirationCount; 3541 // 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... 3542 3543 handlePulse(); 3544 3545 // read just to clear the buffer so poll doesn't trigger again 3546 // BTW I read AFTER the pulse because if the pulse handler takes 3547 // a lot of time to execute, we don't want the app to get stuck 3548 // in a loop of timer hits without a chance to do anything else 3549 // 3550 // IOW handlePulse happens at most once per pulse interval. 3551 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 3552 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 3553 } else if (fd == customEventFDRead) { 3554 // we have some custom events; process 'em 3555 import core.sys.posix.unistd : read; 3556 ulong n; 3557 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 3558 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 3559 //SimpleWindow.processAllCustomEvents(); 3560 } else { 3561 // some other timer 3562 version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); } 3563 3564 if(Timer* t = fd in Timer.mapping) 3565 (*t).trigger(); 3566 3567 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 3568 (*pfr).ready(flags); 3569 3570 // or i might add support for other FDs too 3571 // but for now it is just timer 3572 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 3573 } 3574 } 3575 if(flags & ep.EPOLLHUP) { 3576 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 3577 (*pfr).hup(flags); 3578 if(globalHupHandler) 3579 globalHupHandler(fd, flags); 3580 } 3581 /+ 3582 } else { 3583 // not interested in OUT, we are just reading here. 3584 // 3585 // error or hup might also be reported 3586 // but it shouldn't here since we are only 3587 // using a few types of FD and Xlib will report 3588 // if it dies. 3589 // so instead of thoughtfully handling it, I'll 3590 // just throw. for now at least 3591 3592 throw new Exception("epoll did something else"); 3593 } 3594 +/ 3595 } 3596 // if we won't call `XPending()` here, libX may delay some internal event delivery. 3597 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 3598 xpending: 3599 if (!done && forceXPending) { 3600 this.mtLock(); 3601 scope(exit) this.mtUnlock(); 3602 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 3603 while(!done && XPending(display)) { 3604 done = doXNextEvent(this.display); 3605 } 3606 } 3607 } 3608 } else { 3609 // Generic fallback: yes to simple pulse support, 3610 // but NO timer support! 3611 3612 // FIXME: we could probably support the POSIX timer_create 3613 // signal-based option, but I'm in no rush to write it since 3614 // I prefer the fd-based functions. 3615 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 3616 3617 import core.sys.posix.poll; 3618 3619 pollfd[] pfds; 3620 pollfd[32] pfdsBuffer; 3621 auto len = PosixFdReader.mapping.length + 2; 3622 // FIXME: i should just reuse the buffer 3623 if(len < pfdsBuffer.length) 3624 pfds = pfdsBuffer[0 .. len]; 3625 else 3626 pfds = new pollfd[](len); 3627 3628 pfds[0].fd = display.fd; 3629 pfds[0].events = POLLIN; 3630 pfds[0].revents = 0; 3631 3632 int slot = 1; 3633 3634 if(customEventFDRead != -1) { 3635 pfds[slot].fd = customEventFDRead; 3636 pfds[slot].events = POLLIN; 3637 pfds[slot].revents = 0; 3638 3639 slot++; 3640 } 3641 3642 foreach(fd, obj; PosixFdReader.mapping) { 3643 if(!obj.enabled) continue; 3644 pfds[slot].fd = fd; 3645 pfds[slot].events = POLLIN; 3646 pfds[slot].revents = 0; 3647 3648 slot++; 3649 } 3650 3651 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 3652 if(ret == -1) throw new Exception("poll"); 3653 3654 if(ret == 0) { 3655 // FIXME it may not necessarily time out if events keep coming 3656 if(handlePulse !is null) 3657 handlePulse(); 3658 } else { 3659 foreach(s; 0 .. slot) { 3660 if(pfds[s].revents == 0) continue; 3661 3662 if(pfds[s].fd == display.fd) { 3663 while(!done && XPending(display)) { 3664 this.mtLock(); 3665 scope(exit) this.mtUnlock(); 3666 done = doXNextEvent(this.display); 3667 } 3668 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 3669 3670 import core.sys.posix.unistd : read; 3671 ulong n; 3672 read(customEventFDRead, &n, n.sizeof); 3673 SimpleWindow.processAllCustomEvents(); 3674 } else { 3675 auto obj = PosixFdReader.mapping[pfds[s].fd]; 3676 if(pfds[s].revents & POLLNVAL) { 3677 obj.dispose(); 3678 } else { 3679 obj.ready(pfds[s].revents); 3680 } 3681 } 3682 3683 ret--; 3684 if(ret == 0) break; 3685 } 3686 } 3687 } 3688 } 3689 } 3690 3691 version(Windows) { 3692 int ret = -1; 3693 MSG message; 3694 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 3695 eventLoopRound++; 3696 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 3697 auto waitResult = MsgWaitForMultipleObjectsEx( 3698 cast(int) handles.length, handles.ptr, 3699 (wto == 0 ? INFINITE : wto), /* timeout */ 3700 0x04FF, /* QS_ALLINPUT */ 3701 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 3702 3703 SimpleWindow.processAllCustomEvents(); // anyway 3704 enum WAIT_OBJECT_0 = 0; 3705 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 3706 auto h = handles[waitResult - WAIT_OBJECT_0]; 3707 if(auto e = h in WindowsHandleReader.mapping) { 3708 (*e).ready(); 3709 } 3710 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 3711 // message ready 3712 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 3713 ret = GetMessage(&message, null, 0, 0); 3714 if(ret == -1) 3715 throw new Exception("GetMessage failed"); 3716 TranslateMessage(&message); 3717 DispatchMessage(&message); 3718 3719 if(ret == 0) // WM_QUIT 3720 break; 3721 } 3722 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 3723 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 3724 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 3725 // timeout, should never happen since we aren't using it 3726 } else if(waitResult == 0xFFFFFFFF) { 3727 // failed 3728 throw new Exception("MsgWaitForMultipleObjectsEx failed"); 3729 } else { 3730 // idk.... 3731 } 3732 } 3733 3734 // return message.wParam; 3735 return 0; 3736 } else { 3737 return 0; 3738 } 3739 } 3740 3741 int run(bool delegate() whileCondition = null) { 3742 if(disposed) 3743 initialize(this.pulseTimeout); 3744 3745 version(X11) { 3746 try { 3747 return loopHelper(whileCondition); 3748 } catch(XDisconnectException e) { 3749 if(e.userRequested) { 3750 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 3751 item.discardConnectionState(); 3752 XCloseDisplay(XDisplayConnection.display); 3753 } 3754 3755 XDisplayConnection.display = null; 3756 3757 this.dispose(); 3758 3759 throw e; 3760 } 3761 } else { 3762 return loopHelper(whileCondition); 3763 } 3764 } 3765 } 3766 3767 3768 /++ 3769 Provides an icon on the system notification area (also known as the system tray). 3770 3771 3772 If a notification area is not available with the NotificationIcon object is created, 3773 it will silently succeed and simply attempt to create one when an area becomes available. 3774 3775 3776 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. 3777 If this is wrong, pass -version=WindowsXP to dmd when compiling and it will 3778 use the older version. 3779 +/ 3780 version(OSXCocoa) {} else // NotYetImplementedException 3781 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 3782 3783 version(X11) { 3784 void recreateAfterDisconnect() { 3785 stateDiscarded = false; 3786 clippixmap = None; 3787 throw new Exception("NOT IMPLEMENTED"); 3788 } 3789 3790 bool stateDiscarded; 3791 void discardConnectionState() { 3792 stateDiscarded = true; 3793 } 3794 } 3795 3796 3797 version(X11) { 3798 Image img; 3799 3800 NativeEventHandler getNativeEventHandler() { 3801 return delegate int(XEvent e) { 3802 switch(e.type) { 3803 case EventType.Expose: 3804 //case EventType.VisibilityNotify: 3805 redraw(); 3806 break; 3807 case EventType.ClientMessage: 3808 version(sddddd) { 3809 import std.stdio; 3810 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 3811 writeln("\t", e.xclient.format); 3812 writeln("\t", e.xclient.data.l); 3813 } 3814 break; 3815 case EventType.ButtonPress: 3816 auto event = e.xbutton; 3817 if (onClick !is null || onClickEx !is null) { 3818 MouseButton mb = cast(MouseButton)0; 3819 switch (event.button) { 3820 case 1: mb = MouseButton.left; break; // left 3821 case 2: mb = MouseButton.middle; break; // middle 3822 case 3: mb = MouseButton.right; break; // right 3823 case 4: mb = MouseButton.wheelUp; break; // scroll up 3824 case 5: mb = MouseButton.wheelDown; break; // scroll down 3825 case 6: break; // idk 3826 case 7: break; // idk 3827 case 8: mb = MouseButton.backButton; break; 3828 case 9: mb = MouseButton.forwardButton; break; 3829 default: 3830 } 3831 if (mb) { 3832 try { onClick()(mb); } catch (Exception) {} 3833 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 3834 } 3835 } 3836 break; 3837 case EventType.EnterNotify: 3838 if (onEnter !is null) { 3839 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 3840 } 3841 break; 3842 case EventType.LeaveNotify: 3843 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 3844 break; 3845 case EventType.DestroyNotify: 3846 active = false; 3847 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 3848 break; 3849 case EventType.ConfigureNotify: 3850 auto event = e.xconfigure; 3851 this.width = event.width; 3852 this.height = event.height; 3853 //import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y); 3854 redraw(); 3855 break; 3856 default: return 1; 3857 } 3858 return 1; 3859 }; 3860 } 3861 3862 /* private */ void hideBalloon() { 3863 balloon.close(); 3864 version(with_timer) 3865 timer.destroy(); 3866 balloon = null; 3867 version(with_timer) 3868 timer = null; 3869 } 3870 3871 void redraw() { 3872 if (!active) return; 3873 3874 auto display = XDisplayConnection.get; 3875 auto gc = DefaultGC(display, DefaultScreen(display)); 3876 XClearWindow(display, nativeHandle); 3877 3878 XSetClipMask(display, gc, clippixmap); 3879 3880 XSetForeground(display, gc, 3881 cast(uint) 0 << 16 | 3882 cast(uint) 0 << 8 | 3883 cast(uint) 0); 3884 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 3885 3886 if (img is null) { 3887 XSetForeground(display, gc, 3888 cast(uint) 0 << 16 | 3889 cast(uint) 127 << 8 | 3890 cast(uint) 0); 3891 XFillArc(display, nativeHandle, 3892 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 3893 } else { 3894 int dx = 0; 3895 int dy = 0; 3896 if(width > img.width) 3897 dx = (width - img.width) / 2; 3898 if(height > img.height) 3899 dy = (height - img.height) / 2; 3900 XSetClipOrigin(display, gc, dx, dy); 3901 3902 if (img.usingXshm) 3903 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false); 3904 else 3905 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height); 3906 } 3907 XSetClipMask(display, gc, None); 3908 flushGui(); 3909 } 3910 3911 static Window getTrayOwner() { 3912 auto display = XDisplayConnection.get; 3913 auto i = cast(int) DefaultScreen(display); 3914 if(i < 10 && i >= 0) { 3915 static Atom atom; 3916 if(atom == None) 3917 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 3918 return XGetSelectionOwner(display, atom); 3919 } 3920 return None; 3921 } 3922 3923 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 3924 auto to = getTrayOwner(); 3925 auto display = XDisplayConnection.get; 3926 XEvent ev; 3927 ev.xclient.type = EventType.ClientMessage; 3928 ev.xclient.window = to; 3929 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 3930 ev.xclient.format = 32; 3931 ev.xclient.data.l[0] = CurrentTime; 3932 ev.xclient.data.l[1] = message; 3933 ev.xclient.data.l[2] = d1; 3934 ev.xclient.data.l[3] = d2; 3935 ev.xclient.data.l[4] = d3; 3936 3937 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 3938 } 3939 3940 private static NotificationAreaIcon[] activeIcons; 3941 3942 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 3943 private void newManager() { 3944 close(); 3945 createXWin(); 3946 3947 if(this.clippixmap) 3948 XFreePixmap(XDisplayConnection.get, clippixmap); 3949 if(this.originalMemoryImage) 3950 this.icon = this.originalMemoryImage; 3951 else if(this.img) 3952 this.icon = this.img; 3953 } 3954 3955 private void createXWin () { 3956 // create window 3957 auto display = XDisplayConnection.get; 3958 3959 // to check for MANAGER on root window to catch new/changed tray owners 3960 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 3961 // so if a thing does appear, we can handle it 3962 foreach(ai; activeIcons) 3963 if(ai is this) 3964 goto alreadythere; 3965 activeIcons ~= this; 3966 alreadythere: 3967 3968 // and check for an existing tray 3969 auto trayOwner = getTrayOwner(); 3970 if(trayOwner == None) 3971 return; 3972 //throw new Exception("No notification area found"); 3973 3974 Visual* v = cast(Visual*) CopyFromParent; 3975 /+ 3976 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 3977 if(visualProp !is null) { 3978 c_ulong[] info = cast(c_ulong[]) visualProp; 3979 if(info.length == 1) { 3980 auto vid = info[0]; 3981 int returned; 3982 XVisualInfo t; 3983 t.visualid = vid; 3984 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 3985 if(got !is null) { 3986 if(returned == 1) { 3987 v = got.visual; 3988 import std.stdio; 3989 writeln("using special visual ", *got); 3990 } 3991 XFree(got); 3992 } 3993 } 3994 } 3995 +/ 3996 3997 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null); 3998 assert(nativeWindow); 3999 4000 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 4001 4002 nativeHandle = nativeWindow; 4003 4004 ///+ 4005 arch_ulong[2] info; 4006 info[0] = 0; 4007 info[1] = 1; 4008 4009 string title = this.name is null ? "simpledisplay.d program" : this.name; 4010 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 4011 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 4012 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 4013 4014 XChangeProperty( 4015 display, 4016 nativeWindow, 4017 GetAtom!("_XEMBED_INFO", true)(display), 4018 GetAtom!("_XEMBED_INFO", true)(display), 4019 32 /* bits */, 4020 0 /*PropModeReplace*/, 4021 info.ptr, 4022 2); 4023 4024 import core.sys.posix.unistd; 4025 arch_ulong pid = getpid(); 4026 4027 XChangeProperty( 4028 display, 4029 nativeWindow, 4030 GetAtom!("_NET_WM_PID", true)(display), 4031 XA_CARDINAL, 4032 32 /* bits */, 4033 0 /*PropModeReplace*/, 4034 &pid, 4035 1); 4036 4037 updateNetWmIcon(); 4038 4039 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 4040 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 4041 XClassHint klass; 4042 XWMHints wh; 4043 XSizeHints size; 4044 klass.res_name = sdpyWindowClassStr; 4045 klass.res_class = sdpyWindowClassStr; 4046 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 4047 } 4048 4049 // believe it or not, THIS is what xfce needed for the 9999 issue 4050 XSizeHints sh; 4051 c_long spr; 4052 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 4053 sh.flags |= PMaxSize | PMinSize; 4054 // FIXME maybe nicer resizing 4055 sh.min_width = 16; 4056 sh.min_height = 16; 4057 sh.max_width = 16; 4058 sh.max_height = 16; 4059 XSetWMNormalHints(display, nativeWindow, &sh); 4060 4061 4062 //+/ 4063 4064 4065 XSelectInput(display, nativeWindow, 4066 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 4067 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 4068 4069 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 4070 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 4071 active = true; 4072 } 4073 4074 void updateNetWmIcon() { 4075 if(img is null) return; 4076 auto display = XDisplayConnection.get; 4077 // FIXME: ensure this is correct 4078 arch_ulong[] buffer; 4079 auto imgMi = img.toTrueColorImage; 4080 buffer ~= imgMi.width; 4081 buffer ~= imgMi.height; 4082 foreach(c; imgMi.imageData.colors) { 4083 arch_ulong b; 4084 b |= c.a << 24; 4085 b |= c.r << 16; 4086 b |= c.g << 8; 4087 b |= c.b; 4088 buffer ~= b; 4089 } 4090 4091 XChangeProperty( 4092 display, 4093 nativeHandle, 4094 GetAtom!"_NET_WM_ICON"(display), 4095 GetAtom!"CARDINAL"(display), 4096 32 /* bits */, 4097 0 /*PropModeReplace*/, 4098 buffer.ptr, 4099 cast(int) buffer.length); 4100 } 4101 4102 4103 4104 private SimpleWindow balloon; 4105 version(with_timer) 4106 private Timer timer; 4107 4108 private Window nativeHandle; 4109 private Pixmap clippixmap = None; 4110 private int width = 16; 4111 private int height = 16; 4112 private bool active = false; 4113 4114 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 4115 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 4116 void delegate () onLeave; /// X11 only. 4117 4118 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 4119 4120 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 4121 void getWindowRect (out int x, out int y, out int width, out int height) { 4122 if (!active) { width = 1; height = 1; return; } // 1: just in case 4123 Window dummyw; 4124 auto dpy = XDisplayConnection.get; 4125 //XWindowAttributes xwa; 4126 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 4127 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 4128 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 4129 width = this.width; 4130 height = this.height; 4131 } 4132 } 4133 4134 /+ 4135 What I actually want from this: 4136 4137 * set / change: icon, tooltip 4138 * handle: mouse click, right click 4139 * show: notification bubble. 4140 +/ 4141 4142 version(Windows) { 4143 WindowsIcon win32Icon; 4144 HWND hwnd; 4145 4146 NOTIFYICONDATAW data; 4147 4148 NativeEventHandler getNativeEventHandler() { 4149 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 4150 if(msg == WM_USER) { 4151 auto event = LOWORD(lParam); 4152 auto iconId = HIWORD(lParam); 4153 //auto x = GET_X_LPARAM(wParam); 4154 //auto y = GET_Y_LPARAM(wParam); 4155 switch(event) { 4156 case WM_LBUTTONDOWN: 4157 onClick()(MouseButton.left); 4158 break; 4159 case WM_RBUTTONDOWN: 4160 onClick()(MouseButton.right); 4161 break; 4162 case WM_MBUTTONDOWN: 4163 onClick()(MouseButton.middle); 4164 break; 4165 case WM_MOUSEMOVE: 4166 // sent, we could use it. 4167 break; 4168 case WM_MOUSEWHEEL: 4169 // NOT SENT 4170 break; 4171 //case NIN_KEYSELECT: 4172 //case NIN_SELECT: 4173 //break; 4174 default: {} 4175 } 4176 } 4177 return 0; 4178 }; 4179 } 4180 4181 enum NIF_SHOWTIP = 0x00000080; 4182 4183 private static struct NOTIFYICONDATAW { 4184 DWORD cbSize; 4185 HWND hWnd; 4186 UINT uID; 4187 UINT uFlags; 4188 UINT uCallbackMessage; 4189 HICON hIcon; 4190 WCHAR[128] szTip; 4191 DWORD dwState; 4192 DWORD dwStateMask; 4193 WCHAR[256] szInfo; 4194 union { 4195 UINT uTimeout; 4196 UINT uVersion; 4197 } 4198 WCHAR[64] szInfoTitle; 4199 DWORD dwInfoFlags; 4200 GUID guidItem; 4201 HICON hBalloonIcon; 4202 } 4203 4204 } 4205 4206 /++ 4207 Note that on Windows, only left, right, and middle buttons are sent. 4208 Mouse wheel buttons are NOT set, so don't rely on those events if your 4209 program is meant to be used on Windows too. 4210 +/ 4211 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 4212 // The canonical constructor for Windows needs the MemoryImage, so it is here, 4213 // but on X, we need an Image, so its canonical ctor is there. They should 4214 // forward to each other though. 4215 version(X11) { 4216 this.name = name; 4217 this.onClick = onClick; 4218 createXWin(); 4219 this.icon = icon; 4220 } else version(Windows) { 4221 this.onClick = onClick; 4222 this.win32Icon = new WindowsIcon(icon); 4223 4224 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 4225 4226 static bool registered = false; 4227 if(!registered) { 4228 WNDCLASSEX wc; 4229 wc.cbSize = wc.sizeof; 4230 wc.hInstance = hInstance; 4231 wc.lpfnWndProc = &WndProc; 4232 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 4233 if(!RegisterClassExW(&wc)) 4234 throw new WindowsApiException("RegisterClass"); 4235 registered = true; 4236 } 4237 4238 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 4239 if(hwnd is null) 4240 throw new Exception("CreateWindow"); 4241 4242 data.cbSize = data.sizeof; 4243 data.hWnd = hwnd; 4244 data.uID = cast(uint) cast(void*) this; 4245 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 4246 // NIF_INFO means show balloon 4247 data.uCallbackMessage = WM_USER; 4248 data.hIcon = this.win32Icon.hIcon; 4249 data.szTip = ""; // FIXME 4250 data.dwState = 0; // NIS_HIDDEN; // windows vista 4251 data.dwStateMask = NIS_HIDDEN; // windows vista 4252 4253 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 4254 4255 4256 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 4257 4258 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 4259 } else version(OSXCocoa) { 4260 throw new NotYetImplementedException(); 4261 } else static assert(0); 4262 } 4263 4264 /// ditto 4265 this(string name, Image icon, void delegate(MouseButton button) onClick) { 4266 version(X11) { 4267 this.onClick = onClick; 4268 this.name = name; 4269 createXWin(); 4270 this.icon = icon; 4271 } else version(Windows) { 4272 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 4273 } else version(OSXCocoa) { 4274 throw new NotYetImplementedException(); 4275 } else static assert(0); 4276 } 4277 4278 version(X11) { 4279 /++ 4280 X-specific extension (for now at least) 4281 +/ 4282 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 4283 this.onClickEx = onClickEx; 4284 createXWin(); 4285 if (icon !is null) this.icon = icon; 4286 } 4287 4288 /// ditto 4289 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 4290 this.onClickEx = onClickEx; 4291 createXWin(); 4292 this.icon = icon; 4293 } 4294 } 4295 4296 private void delegate (MouseButton button) onClick_; 4297 4298 /// 4299 @property final void delegate(MouseButton) onClick() { 4300 if(onClick_ is null) 4301 onClick_ = delegate void(MouseButton) {}; 4302 return onClick_; 4303 } 4304 4305 /// ditto 4306 @property final void onClick(void delegate(MouseButton) handler) { 4307 // I made this a property setter so we can wrap smaller arg 4308 // delegates and just forward all to onClickEx or something. 4309 onClick_ = handler; 4310 } 4311 4312 4313 string name_; 4314 @property void name(string n) { 4315 name_ = n; 4316 } 4317 4318 @property string name() { 4319 return name_; 4320 } 4321 4322 private MemoryImage originalMemoryImage; 4323 4324 /// 4325 @property void icon(MemoryImage i) { 4326 version(X11) { 4327 this.originalMemoryImage = i; 4328 if (!active) return; 4329 if (i !is null) { 4330 this.img = Image.fromMemoryImage(i); 4331 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 4332 //import std.stdio; writeln("using pixmap ", clippixmap); 4333 updateNetWmIcon(); 4334 redraw(); 4335 } else { 4336 if (this.img !is null) { 4337 this.img = null; 4338 redraw(); 4339 } 4340 } 4341 } else version(Windows) { 4342 this.win32Icon = new WindowsIcon(i); 4343 4344 data.uFlags = NIF_ICON; 4345 data.hIcon = this.win32Icon.hIcon; 4346 4347 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4348 } else version(OSXCocoa) { 4349 throw new NotYetImplementedException(); 4350 } else static assert(0); 4351 } 4352 4353 /// ditto 4354 @property void icon (Image i) { 4355 version(X11) { 4356 if (!active) return; 4357 if (i !is img) { 4358 originalMemoryImage = null; 4359 img = i; 4360 redraw(); 4361 } 4362 } else version(Windows) { 4363 this.icon(i is null ? null : i.toTrueColorImage()); 4364 } else version(OSXCocoa) { 4365 throw new NotYetImplementedException(); 4366 } else static assert(0); 4367 } 4368 4369 /++ 4370 Shows a balloon notification. You can only show one balloon at a time, if you call 4371 it twice while one is already up, the first balloon will be replaced. 4372 4373 4374 The user is free to block notifications and they will automatically disappear after 4375 a timeout period. 4376 4377 Params: 4378 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 4379 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 4380 icon = the icon to display with the notification. If null, it uses your existing icon. 4381 onclick = delegate called if the user clicks the balloon. (not yet implemented) 4382 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 4383 +/ 4384 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 4385 bool useCustom = true; 4386 version(libnotify) { 4387 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 4388 try { 4389 if(!active) return; 4390 4391 if(libnotify is null) { 4392 libnotify = new C_DynamicLibrary("libnotify.so"); 4393 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 4394 } 4395 4396 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 4397 4398 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 4399 4400 if(onclick) { 4401 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 4402 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); 4403 libnotify_action_delegates_count++; 4404 } 4405 4406 // FIXME icon 4407 4408 // set hint image-data 4409 // set default action for onclick 4410 4411 void* error; 4412 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 4413 4414 useCustom = false; 4415 } catch(Exception e) { 4416 4417 } 4418 } 4419 4420 version(X11) { 4421 if(useCustom) { 4422 if(!active) return; 4423 if(balloon) { 4424 hideBalloon(); 4425 } 4426 // I know there are two specs for this, but one is never 4427 // implemented by any window manager I have ever seen, and 4428 // the other is a bloated mess and too complicated for simpledisplay... 4429 // so doing my own little window instead. 4430 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 4431 4432 int x, y, width, height; 4433 getWindowRect(x, y, width, height); 4434 4435 int bx = x - balloon.width; 4436 int by = y - balloon.height; 4437 if(bx < 0) 4438 bx = x + width + balloon.width; 4439 if(by < 0) 4440 by = y + height; 4441 4442 // just in case, make sure it is actually on scren 4443 if(bx < 0) 4444 bx = 0; 4445 if(by < 0) 4446 by = 0; 4447 4448 balloon.move(bx, by); 4449 auto painter = balloon.draw(); 4450 painter.fillColor = Color(220, 220, 220); 4451 painter.outlineColor = Color.black; 4452 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 4453 auto iconWidth = icon is null ? 0 : icon.width; 4454 if(icon) 4455 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 4456 iconWidth += 6; // margin around the icon 4457 4458 // draw a close button 4459 painter.outlineColor = Color(44, 44, 44); 4460 painter.fillColor = Color(255, 255, 255); 4461 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 4462 painter.pen = Pen(Color.black, 3); 4463 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 4464 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 4465 painter.pen = Pen(Color.black, 1); 4466 painter.fillColor = Color(220, 220, 220); 4467 4468 // Draw the title and message 4469 painter.drawText(Point(4 + iconWidth, 4), title); 4470 painter.drawLine( 4471 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 4472 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 4473 ); 4474 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 4475 4476 balloon.setEventHandlers( 4477 (MouseEvent ev) { 4478 if(ev.type == MouseEventType.buttonPressed) { 4479 if(ev.x > balloon.width - 16 && ev.y < 16) 4480 hideBalloon(); 4481 else if(onclick) 4482 onclick(); 4483 } 4484 } 4485 ); 4486 balloon.show(); 4487 4488 version(with_timer) 4489 timer = new Timer(timeout, &hideBalloon); 4490 else {} // FIXME 4491 } 4492 } else version(Windows) { 4493 enum NIF_INFO = 0x00000010; 4494 4495 data.uFlags = NIF_INFO; 4496 4497 // FIXME: go back to the last valid unicode code point 4498 if(title.length > 40) 4499 title = title[0 .. 40]; 4500 if(message.length > 220) 4501 message = message[0 .. 220]; 4502 4503 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 4504 enum NIIF_LARGE_ICON = 0x00000020; 4505 enum NIIF_NOSOUND = 0x00000010; 4506 enum NIIF_USER = 0x00000004; 4507 enum NIIF_ERROR = 0x00000003; 4508 enum NIIF_WARNING = 0x00000002; 4509 enum NIIF_INFO = 0x00000001; 4510 enum NIIF_NONE = 0; 4511 4512 WCharzBuffer t = WCharzBuffer(title); 4513 WCharzBuffer m = WCharzBuffer(message); 4514 4515 t.copyInto(data.szInfoTitle); 4516 m.copyInto(data.szInfo); 4517 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 4518 4519 if(icon !is null) { 4520 auto i = new WindowsIcon(icon); 4521 data.hBalloonIcon = i.hIcon; 4522 data.dwInfoFlags |= NIIF_USER; 4523 } 4524 4525 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4526 } else version(OSXCocoa) { 4527 throw new NotYetImplementedException(); 4528 } else static assert(0); 4529 } 4530 4531 /// 4532 //version(Windows) 4533 void show() { 4534 version(X11) { 4535 if(!hidden) 4536 return; 4537 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 4538 hidden = false; 4539 } else version(Windows) { 4540 data.uFlags = NIF_STATE; 4541 data.dwState = 0; // NIS_HIDDEN; // windows vista 4542 data.dwStateMask = NIS_HIDDEN; // windows vista 4543 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4544 } else version(OSXCocoa) { 4545 throw new NotYetImplementedException(); 4546 } else static assert(0); 4547 } 4548 4549 version(X11) 4550 bool hidden = false; 4551 4552 /// 4553 //version(Windows) 4554 void hide() { 4555 version(X11) { 4556 if(hidden) 4557 return; 4558 hidden = true; 4559 XUnmapWindow(XDisplayConnection.get, nativeHandle); 4560 } else version(Windows) { 4561 data.uFlags = NIF_STATE; 4562 data.dwState = NIS_HIDDEN; // windows vista 4563 data.dwStateMask = NIS_HIDDEN; // windows vista 4564 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4565 } else version(OSXCocoa) { 4566 throw new NotYetImplementedException(); 4567 } else static assert(0); 4568 } 4569 4570 /// 4571 void close () { 4572 version(X11) { 4573 if (active) { 4574 active = false; // event handler will set this too, but meh 4575 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 4576 XDestroyWindow(XDisplayConnection.get, nativeHandle); 4577 flushGui(); 4578 } 4579 } else version(Windows) { 4580 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 4581 } else version(OSXCocoa) { 4582 throw new NotYetImplementedException(); 4583 } else static assert(0); 4584 } 4585 4586 ~this() { 4587 version(X11) 4588 if(clippixmap != None) 4589 XFreePixmap(XDisplayConnection.get, clippixmap); 4590 close(); 4591 } 4592 } 4593 4594 version(X11) 4595 /// call XFreePixmap on the return value 4596 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 4597 char[] data = new char[](i.width * i.height / 8 + 2); 4598 data[] = 0; 4599 4600 int bitOffset = 0; 4601 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 4602 ubyte v = c.a > 128 ? 1 : 0; 4603 data[bitOffset / 8] |= v << (bitOffset%8); 4604 bitOffset++; 4605 } 4606 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 4607 return handle; 4608 } 4609 4610 4611 // basic functions to make timers 4612 /** 4613 A timer that will trigger your function on a given interval. 4614 4615 4616 You create a timer with an interval and a callback. It will continue 4617 to fire on the interval until it is destroyed. 4618 4619 There are currently no one-off timers (instead, just create one and 4620 destroy it when it is triggered) nor are there pause/resume functions - 4621 the timer must again be destroyed and recreated if you want to pause it. 4622 4623 auto timer = new Timer(50, { it happened!; }); 4624 timer.destroy(); 4625 4626 Timers can only be expected to fire when the event loop is running and only 4627 once per iteration through the event loop. 4628 4629 History: 4630 Prior to December 9, 2020, a timer pulse set too high with a handler too 4631 slow could lock up the event loop. It now guarantees other things will 4632 get a chance to run between timer calls, even if that means not keeping up 4633 with the requested interval. 4634 */ 4635 version(with_timer) { 4636 class Timer { 4637 // FIXME: needs pause and unpause 4638 // FIXME: I might add overloads for ones that take a count of 4639 // how many elapsed since last time (on Windows, it will divide 4640 // the ticks thing given, on Linux it is just available) and 4641 // maybe one that takes an instance of the Timer itself too 4642 /// Create a timer with a callback when it triggers. 4643 this(int intervalInMilliseconds, void delegate() onPulse) { 4644 assert(onPulse !is null); 4645 4646 this.intervalInMilliseconds = intervalInMilliseconds; 4647 this.onPulse = onPulse; 4648 4649 version(Windows) { 4650 /* 4651 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 4652 if(handle == 0) 4653 throw new Exception("SetTimer fail"); 4654 */ 4655 4656 // thanks to Archival 998 for the WaitableTimer blocks 4657 handle = CreateWaitableTimer(null, false, null); 4658 long initialTime = -intervalInMilliseconds; 4659 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 4660 throw new Exception("SetWaitableTimer Failed"); 4661 4662 mapping[handle] = this; 4663 4664 } else version(linux) { 4665 static import ep = core.sys.linux.epoll; 4666 4667 import core.sys.linux.timerfd; 4668 4669 fd = timerfd_create(CLOCK_MONOTONIC, 0); 4670 if(fd == -1) 4671 throw new Exception("timer create failed"); 4672 4673 mapping[fd] = this; 4674 4675 itimerspec value; 4676 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 4677 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 4678 4679 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 4680 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 4681 4682 if(timerfd_settime(fd, 0, &value, null) == -1) 4683 throw new Exception("couldn't make pulse timer"); 4684 4685 version(with_eventloop) { 4686 import arsd.eventloop; 4687 addFileEventListeners(fd, &trigger, null, null); 4688 } else { 4689 prepareEventLoop(); 4690 4691 ep.epoll_event ev = void; 4692 ev.events = ep.EPOLLIN; 4693 ev.data.fd = fd; 4694 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 4695 } 4696 } else featureNotImplemented(); 4697 } 4698 4699 private int intervalInMilliseconds; 4700 4701 /// Stop and destroy the timer object. 4702 void destroy() { 4703 version(Windows) { 4704 if(handle) { 4705 // KillTimer(null, handle); 4706 CancelWaitableTimer(cast(void*)handle); 4707 mapping.remove(handle); 4708 CloseHandle(handle); 4709 handle = null; 4710 } 4711 } else version(linux) { 4712 if(fd != -1) { 4713 import unix = core.sys.posix.unistd; 4714 static import ep = core.sys.linux.epoll; 4715 4716 version(with_eventloop) { 4717 import arsd.eventloop; 4718 removeFileEventListeners(fd); 4719 } else { 4720 ep.epoll_event ev = void; 4721 ev.events = ep.EPOLLIN; 4722 ev.data.fd = fd; 4723 4724 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 4725 } 4726 unix.close(fd); 4727 mapping.remove(fd); 4728 fd = -1; 4729 } 4730 } else featureNotImplemented(); 4731 } 4732 4733 ~this() { 4734 destroy(); 4735 } 4736 4737 4738 void changeTime(int intervalInMilliseconds) 4739 { 4740 this.intervalInMilliseconds = intervalInMilliseconds; 4741 version(Windows) 4742 { 4743 if(handle) 4744 { 4745 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 4746 long initialTime = -intervalInMilliseconds; 4747 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 4748 throw new Exception("couldn't change pulse timer"); 4749 } 4750 } 4751 } 4752 4753 4754 private: 4755 4756 void delegate() onPulse; 4757 4758 int lastEventLoopRoundTriggered; 4759 4760 void trigger() { 4761 version(linux) { 4762 import unix = core.sys.posix.unistd; 4763 long val; 4764 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 4765 } else version(Windows) { 4766 if(this.lastEventLoopRoundTriggered == eventLoopRound) 4767 return; // never try to actually run faster than the event loop 4768 lastEventLoopRoundTriggered = eventLoopRound; 4769 } else featureNotImplemented(); 4770 4771 onPulse(); 4772 } 4773 4774 version(Windows) 4775 void rearm() { 4776 4777 } 4778 4779 version(Windows) 4780 extern(Windows) 4781 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 4782 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 4783 if(Timer* t = timer in mapping) { 4784 try 4785 (*t).trigger(); 4786 catch(Exception e) { sdpy_abort(e); assert(0); } 4787 } 4788 } 4789 4790 version(Windows) { 4791 //UINT_PTR handle; 4792 //static Timer[UINT_PTR] mapping; 4793 HANDLE handle; 4794 __gshared Timer[HANDLE] mapping; 4795 } else version(linux) { 4796 int fd = -1; 4797 __gshared Timer[int] mapping; 4798 } else static assert(0, "timer not supported"); 4799 } 4800 } 4801 4802 version(Windows) 4803 private int eventLoopRound; 4804 4805 version(Windows) 4806 /// 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 4807 class WindowsHandleReader { 4808 /// 4809 this(void delegate() onReady, HANDLE handle) { 4810 this.onReady = onReady; 4811 this.handle = handle; 4812 4813 mapping[handle] = this; 4814 4815 enable(); 4816 } 4817 4818 /// 4819 void enable() { 4820 auto el = EventLoop.get().impl; 4821 el.handles ~= handle; 4822 } 4823 4824 /// 4825 void disable() { 4826 auto el = EventLoop.get().impl; 4827 for(int i = 0; i < el.handles.length; i++) { 4828 if(el.handles[i] is handle) { 4829 el.handles[i] = el.handles[$-1]; 4830 el.handles = el.handles[0 .. $-1]; 4831 return; 4832 } 4833 } 4834 } 4835 4836 void dispose() { 4837 disable(); 4838 if(handle) 4839 mapping.remove(handle); 4840 handle = null; 4841 } 4842 4843 void ready() { 4844 if(onReady) 4845 onReady(); 4846 } 4847 4848 HANDLE handle; 4849 void delegate() onReady; 4850 4851 __gshared WindowsHandleReader[HANDLE] mapping; 4852 } 4853 4854 version(Posix) 4855 /// Lets you add files to the event loop for reading. Use at your own risk. 4856 class PosixFdReader { 4857 /// 4858 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 4859 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 4860 } 4861 4862 /// 4863 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 4864 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 4865 } 4866 4867 /// 4868 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 4869 this.onReady = onReady; 4870 this.fd = fd; 4871 this.captureWrites = captureWrites; 4872 this.captureReads = captureReads; 4873 4874 mapping[fd] = this; 4875 4876 version(with_eventloop) { 4877 import arsd.eventloop; 4878 addFileEventListeners(fd, &readyel); 4879 } else { 4880 enable(); 4881 } 4882 } 4883 4884 bool captureReads; 4885 bool captureWrites; 4886 4887 version(with_eventloop) {} else 4888 /// 4889 void enable() { 4890 prepareEventLoop(); 4891 4892 enabled = true; 4893 4894 version(linux) { 4895 static import ep = core.sys.linux.epoll; 4896 ep.epoll_event ev = void; 4897 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 4898 //import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites); 4899 ev.data.fd = fd; 4900 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 4901 } else { 4902 4903 } 4904 } 4905 4906 version(with_eventloop) {} else 4907 /// 4908 void disable() { 4909 prepareEventLoop(); 4910 4911 enabled = false; 4912 4913 version(linux) { 4914 static import ep = core.sys.linux.epoll; 4915 ep.epoll_event ev = void; 4916 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 4917 //import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites); 4918 ev.data.fd = fd; 4919 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 4920 } 4921 } 4922 4923 version(with_eventloop) {} else 4924 /// 4925 void dispose() { 4926 if(enabled) 4927 disable(); 4928 if(fd != -1) 4929 mapping.remove(fd); 4930 fd = -1; 4931 } 4932 4933 void delegate(int, bool, bool) onReady; 4934 4935 version(with_eventloop) 4936 void readyel() { 4937 onReady(fd, true, true); 4938 } 4939 4940 void ready(uint flags) { 4941 version(linux) { 4942 static import ep = core.sys.linux.epoll; 4943 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 4944 } else { 4945 import core.sys.posix.poll; 4946 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 4947 } 4948 } 4949 4950 void hup(uint flags) { 4951 if(onHup) 4952 onHup(); 4953 } 4954 4955 void delegate() onHup; 4956 4957 int fd = -1; 4958 private bool enabled; 4959 __gshared PosixFdReader[int] mapping; 4960 } 4961 4962 // basic functions to access the clipboard 4963 /+ 4964 4965 4966 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 4967 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 4968 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 4969 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 4970 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 4971 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 4972 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 4973 4974 +/ 4975 4976 /++ 4977 this does a delegate because it is actually an async call on X... 4978 the receiver may never be called if the clipboard is empty or unavailable 4979 gets plain text from the clipboard 4980 +/ 4981 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 4982 version(Windows) { 4983 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 4984 if(OpenClipboard(hwndOwner) == 0) 4985 throw new Exception("OpenClipboard"); 4986 scope(exit) 4987 CloseClipboard(); 4988 // see: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpriorityclipboardformat 4989 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 4990 4991 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 4992 scope(exit) 4993 GlobalUnlock(dataHandle); 4994 4995 // FIXME: CR/LF conversions 4996 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 4997 int len = 0; 4998 auto d = data; 4999 while(*d) { 5000 d++; 5001 len++; 5002 } 5003 string s; 5004 s.reserve(len); 5005 foreach(dchar ch; data[0 .. len]) { 5006 s ~= ch; 5007 } 5008 receiver(s); 5009 } 5010 } 5011 } else version(X11) { 5012 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5013 } else version(OSXCocoa) { 5014 throw new NotYetImplementedException(); 5015 } else static assert(0); 5016 } 5017 5018 // FIXME: a clipboard listener might be cool btw 5019 5020 /++ 5021 this does a delegate because it is actually an async call on X... 5022 the receiver may never be called if the clipboard is empty or unavailable 5023 gets image from the clipboard 5024 5025 templated because it introduces an optional dependency on arsd.bmp 5026 +/ 5027 void getClipboardImage()(SimpleWindow clipboardOwner, void delegate(MemoryImage) receiver) { 5028 version(Windows) { 5029 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 5030 if(OpenClipboard(hwndOwner) == 0) 5031 throw new Exception("OpenClipboard"); 5032 scope(exit) 5033 CloseClipboard(); 5034 if(auto dataHandle = GetClipboardData(CF_DIBV5)) { 5035 if(auto data = cast(ubyte*) GlobalLock(dataHandle)) { 5036 scope(exit) 5037 GlobalUnlock(dataHandle); 5038 5039 auto len = GlobalSize(dataHandle); 5040 5041 import arsd.bmp; 5042 auto img = readBmp(data[0 .. len], false); 5043 receiver(img); 5044 } 5045 } 5046 } else version(X11) { 5047 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 5048 } else version(OSXCocoa) { 5049 throw new NotYetImplementedException(); 5050 } else static assert(0); 5051 } 5052 5053 version(Windows) 5054 struct WCharzBuffer { 5055 wchar[] buffer; 5056 wchar[256] staticBuffer = void; 5057 5058 size_t length() { 5059 return buffer.length; 5060 } 5061 5062 wchar* ptr() { 5063 return buffer.ptr; 5064 } 5065 5066 wchar[] slice() { 5067 return buffer; 5068 } 5069 5070 void copyInto(R)(ref R r) { 5071 static if(is(R == wchar[N], size_t N)) { 5072 r[0 .. this.length] = slice[]; 5073 r[this.length] = 0; 5074 } else static assert(0, "can only copy into wchar[n], not " ~ R.stringof); 5075 } 5076 5077 /++ 5078 conversionFlags = [WindowsStringConversionFlags] 5079 +/ 5080 this(in char[] data, int conversionFlags = 0) { 5081 conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name 5082 auto sz = sizeOfConvertedWstring(data, conversionFlags); 5083 if(sz > staticBuffer.length) 5084 buffer = new wchar[](sz); 5085 else 5086 buffer = staticBuffer[]; 5087 5088 buffer = makeWindowsString(data, buffer, conversionFlags); 5089 } 5090 } 5091 5092 version(Windows) 5093 int sizeOfConvertedWstring(in char[] s, int conversionFlags) { 5094 int size = 0; 5095 5096 if(conversionFlags & WindowsStringConversionFlags.convertNewLines) { 5097 // need to convert line endings, which means the length will get bigger. 5098 5099 // BTW I betcha this could be faster with some simd stuff. 5100 char last; 5101 foreach(char ch; s) { 5102 if(ch == 10 && last != 13) 5103 size++; // will add a 13 before it... 5104 size++; 5105 last = ch; 5106 } 5107 } else { 5108 // no conversion necessary, just estimate based on length 5109 /* 5110 I don't think there's any string with a longer length 5111 in code units when encoded in UTF-16 than it has in UTF-8. 5112 This will probably over allocate, but that's OK. 5113 */ 5114 size = cast(int) s.length; 5115 } 5116 5117 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) 5118 size++; 5119 5120 return size; 5121 } 5122 5123 version(Windows) 5124 enum WindowsStringConversionFlags : int { 5125 zeroTerminate = 1, 5126 convertNewLines = 2, 5127 } 5128 5129 version(Windows) 5130 class WindowsApiException : Exception { 5131 char[256] buffer; 5132 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 5133 assert(msg.length < 100); 5134 5135 auto error = GetLastError(); 5136 buffer[0 .. msg.length] = msg; 5137 buffer[msg.length] = ' '; 5138 5139 int pos = cast(int) msg.length + 1; 5140 5141 if(error == 0) 5142 buffer[pos++] = '0'; 5143 else { 5144 5145 auto ec = error; 5146 auto init = pos; 5147 while(ec) { 5148 buffer[pos++] = (ec % 10) + '0'; 5149 ec /= 10; 5150 } 5151 5152 buffer[pos++] = ' '; 5153 5154 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); 5155 5156 pos += size; 5157 } 5158 5159 5160 super(cast(string) buffer[0 .. pos], file, line, next); 5161 } 5162 } 5163 5164 class ErrnoApiException : Exception { 5165 char[256] buffer; 5166 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 5167 assert(msg.length < 100); 5168 5169 import core.stdc.errno; 5170 auto error = errno; 5171 buffer[0 .. msg.length] = msg; 5172 buffer[msg.length] = ' '; 5173 5174 int pos = cast(int) msg.length + 1; 5175 5176 if(error == 0) 5177 buffer[pos++] = '0'; 5178 else { 5179 auto init = pos; 5180 while(error) { 5181 buffer[pos++] = (error % 10) + '0'; 5182 error /= 10; 5183 } 5184 for(int i = 0; i < (pos - init) / 2; i++) { 5185 char c = buffer[i + init]; 5186 buffer[i + init] = buffer[pos - (i + init) - 1]; 5187 buffer[pos - (i + init) - 1] = c; 5188 } 5189 } 5190 5191 5192 super(cast(string) buffer[0 .. pos], file, line, next); 5193 } 5194 5195 } 5196 5197 version(Windows) 5198 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) { 5199 if(str.length == 0) 5200 return null; 5201 5202 int pos = 0; 5203 dchar last; 5204 foreach(dchar c; str) { 5205 if(c <= 0xFFFF) { 5206 if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13) 5207 buffer[pos++] = 13; 5208 buffer[pos++] = cast(wchar) c; 5209 } else if(c <= 0x10FFFF) { 5210 buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 5211 buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); 5212 } 5213 5214 last = c; 5215 } 5216 5217 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) { 5218 buffer[pos] = 0; 5219 } 5220 5221 return buffer[0 .. pos]; 5222 } 5223 5224 version(Windows) 5225 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) { 5226 if(str.length == 0) 5227 return null; 5228 5229 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null); 5230 if(got == 0) { 5231 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 5232 throw new Exception("not enough buffer"); 5233 else 5234 throw new Exception("conversion"); // FIXME: GetLastError 5235 } 5236 return buffer[0 .. got]; 5237 } 5238 5239 version(Windows) 5240 string makeUtf8StringFromWindowsString(in wchar[] str) { 5241 char[] buffer; 5242 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null); 5243 buffer.length = got; 5244 5245 // it is unique because we just allocated it above! 5246 return cast(string) makeUtf8StringFromWindowsString(str, buffer); 5247 } 5248 5249 version(Windows) 5250 string makeUtf8StringFromWindowsString(wchar* str) { 5251 char[] buffer; 5252 auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null); 5253 buffer.length = got; 5254 5255 got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null); 5256 if(got == 0) { 5257 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 5258 throw new Exception("not enough buffer"); 5259 else 5260 throw new Exception("conversion"); // FIXME: GetLastError 5261 } 5262 return cast(string) buffer[0 .. got]; 5263 } 5264 5265 int findIndexOfZero(in wchar[] str) { 5266 foreach(idx, wchar ch; str) 5267 if(ch == 0) 5268 return cast(int) idx; 5269 return cast(int) str.length; 5270 } 5271 int findIndexOfZero(in char[] str) { 5272 foreach(idx, char ch; str) 5273 if(ch == 0) 5274 return cast(int) idx; 5275 return cast(int) str.length; 5276 } 5277 5278 /// copies some text to the clipboard 5279 void setClipboardText(SimpleWindow clipboardOwner, string text) { 5280 assert(clipboardOwner !is null); 5281 version(Windows) { 5282 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5283 throw new Exception("OpenClipboard"); 5284 scope(exit) 5285 CloseClipboard(); 5286 EmptyClipboard(); 5287 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5288 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 5289 if(handle is null) throw new Exception("GlobalAlloc"); 5290 if(auto data = cast(wchar*) GlobalLock(handle)) { 5291 auto slice = data[0 .. sz]; 5292 scope(failure) 5293 GlobalUnlock(handle); 5294 5295 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 5296 5297 GlobalUnlock(handle); 5298 SetClipboardData(CF_UNICODETEXT, handle); 5299 } 5300 } else version(X11) { 5301 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 5302 } else version(OSXCocoa) { 5303 throw new NotYetImplementedException(); 5304 } else static assert(0); 5305 } 5306 5307 void setClipboardImage()(SimpleWindow clipboardOwner, MemoryImage img) { 5308 assert(clipboardOwner !is null); 5309 version(Windows) { 5310 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 5311 throw new Exception("OpenClipboard"); 5312 scope(exit) 5313 CloseClipboard(); 5314 EmptyClipboard(); 5315 5316 5317 import arsd.bmp; 5318 ubyte[] mdata; 5319 mdata.reserve(img.width * img.height); 5320 void sink(ubyte b) { 5321 mdata ~= b; 5322 } 5323 writeBmpIndirect(img, &sink, false); 5324 5325 auto handle = GlobalAlloc(GMEM_MOVEABLE, mdata.length); 5326 if(handle is null) throw new Exception("GlobalAlloc"); 5327 if(auto data = cast(ubyte*) GlobalLock(handle)) { 5328 auto slice = data[0 .. mdata.length]; 5329 scope(failure) 5330 GlobalUnlock(handle); 5331 5332 slice[] = mdata[]; 5333 5334 GlobalUnlock(handle); 5335 SetClipboardData(CF_DIB, handle); 5336 } 5337 } else version(X11) { 5338 static class X11SetSelectionHandler_Image : X11SetSelectionHandler { 5339 mixin X11SetSelectionHandler_Basics; 5340 private const(ubyte)[] mdata; 5341 private const(ubyte)[] mdata_original; 5342 this(MemoryImage img) { 5343 import arsd.bmp; 5344 5345 mdata.reserve(img.width * img.height); 5346 void sink(ubyte b) { 5347 mdata ~= b; 5348 } 5349 writeBmpIndirect(img, &sink, true); 5350 5351 mdata_original = mdata; 5352 } 5353 5354 Atom[] availableFormats() { 5355 auto display = XDisplayConnection.get; 5356 return [ 5357 GetAtom!"image/bmp"(display), 5358 GetAtom!"TARGETS"(display) 5359 ]; 5360 } 5361 5362 ubyte[] getData(Atom format, return scope ubyte[] data) { 5363 if(mdata.length < data.length) { 5364 data[0 .. mdata.length] = mdata[]; 5365 auto ret = data[0 .. mdata.length]; 5366 mdata = mdata[$..$]; 5367 return ret; 5368 } else { 5369 data[] = mdata[0 .. data.length]; 5370 mdata = mdata[data.length .. $]; 5371 return data[]; 5372 } 5373 } 5374 5375 void done() { 5376 mdata = mdata_original; 5377 } 5378 } 5379 5380 setX11Selection!"CLIPBOARD"(clipboardOwner, new X11SetSelectionHandler_Image(img)); 5381 } else version(OSXCocoa) { 5382 throw new NotYetImplementedException(); 5383 } else static assert(0); 5384 } 5385 5386 5387 version(X11) { 5388 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 5389 5390 private Atom*[] interredAtoms; // for discardAndRecreate 5391 5392 // FIXME: do a GetAtomUpfront too that just queues all at CT and combines it all. 5393 /// Platform specific for X11 5394 /// History: On February 21, 2021, I changed the default value of `create` to be true. 5395 @property Atom GetAtom(string name, bool create = true)(Display* display) { 5396 static Atom a; 5397 if(!a) { 5398 a = XInternAtom(display, name, !create); 5399 interredAtoms ~= &a; 5400 } 5401 if(a == None) 5402 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 5403 return a; 5404 } 5405 5406 /// Platform specific for X11 - gets atom names as a string 5407 string getAtomName(Atom atom, Display* display) { 5408 auto got = XGetAtomName(display, atom); 5409 scope(exit) XFree(got); 5410 import core.stdc.string; 5411 string s = got[0 .. strlen(got)].idup; 5412 return s; 5413 } 5414 5415 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later 5416 void setPrimarySelection(SimpleWindow window, string text) { 5417 setX11Selection!"PRIMARY"(window, text); 5418 } 5419 5420 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later 5421 void setSecondarySelection(SimpleWindow window, string text) { 5422 setX11Selection!"SECONDARY"(window, text); 5423 } 5424 5425 interface X11SetSelectionHandler { 5426 // should include TARGETS right now 5427 Atom[] availableFormats(); 5428 // Return the slice of data you filled, empty slice if done. 5429 // this is to support the incremental thing 5430 ubyte[] getData(Atom format, return scope ubyte[] data); 5431 5432 void done(); 5433 5434 void handleRequest(XEvent); 5435 5436 bool matchesIncr(Window, Atom); 5437 void sendMoreIncr(XPropertyEvent*); 5438 } 5439 5440 mixin template X11SetSelectionHandler_Basics() { 5441 Window incrWindow; 5442 Atom incrAtom; 5443 Atom selectionAtom; 5444 Atom formatAtom; 5445 ubyte[] toSend; 5446 bool matchesIncr(Window w, Atom a) { 5447 return incrAtom && incrAtom == a && w == incrWindow; 5448 } 5449 void sendMoreIncr(XPropertyEvent* event) { 5450 auto display = XDisplayConnection.get; 5451 5452 XChangeProperty (display, 5453 incrWindow, 5454 incrAtom, 5455 formatAtom, 5456 8 /* bits */, PropModeReplace, 5457 toSend.ptr, cast(int) toSend.length); 5458 5459 if(toSend.length != 0) { 5460 toSend = this.getData(formatAtom, toSend[]); 5461 } else { 5462 this.done(); 5463 incrWindow = None; 5464 incrAtom = None; 5465 selectionAtom = None; 5466 formatAtom = None; 5467 toSend = null; 5468 } 5469 } 5470 void handleRequest(XEvent ev) { 5471 5472 auto display = XDisplayConnection.get; 5473 5474 XSelectionRequestEvent* event = &ev.xselectionrequest; 5475 XSelectionEvent selectionEvent; 5476 selectionEvent.type = EventType.SelectionNotify; 5477 selectionEvent.display = event.display; 5478 selectionEvent.requestor = event.requestor; 5479 selectionEvent.selection = event.selection; 5480 selectionEvent.time = event.time; 5481 selectionEvent.target = event.target; 5482 5483 bool supportedType() { 5484 foreach(t; this.availableFormats()) 5485 if(t == event.target) 5486 return true; 5487 return false; 5488 } 5489 5490 if(event.property == None) { 5491 selectionEvent.property = event.target; 5492 5493 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 5494 XFlush(display); 5495 } if(event.target == GetAtom!"TARGETS"(display)) { 5496 /* respond with the supported types */ 5497 auto tlist = this.availableFormats(); 5498 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, cast(int) tlist.length); 5499 selectionEvent.property = event.property; 5500 5501 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 5502 XFlush(display); 5503 } else if(supportedType()) { 5504 auto buffer = new ubyte[](1024 * 64); 5505 auto toSend = this.getData(event.target, buffer[]); 5506 5507 if(toSend.length < 32 * 1024) { 5508 // small enough to send directly... 5509 selectionEvent.property = event.property; 5510 XChangeProperty (display, 5511 selectionEvent.requestor, 5512 selectionEvent.property, 5513 event.target, 5514 8 /* bits */, 0 /* PropModeReplace */, 5515 toSend.ptr, cast(int) toSend.length); 5516 5517 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 5518 XFlush(display); 5519 } else { 5520 // large, let's send incrementally 5521 arch_ulong l = toSend.length; 5522 5523 // if I wanted other events from this window don't want to clear that out.... 5524 XWindowAttributes xwa; 5525 XGetWindowAttributes(display, selectionEvent.requestor, &xwa); 5526 5527 XSelectInput(display, selectionEvent.requestor, cast(EventMask) (xwa.your_event_mask | EventMask.PropertyChangeMask)); 5528 5529 incrWindow = event.requestor; 5530 incrAtom = event.property; 5531 formatAtom = event.target; 5532 selectionAtom = event.selection; 5533 this.toSend = toSend; 5534 5535 selectionEvent.property = event.property; 5536 XChangeProperty (display, 5537 selectionEvent.requestor, 5538 selectionEvent.property, 5539 GetAtom!"INCR"(display), 5540 32 /* bits */, PropModeReplace, 5541 &l, 1); 5542 5543 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 5544 XFlush(display); 5545 } 5546 //if(after) 5547 //after(); 5548 } else { 5549 debug(sdpy_clip) { 5550 import std.stdio; writeln("Unsupported data ", getAtomName(event.target, display)); 5551 } 5552 selectionEvent.property = None; // I don't know how to handle this type... 5553 XSendEvent(display, selectionEvent.requestor, false, EventMask.NoEventMask, cast(XEvent*) &selectionEvent); 5554 XFlush(display); 5555 } 5556 } 5557 } 5558 5559 class X11SetSelectionHandler_Text : X11SetSelectionHandler { 5560 mixin X11SetSelectionHandler_Basics; 5561 private const(ubyte)[] text; 5562 private const(ubyte)[] text_original; 5563 this(string text) { 5564 this.text = cast(const ubyte[]) text; 5565 this.text_original = this.text; 5566 } 5567 Atom[] availableFormats() { 5568 auto display = XDisplayConnection.get; 5569 return [ 5570 GetAtom!"UTF8_STRING"(display), 5571 GetAtom!"text/plain"(display), 5572 XA_STRING, 5573 GetAtom!"TARGETS"(display) 5574 ]; 5575 } 5576 5577 ubyte[] getData(Atom format, return scope ubyte[] data) { 5578 if(text.length < data.length) { 5579 data[0 .. text.length] = text[]; 5580 return data[0 .. text.length]; 5581 } else { 5582 data[] = text[0 .. data.length]; 5583 text = text[data.length .. $]; 5584 return data[]; 5585 } 5586 } 5587 5588 void done() { 5589 text = text_original; 5590 } 5591 } 5592 5593 /// 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?!) 5594 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 5595 setX11Selection!atomName(window, new X11SetSelectionHandler_Text(text), after); 5596 } 5597 5598 void setX11Selection(string atomName)(SimpleWindow window, X11SetSelectionHandler data, void delegate() after = null) { 5599 assert(window !is null); 5600 5601 auto display = XDisplayConnection.get(); 5602 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 5603 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 5604 else Atom a = GetAtom!atomName(display); 5605 5606 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 5607 5608 window.impl.setSelectionHandlers[a] = data; 5609 } 5610 5611 /// 5612 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 5613 getX11Selection!"PRIMARY"(window, handler); 5614 } 5615 5616 // added July 28, 2020 5617 // undocumented as experimental tho 5618 interface X11GetSelectionHandler { 5619 void handleData(Atom target, in ubyte[] data); 5620 Atom findBestFormat(Atom[] answer); 5621 5622 void prepareIncremental(Window, Atom); 5623 bool matchesIncr(Window, Atom); 5624 void handleIncrData(Atom, in ubyte[] data); 5625 } 5626 5627 mixin template X11GetSelectionHandler_Basics() { 5628 Window incrWindow; 5629 Atom incrAtom; 5630 5631 void prepareIncremental(Window w, Atom a) { 5632 incrWindow = w; 5633 incrAtom = a; 5634 } 5635 bool matchesIncr(Window w, Atom a) { 5636 return incrWindow == w && incrAtom == a; 5637 } 5638 5639 Atom incrFormatAtom; 5640 ubyte[] incrData; 5641 void handleIncrData(Atom format, in ubyte[] data) { 5642 incrFormatAtom = format; 5643 5644 if(data.length) 5645 incrData ~= data; 5646 else 5647 handleData(incrFormatAtom, incrData); 5648 5649 } 5650 } 5651 5652 /// 5653 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler, Time timestamp = 0 /* CurrentTime */) { 5654 assert(window !is null); 5655 5656 auto display = XDisplayConnection.get(); 5657 auto atom = GetAtom!atomName(display); 5658 5659 static class X11GetSelectionHandler_Text : X11GetSelectionHandler { 5660 this(void delegate(in char[]) handler) { 5661 this.handler = handler; 5662 } 5663 5664 mixin X11GetSelectionHandler_Basics; 5665 5666 void delegate(in char[]) handler; 5667 5668 void handleData(Atom target, in ubyte[] data) { 5669 if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 5670 handler(cast(const char[]) data); 5671 } 5672 5673 Atom findBestFormat(Atom[] answer) { 5674 Atom best = None; 5675 foreach(option; answer) { 5676 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 5677 best = option; 5678 break; 5679 } else if(option == XA_STRING) { 5680 best = option; 5681 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 5682 best = option; 5683 } 5684 } 5685 return best; 5686 } 5687 } 5688 5689 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Text(handler); 5690 5691 auto target = GetAtom!"TARGETS"(display); 5692 5693 // SDD_DATA is "simpledisplay.d data" 5694 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, timestamp); 5695 } 5696 5697 /// Gets the image on the clipboard, if there is one. Added July 2020. 5698 void getX11Selection(string atomName)(SimpleWindow window, void delegate(MemoryImage) handler) { 5699 assert(window !is null); 5700 5701 auto display = XDisplayConnection.get(); 5702 auto atom = GetAtom!atomName(display); 5703 5704 static class X11GetSelectionHandler_Image : X11GetSelectionHandler { 5705 this(void delegate(MemoryImage) handler) { 5706 this.handler = handler; 5707 } 5708 5709 mixin X11GetSelectionHandler_Basics; 5710 5711 void delegate(MemoryImage) handler; 5712 5713 void handleData(Atom target, in ubyte[] data) { 5714 if(target == GetAtom!"image/bmp"(XDisplayConnection.get)) { 5715 import arsd.bmp; 5716 handler(readBmp(data)); 5717 } 5718 } 5719 5720 Atom findBestFormat(Atom[] answer) { 5721 Atom best = None; 5722 foreach(option; answer) { 5723 if(option == GetAtom!"image/bmp"(XDisplayConnection.get)) { 5724 best = option; 5725 } 5726 } 5727 return best; 5728 } 5729 5730 } 5731 5732 5733 window.impl.getSelectionHandlers[atom] = new X11GetSelectionHandler_Image(handler); 5734 5735 auto target = GetAtom!"TARGETS"(display); 5736 5737 // SDD_DATA is "simpledisplay.d data" 5738 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 5739 } 5740 5741 5742 /// 5743 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 5744 Atom actualType; 5745 int actualFormat; 5746 arch_ulong actualItems; 5747 arch_ulong bytesRemaining; 5748 void* data; 5749 5750 auto display = XDisplayConnection.get(); 5751 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 5752 if(actualFormat == 0) 5753 return null; 5754 else { 5755 int byteLength; 5756 if(actualFormat == 32) { 5757 // 32 means it is a C long... which is variable length 5758 actualFormat = cast(int) arch_long.sizeof * 8; 5759 } 5760 5761 // then it is just a bit count 5762 byteLength = cast(int) (actualItems * actualFormat / 8); 5763 5764 auto d = new ubyte[](byteLength); 5765 d[] = cast(ubyte[]) data[0 .. byteLength]; 5766 XFree(data); 5767 return d; 5768 } 5769 } 5770 return null; 5771 } 5772 5773 /* defined in the systray spec */ 5774 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 5775 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 5776 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 5777 5778 5779 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 5780 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 5781 public class GlobalHotkey { 5782 KeyEvent key; 5783 void delegate () handler; 5784 5785 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 5786 5787 /// Create from initialzed KeyEvent object 5788 this (KeyEvent akey, void delegate () ahandler=null) { 5789 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 5790 key = akey; 5791 handler = ahandler; 5792 } 5793 5794 /// Create from emacs-like key name ("C-M-Y", etc.) 5795 this (const(char)[] akey, void delegate () ahandler=null) { 5796 key = KeyEvent.parse(akey); 5797 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 5798 handler = ahandler; 5799 } 5800 5801 } 5802 5803 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 5804 //conwriteln("failed to grab key"); 5805 GlobalHotkeyManager.ghfailed = true; 5806 return 0; 5807 } 5808 5809 private extern(C) int XShmErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 5810 Image.impl.xshmfailed = true; 5811 return 0; 5812 } 5813 5814 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 5815 import core.stdc.stdio; 5816 char[265] buffer; 5817 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 5818 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); 5819 return 0; 5820 } 5821 5822 /++ 5823 Global hotkey manager. It contains static methods to manage global hotkeys. 5824 5825 --- 5826 try { 5827 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 5828 } catch (Exception e) { 5829 conwriteln("ERROR registering hotkey!"); 5830 } 5831 --- 5832 5833 The key strings are based on Emacs. In practical terms, 5834 `M` means `alt` and `H` means the Windows logo key. `C` 5835 is `ctrl`. 5836 5837 $(WARNING 5838 This is X-specific right now. If you are on 5839 Windows, try [registerHotKey] instead. 5840 5841 We will probably merge these into a single 5842 interface later. 5843 ) 5844 +/ 5845 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 5846 version(X11) { 5847 void recreateAfterDisconnect() { 5848 throw new Exception("NOT IMPLEMENTED"); 5849 } 5850 void discardConnectionState() { 5851 throw new Exception("NOT IMPLEMENTED"); 5852 } 5853 } 5854 5855 private static immutable uint[8] masklist = [ 0, 5856 KeyOrButtonMask.LockMask, 5857 KeyOrButtonMask.Mod2Mask, 5858 KeyOrButtonMask.Mod3Mask, 5859 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 5860 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 5861 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 5862 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 5863 ]; 5864 private __gshared GlobalHotkeyManager ghmanager; 5865 private __gshared bool ghfailed = false; 5866 5867 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 5868 if (modmask == 0) return false; 5869 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 5870 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 5871 return true; 5872 } 5873 5874 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 5875 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 5876 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 5877 return modmask; 5878 } 5879 5880 private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) { 5881 uint keycode = cast(uint)ke.key; 5882 auto dpy = XDisplayConnection.get; 5883 return XKeysymToKeycode(dpy, keycode); 5884 } 5885 5886 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 5887 5888 private __gshared GlobalHotkey[ulong] globalHotkeyList; 5889 5890 NativeEventHandler getNativeEventHandler () { 5891 return delegate int (XEvent e) { 5892 if (e.type != EventType.KeyPress) return 1; 5893 auto kev = cast(const(XKeyEvent)*)&e; 5894 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 5895 if (auto ghkp = hash in globalHotkeyList) { 5896 try { 5897 ghkp.doHandle(); 5898 } catch (Exception e) { 5899 import core.stdc.stdio : stderr, fprintf; 5900 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 5901 } 5902 } 5903 return 1; 5904 }; 5905 } 5906 5907 private this () { 5908 auto dpy = XDisplayConnection.get; 5909 auto root = RootWindow(dpy, DefaultScreen(dpy)); 5910 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 5911 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 5912 } 5913 5914 /// Register new global hotkey with initialized `GlobalHotkey` object. 5915 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 5916 static void register (GlobalHotkey gh) { 5917 if (gh is null) return; 5918 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 5919 5920 auto dpy = XDisplayConnection.get; 5921 immutable keycode = keyEvent2KeyCode(gh.key); 5922 5923 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 5924 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 5925 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 5926 XSync(dpy, 0/*False*/); 5927 5928 Window root = RootWindow(dpy, DefaultScreen(dpy)); 5929 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 5930 ghfailed = false; 5931 foreach (immutable uint ormask; masklist[]) { 5932 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 5933 } 5934 XSync(dpy, 0/*False*/); 5935 XSetErrorHandler(savedErrorHandler); 5936 5937 if (ghfailed) { 5938 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 5939 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 5940 XSync(dpy, 0/*False*/); 5941 XSetErrorHandler(savedErrorHandler); 5942 throw new Exception("cannot register global hotkey"); 5943 } 5944 5945 globalHotkeyList[hash] = gh; 5946 } 5947 5948 /// Ditto 5949 static void register (const(char)[] akey, void delegate () ahandler) { 5950 register(new GlobalHotkey(akey, ahandler)); 5951 } 5952 5953 private static void removeByHash (ulong hash) { 5954 if (auto ghp = hash in globalHotkeyList) { 5955 auto dpy = XDisplayConnection.get; 5956 immutable keycode = keyEvent2KeyCode(ghp.key); 5957 Window root = RootWindow(dpy, DefaultScreen(dpy)); 5958 XSync(dpy, 0/*False*/); 5959 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 5960 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 5961 XSync(dpy, 0/*False*/); 5962 XSetErrorHandler(savedErrorHandler); 5963 globalHotkeyList.remove(hash); 5964 } 5965 } 5966 5967 /// Register new global hotkey with previously used `GlobalHotkey` object. 5968 /// It is safe to unregister unknown or invalid hotkey. 5969 static void unregister (GlobalHotkey gh) { 5970 //TODO: add second AA for faster search? prolly doesn't worth it. 5971 if (gh is null) return; 5972 foreach (const ref kv; globalHotkeyList.byKeyValue) { 5973 if (kv.value is gh) { 5974 removeByHash(kv.key); 5975 return; 5976 } 5977 } 5978 } 5979 5980 /// Ditto. 5981 static void unregister (const(char)[] key) { 5982 auto kev = KeyEvent.parse(key); 5983 immutable keycode = keyEvent2KeyCode(kev); 5984 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 5985 } 5986 } 5987 } 5988 5989 version(Windows) { 5990 /// Platform-specific for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application) 5991 void sendSyntheticInput(wstring s) { 5992 INPUT[] inputs; 5993 inputs.reserve(s.length * 2); 5994 5995 foreach(wchar c; s) { 5996 INPUT input; 5997 input.type = INPUT_KEYBOARD; 5998 input.ki.wScan = c; 5999 input.ki.dwFlags = KEYEVENTF_UNICODE; 6000 inputs ~= input; 6001 6002 input.ki.dwFlags |= KEYEVENTF_KEYUP; 6003 inputs ~= input; 6004 } 6005 6006 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 6007 throw new Exception("SendInput failed"); 6008 } 6009 } 6010 6011 6012 // global hotkey helper function 6013 6014 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. 6015 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 6016 __gshared int hotkeyId = 0; 6017 int id = ++hotkeyId; 6018 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 6019 throw new Exception("RegisterHotKey failed"); 6020 6021 __gshared void delegate()[WPARAM][HWND] handlers; 6022 6023 handlers[window.impl.hwnd][id] = handler; 6024 6025 int delegate(HWND, UINT, WPARAM, LPARAM) oldHandler; 6026 6027 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 6028 switch(msg) { 6029 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 6030 case WM_HOTKEY: 6031 if(auto list = hwnd in handlers) { 6032 if(auto h = wParam in *list) { 6033 (*h)(); 6034 return 0; 6035 } 6036 } 6037 goto default; 6038 default: 6039 } 6040 if(oldHandler) 6041 return oldHandler(hwnd, msg, wParam, lParam); 6042 return 1; // pass it on 6043 }; 6044 6045 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 6046 oldHandler = window.handleNativeEvent; 6047 window.handleNativeEvent = nativeEventHandler; 6048 } 6049 6050 return id; 6051 } 6052 6053 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by registerHotKey. 6054 void unregisterHotKey(SimpleWindow window, int id) { 6055 if(!UnregisterHotKey(window.impl.hwnd, id)) 6056 throw new Exception("UnregisterHotKey"); 6057 } 6058 } 6059 6060 version (X11) { 6061 pragma(lib, "dl"); 6062 import core.sys.posix.dlfcn; 6063 6064 /++ 6065 Allows for sending synthetic input to the X server via the Xtst 6066 extension. 6067 6068 Please remember user input is meant to be user - don't use this 6069 if you have some other alternative! 6070 6071 If you need this on Windows btw, the top-level [sendSyntheticInput] shows 6072 the Win32 api to start it, but I only did basics there, PR welcome if you like, 6073 it is an easy enough function to use. 6074 6075 History: Added May 17, 2020. 6076 +/ 6077 struct SyntheticInput { 6078 @disable this(); 6079 6080 private void* lib; 6081 private int* refcount; 6082 6083 private extern(C) { 6084 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 6085 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 6086 } 6087 6088 /// The dummy param must be 0. 6089 this(int dummy) { 6090 lib = dlopen("libXtst.so", RTLD_NOW); 6091 if(lib is null) 6092 throw new Exception("cannot load xtest lib extension"); 6093 scope(failure) 6094 dlclose(lib); 6095 6096 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 6097 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 6098 6099 if(XTestFakeKeyEvent is null) 6100 throw new Exception("No XTestFakeKeyEvent"); 6101 if(XTestFakeButtonEvent is null) 6102 throw new Exception("No XTestFakeButtonEvent"); 6103 6104 refcount = new int; 6105 *refcount = 1; 6106 } 6107 6108 this(this) { 6109 if(refcount) 6110 *refcount += 1; 6111 } 6112 6113 ~this() { 6114 if(refcount) { 6115 *refcount -= 1; 6116 if(*refcount == 0) 6117 // I commented this because if I close the lib before 6118 // XCloseDisplay, it is liable to segfault... so just 6119 // gonna keep it loaded if it is loaded, no big deal 6120 // anyway. 6121 {} // dlclose(lib); 6122 } 6123 } 6124 6125 /// This ONLY works with basic ascii! 6126 void sendSyntheticInput(string s) { 6127 int delay = 0; 6128 foreach(ch; s) { 6129 pressKey(cast(Key) ch, true, delay); 6130 pressKey(cast(Key) ch, false, delay); 6131 delay += 5; 6132 } 6133 } 6134 6135 /++ 6136 Sends a fake press key event. 6137 6138 Please note you need to call [flushGui] or return to the event loop for this to actually be sent. 6139 +/ 6140 void pressKey(Key key, bool pressed, int delay = 0) { 6141 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 6142 } 6143 6144 /// 6145 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 6146 int btn; 6147 6148 switch(button) { 6149 case MouseButton.left: btn = 1; break; 6150 case MouseButton.middle: btn = 2; break; 6151 case MouseButton.right: btn = 3; break; 6152 case MouseButton.wheelUp: btn = 4; break; 6153 case MouseButton.wheelDown: btn = 5; break; 6154 case MouseButton.backButton: btn = 8; break; 6155 case MouseButton.forwardButton: btn = 9; break; 6156 default: 6157 } 6158 6159 assert(btn); 6160 6161 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 6162 } 6163 6164 /// 6165 static void moveMouseArrowBy(int dx, int dy) { 6166 auto disp = XDisplayConnection.get(); 6167 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 6168 XFlush(disp); 6169 } 6170 6171 /// 6172 static void moveMouseArrowTo(int x, int y) { 6173 auto disp = XDisplayConnection.get(); 6174 auto root = RootWindow(disp, DefaultScreen(disp)); 6175 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 6176 XFlush(disp); 6177 } 6178 } 6179 } 6180 6181 6182 6183 /++ 6184 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 6185 6186 See_Also: 6187 $(LIST 6188 *[ScreenPainter] 6189 *[ScreenPainter.rasterOp] 6190 ) 6191 +/ 6192 enum RasterOp { 6193 normal, /// Replaces the pixel. 6194 xor, /// Uses bitwise xor to draw. 6195 } 6196 6197 // being phobos-free keeps the size WAY down 6198 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 6199 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 6200 package(arsd) const(wchar)* toWStringz(string s) { 6201 wstring r; 6202 foreach(dchar c; s) 6203 r ~= c; 6204 r ~= '\0'; 6205 return r.ptr; 6206 } 6207 private string[] split(in void[] a, char c) { 6208 string[] ret; 6209 size_t previous = 0; 6210 foreach(i, char ch; cast(ubyte[]) a) { 6211 if(ch == c) { 6212 ret ~= cast(string) a[previous .. i]; 6213 previous = i + 1; 6214 } 6215 } 6216 if(previous != a.length) 6217 ret ~= cast(string) a[previous .. $]; 6218 return ret; 6219 } 6220 6221 version(without_opengl) { 6222 enum OpenGlOptions { 6223 no, 6224 } 6225 } else { 6226 /++ 6227 Determines if you want an OpenGL context created on the new window. 6228 6229 6230 See more: [#topics-3d|in the 3d topic]. 6231 6232 --- 6233 import arsd.simpledisplay; 6234 void main() { 6235 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 6236 6237 // Set up the matrix 6238 window.setAsCurrentOpenGlContext(); // make this window active 6239 6240 // This is called on each frame, we will draw our scene 6241 window.redrawOpenGlScene = delegate() { 6242 6243 }; 6244 6245 window.eventLoop(0); 6246 } 6247 --- 6248 +/ 6249 enum OpenGlOptions { 6250 no, /// No OpenGL context is created 6251 yes, /// Yes, create an OpenGL context 6252 } 6253 6254 version(X11) { 6255 static if (!SdpyIsUsingIVGLBinds) { 6256 6257 6258 struct __GLXFBConfigRec {} 6259 alias GLXFBConfig = __GLXFBConfigRec*; 6260 6261 //pragma(lib, "GL"); 6262 //pragma(lib, "GLU"); 6263 interface GLX { 6264 extern(C) nothrow @nogc { 6265 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 6266 const int *attrib_list); 6267 6268 void glXCopyContext(Display *dpy, GLXContext src, 6269 GLXContext dst, arch_ulong mask); 6270 6271 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 6272 GLXContext share_list, Bool direct); 6273 6274 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 6275 Pixmap pixmap); 6276 6277 void glXDestroyContext(Display *dpy, GLXContext ctx); 6278 6279 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 6280 6281 int glXGetConfig(Display *dpy, XVisualInfo *vis, 6282 int attrib, int *value); 6283 6284 GLXContext glXGetCurrentContext(); 6285 6286 GLXDrawable glXGetCurrentDrawable(); 6287 6288 Bool glXIsDirect(Display *dpy, GLXContext ctx); 6289 6290 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 6291 GLXContext ctx); 6292 6293 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 6294 6295 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 6296 6297 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 6298 6299 void glXUseXFont(Font font, int first, int count, int list_base); 6300 6301 void glXWaitGL(); 6302 6303 void glXWaitX(); 6304 6305 6306 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 6307 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 6308 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 6309 6310 char* glXQueryExtensionsString (Display*, int); 6311 void* glXGetProcAddress (const(char)*); 6312 6313 } 6314 } 6315 6316 version(OSX) 6317 mixin DynamicLoad!(GLX, "GL", 0, true) glx; 6318 else 6319 mixin DynamicLoad!(GLX, "GLX", 0, true) glx; 6320 shared static this() { 6321 glx.loadDynamicLibrary(); 6322 } 6323 6324 alias glbindGetProcAddress = glXGetProcAddress; 6325 } 6326 } else version(Windows) { 6327 /* it is done below by interface GL */ 6328 } else 6329 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."); 6330 } 6331 6332 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 6333 alias Resizablity = Resizability; 6334 6335 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 6336 enum Resizability { 6337 fixedSize, /// the window cannot be resized 6338 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. 6339 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. 6340 6341 // FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events 6342 } 6343 6344 6345 /++ 6346 Alignment for [ScreenPainter.drawText]. Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 6347 +/ 6348 enum TextAlignment : uint { 6349 Left = 0, /// 6350 Center = 1, /// 6351 Right = 2, /// 6352 6353 VerticalTop = 0, /// 6354 VerticalCenter = 4, /// 6355 VerticalBottom = 8, /// 6356 } 6357 6358 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 6359 alias Rectangle = arsd.color.Rectangle; 6360 6361 6362 /++ 6363 Keyboard press and release events 6364 +/ 6365 struct KeyEvent { 6366 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 6367 Key key; 6368 ubyte hardwareCode; /// A platform and hardware specific code for the key 6369 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 6370 6371 deprecated("This never actually worked anyway, you should do a character event handler instead.") dchar character; 6372 6373 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 6374 6375 SimpleWindow window; /// associated Window 6376 6377 /++ 6378 A view into the upcoming buffer holding coming character events that are sent if and only if neither 6379 the alt or super modifier keys are pressed (check this with `!(modifierState & (ModifierState.window | ModifierState.alt))` 6380 to predict if char events are actually coming.. 6381 6382 Only available on X systems since this information is not given ahead of time elsewhere. 6383 (Well, you COULD probably dig it up, but as far as I know right now, it isn't terribly pretty.) 6384 6385 I'm adding this because it is useful to the terminal emulator, but given its platform specificness 6386 and potential quirks I'd recommend avoiding it. 6387 6388 History: 6389 Added April 26, 2021 (dub v9.5) 6390 +/ 6391 version(X11) 6392 dchar[] charsPossible; 6393 6394 // convert key event to simplified string representation a-la emacs 6395 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 6396 uint dpos = 0; 6397 void put (const(char)[] s...) nothrow @trusted { 6398 static if (growdest) { 6399 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 6400 } else { 6401 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 6402 } 6403 } 6404 6405 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 6406 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 6407 } 6408 6409 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 6410 6411 // put modifiers 6412 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 6413 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 6414 putMod(ModifierState.alt, Key.Alt, "Alt+"); 6415 putMod(ModifierState.windows, Key.Shift, "Windows+"); 6416 putMod(ModifierState.shift, Key.Shift, "Shift+"); 6417 6418 if (this.key) { 6419 foreach (string kn; __traits(allMembers, Key)) { 6420 if (this.key == __traits(getMember, Key, kn)) { 6421 // HACK! 6422 static if (kn == "N0") put("0"); 6423 else static if (kn == "N1") put("1"); 6424 else static if (kn == "N2") put("2"); 6425 else static if (kn == "N3") put("3"); 6426 else static if (kn == "N4") put("4"); 6427 else static if (kn == "N5") put("5"); 6428 else static if (kn == "N6") put("6"); 6429 else static if (kn == "N7") put("7"); 6430 else static if (kn == "N8") put("8"); 6431 else static if (kn == "N9") put("9"); 6432 else put(kn); 6433 return dest[0..dpos]; 6434 } 6435 } 6436 put("Unknown"); 6437 } else { 6438 if (dpos && dest[dpos-1] == '+') --dpos; 6439 } 6440 return dest[0..dpos]; 6441 } 6442 6443 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 6444 6445 /** Parse string into key name with modifiers. It accepts things like: 6446 * 6447 * C-H-1 -- emacs style (ctrl, and windows, and 1) 6448 * 6449 * Ctrl+Win+1 -- windows style 6450 * 6451 * Ctrl-Win-1 -- '-' is a valid delimiter too 6452 * 6453 * Ctrl Win 1 -- and space 6454 * 6455 * and even "Win + 1 + Ctrl". 6456 */ 6457 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 6458 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 6459 6460 // remove trailing spaces 6461 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 6462 6463 // tokens delimited by blank, '+', or '-' 6464 // null on eol 6465 const(char)[] getToken () nothrow @trusted @nogc { 6466 // remove leading spaces and delimiters 6467 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 6468 if (name.length == 0) return null; // oops, no more tokens 6469 // get token 6470 size_t epos = 0; 6471 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 6472 assert(epos > 0 && epos <= name.length); 6473 auto res = name[0..epos]; 6474 name = name[epos..$]; 6475 return res; 6476 } 6477 6478 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 6479 if (s0.length != s1.length) return false; 6480 foreach (immutable ci, char c0; s0) { 6481 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 6482 char c1 = s1[ci]; 6483 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 6484 if (c0 != c1) return false; 6485 } 6486 return true; 6487 } 6488 6489 if (ignoreModsOut !is null) *ignoreModsOut = false; 6490 if (updown !is null) *updown = -1; 6491 KeyEvent res; 6492 res.key = cast(Key)0; // just in case 6493 const(char)[] tk, tkn; // last token 6494 bool allowEmascStyle = true; 6495 bool ignoreModifiers = false; 6496 tokenloop: for (;;) { 6497 tk = tkn; 6498 tkn = getToken(); 6499 //k8: yay, i took "Bloody Mess" trait from Fallout! 6500 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 6501 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 6502 if (allowEmascStyle && tkn.length != 0) { 6503 if (tk.length == 1) { 6504 char mdc = tk[0]; 6505 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 6506 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 6507 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 6508 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 6509 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 6510 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 6511 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 6512 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 6513 } 6514 } 6515 allowEmascStyle = false; 6516 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 6517 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 6518 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 6519 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 6520 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 6521 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 6522 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 6523 if (tk.length == 0) continue; 6524 // try key name 6525 if (res.key == 0) { 6526 // little hack 6527 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 6528 final switch (tk[0]) { 6529 case '0': tk = "N0"; break; 6530 case '1': tk = "N1"; break; 6531 case '2': tk = "N2"; break; 6532 case '3': tk = "N3"; break; 6533 case '4': tk = "N4"; break; 6534 case '5': tk = "N5"; break; 6535 case '6': tk = "N6"; break; 6536 case '7': tk = "N7"; break; 6537 case '8': tk = "N8"; break; 6538 case '9': tk = "N9"; break; 6539 } 6540 } 6541 foreach (string kn; __traits(allMembers, Key)) { 6542 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 6543 } 6544 } 6545 // unknown or duplicate key name, get out of here 6546 break; 6547 } 6548 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 6549 return res; // something 6550 } 6551 6552 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 6553 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 6554 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 6555 if (kk == k) { mask |= mst; kk = cast(Key)0; } 6556 } 6557 bool ignoreMods; 6558 int updown; 6559 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 6560 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 6561 if (this.key != ke.key) { 6562 // things like "ctrl+alt" are complicated 6563 uint tkm = this.modifierState&modmask; 6564 uint kkm = ke.modifierState&modmask; 6565 Key tk = this.key; 6566 // ke 6567 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 6568 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 6569 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 6570 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 6571 // this 6572 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 6573 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 6574 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 6575 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 6576 return (tk == ke.key && tkm == kkm); 6577 } 6578 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 6579 } 6580 } 6581 6582 /// sets the application name. 6583 @property string ApplicationName(string name) { 6584 return _applicationName = name; 6585 } 6586 6587 string _applicationName; 6588 6589 /// ditto 6590 @property string ApplicationName() { 6591 if(_applicationName is null) { 6592 import core.runtime; 6593 return Runtime.args[0]; 6594 } 6595 return _applicationName; 6596 } 6597 6598 6599 /// Type of a [MouseEvent] 6600 enum MouseEventType : int { 6601 motion = 0, /// The mouse moved inside the window 6602 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 6603 buttonReleased = 2, /// A mouse button was released 6604 } 6605 6606 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 6607 /++ 6608 Listen for this on your event listeners if you are interested in mouse action. 6609 6610 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. 6611 6612 Examples: 6613 6614 This will draw boxes on the window with the mouse as you hold the left button. 6615 --- 6616 import arsd.simpledisplay; 6617 6618 void main() { 6619 auto window = new SimpleWindow(); 6620 6621 window.eventLoop(0, 6622 (MouseEvent ev) { 6623 if(ev.modifierState & ModifierState.leftButtonDown) { 6624 auto painter = window.draw(); 6625 painter.fillColor = Color.red; 6626 painter.outlineColor = Color.black; 6627 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 6628 } 6629 } 6630 ); 6631 } 6632 --- 6633 +/ 6634 struct MouseEvent { 6635 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 6636 6637 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. 6638 int y; /// Current Y position of the cursor when the event fired. 6639 6640 int dx; /// Change in X position since last report 6641 int dy; /// Change in Y position since last report 6642 6643 MouseButton button; /// See [MouseButton] 6644 int modifierState; /// See [ModifierState] 6645 6646 version(X11) 6647 private Time timestamp; 6648 6649 /// Returns a linear representation of mouse button, 6650 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 6651 /// 6652 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 6653 @property ubyte buttonLinear() const { 6654 import core.bitop; 6655 if(button == 0) 6656 return 0; 6657 return (bsf(button) + 1) & 0b1111; 6658 } 6659 6660 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 6661 6662 SimpleWindow window; /// The window in which the event happened. 6663 6664 Point globalCoordinates() { 6665 Point p; 6666 if(window is null) 6667 throw new Exception("wtf"); 6668 static if(UsingSimpledisplayX11) { 6669 Window child; 6670 XTranslateCoordinates( 6671 XDisplayConnection.get, 6672 window.impl.window, 6673 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 6674 x, y, &p.x, &p.y, &child); 6675 return p; 6676 } else version(Windows) { 6677 POINT[1] points; 6678 points[0].x = x; 6679 points[0].y = y; 6680 MapWindowPoints( 6681 window.impl.hwnd, 6682 null, 6683 points.ptr, 6684 points.length 6685 ); 6686 p.x = points[0].x; 6687 p.y = points[0].y; 6688 6689 return p; 6690 } else version(OSXCocoa) { 6691 throw new NotYetImplementedException(); 6692 } else static assert(0); 6693 } 6694 6695 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 6696 6697 /** 6698 can contain emacs-like modifier prefix 6699 case-insensitive names: 6700 lmbX/leftX 6701 rmbX/rightX 6702 mmbX/middleX 6703 wheelX 6704 motion (no prefix allowed) 6705 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 6706 */ 6707 static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 6708 if (str.length == 0) return false; // just in case 6709 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 6710 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 6711 auto anchor = str; 6712 uint mods = 0; // uint.max == any 6713 // interesting bits in kmod 6714 uint kmodmask = 6715 ModifierState.shift| 6716 ModifierState.ctrl| 6717 ModifierState.alt| 6718 ModifierState.windows| 6719 ModifierState.leftButtonDown| 6720 ModifierState.middleButtonDown| 6721 ModifierState.rightButtonDown| 6722 0; 6723 uint lastButt = uint.max; // otherwise, bit 31 means "down" 6724 bool wasButtons = false; 6725 while (str.length) { 6726 if (str.ptr[0] <= ' ') { 6727 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 6728 continue; 6729 } 6730 // one-letter modifier? 6731 if (str.length >= 2 && str.ptr[1] == '-') { 6732 switch (str.ptr[0]) { 6733 case '*': // "any" modifier (cannot be undone) 6734 mods = mods.max; 6735 break; 6736 case 'C': case 'c': // emacs "ctrl" 6737 if (mods != mods.max) mods |= ModifierState.ctrl; 6738 break; 6739 case 'M': case 'm': // emacs "meta" 6740 if (mods != mods.max) mods |= ModifierState.alt; 6741 break; 6742 case 'S': case 's': // emacs "shift" 6743 if (mods != mods.max) mods |= ModifierState.shift; 6744 break; 6745 case 'H': case 'h': // emacs "hyper" (aka winkey) 6746 if (mods != mods.max) mods |= ModifierState.windows; 6747 break; 6748 default: 6749 return false; // unknown modifier 6750 } 6751 str = str[2..$]; 6752 continue; 6753 } 6754 // word 6755 char[16] buf = void; // locased 6756 auto wep = 0; 6757 while (str.length) { 6758 immutable char ch = str.ptr[0]; 6759 if (ch <= ' ' || ch == '-') break; 6760 str = str[1..$]; 6761 if (wep > buf.length) return false; // too long 6762 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 6763 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 6764 else return false; // invalid char 6765 } 6766 if (wep == 0) return false; // just in case 6767 uint bnum; 6768 enum UpDown { None = -1, Up, Down, Any } 6769 auto updown = UpDown.None; // 0: up; 1: down 6770 switch (buf[0..wep]) { 6771 // left button 6772 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 6773 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 6774 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 6775 case "lmb": case "left": bnum = 0; break; 6776 // middle button 6777 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 6778 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 6779 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 6780 case "mmb": case "middle": bnum = 1; break; 6781 // right button 6782 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 6783 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 6784 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 6785 case "rmb": case "right": bnum = 2; break; 6786 // wheel 6787 case "wheelup": updown = UpDown.Up; goto case "wheel"; 6788 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 6789 case "wheelany": updown = UpDown.Any; goto case "wheel"; 6790 case "wheel": bnum = 3; break; 6791 // motion 6792 case "motion": bnum = 7; break; 6793 // unknown 6794 default: return false; 6795 } 6796 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 6797 // parse possible "-up" or "-down" 6798 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 6799 wep = 0; 6800 foreach (immutable idx, immutable char ch; str[1..$]) { 6801 if (ch <= ' ' || ch == '-') break; 6802 assert(idx == wep); // for now; trick 6803 if (wep > buf.length) { wep = 0; break; } // too long 6804 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 6805 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 6806 else { wep = 0; break; } // invalid char 6807 } 6808 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 6809 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 6810 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 6811 // remove parsed part 6812 if (updown != UpDown.None) str = str[wep+1..$]; 6813 } 6814 if (updown == UpDown.None) { 6815 updown = UpDown.Down; 6816 } 6817 wasButtons = wasButtons || (bnum <= 2); 6818 //assert(updown != UpDown.None); 6819 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 6820 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 6821 if (lastButt != lastButt.max) { 6822 if ((lastButt&0xff) >= 3) return false; // wheel or motion 6823 if (mods != mods.max) { 6824 uint butbit = 0; 6825 final switch (lastButt&0x03) { 6826 case 0: butbit = ModifierState.leftButtonDown; break; 6827 case 1: butbit = ModifierState.middleButtonDown; break; 6828 case 2: butbit = ModifierState.rightButtonDown; break; 6829 } 6830 if (lastButt&Flag.Down) mods |= butbit; 6831 else if (lastButt&Flag.Up) mods &= ~butbit; 6832 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 6833 } 6834 } 6835 // remember last button 6836 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 6837 } 6838 // no button -- nothing to do 6839 if (lastButt == lastButt.max) return false; 6840 // done parsing, check if something's left 6841 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 6842 // remove action button from mask 6843 if ((lastButt&0xff) < 3) { 6844 final switch (lastButt&0x03) { 6845 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 6846 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 6847 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 6848 } 6849 } 6850 // special case: "Motion" means "ignore buttons" 6851 if ((lastButt&0xff) == 7 && !wasButtons) { 6852 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 6853 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 6854 } 6855 uint kmod = event.modifierState&kmodmask; 6856 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 6857 // check modifier state 6858 if (mods != mods.max) { 6859 if (kmod != mods) return false; 6860 } 6861 // now check type 6862 if ((lastButt&0xff) == 7) { 6863 // motion 6864 if (event.type != MouseEventType.motion) return false; 6865 } else if ((lastButt&0xff) == 3) { 6866 // wheel 6867 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 6868 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 6869 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 6870 return false; 6871 } else { 6872 // buttons 6873 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 6874 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 6875 { 6876 return false; 6877 } 6878 // button number 6879 switch (lastButt&0x03) { 6880 case 0: if (event.button != MouseButton.left) return false; break; 6881 case 1: if (event.button != MouseButton.middle) return false; break; 6882 case 2: if (event.button != MouseButton.right) return false; break; 6883 default: return false; 6884 } 6885 } 6886 return true; 6887 } 6888 } 6889 6890 version(arsd_mevent_strcmp_test) unittest { 6891 MouseEvent event; 6892 event.type = MouseEventType.buttonPressed; 6893 event.button = MouseButton.left; 6894 event.modifierState = ModifierState.ctrl; 6895 assert(event == "C-LMB"); 6896 assert(event != "C-LMBUP"); 6897 assert(event != "C-LMB-UP"); 6898 assert(event != "C-S-LMB"); 6899 assert(event == "*-LMB"); 6900 assert(event != "*-LMB-UP"); 6901 6902 event.type = MouseEventType.buttonReleased; 6903 assert(event != "C-LMB"); 6904 assert(event == "C-LMBUP"); 6905 assert(event == "C-LMB-UP"); 6906 assert(event != "C-S-LMB"); 6907 assert(event != "*-LMB"); 6908 assert(event == "*-LMB-UP"); 6909 6910 event.button = MouseButton.right; 6911 event.modifierState |= ModifierState.shift; 6912 event.type = MouseEventType.buttonPressed; 6913 assert(event != "C-LMB"); 6914 assert(event != "C-LMBUP"); 6915 assert(event != "C-LMB-UP"); 6916 assert(event != "C-S-LMB"); 6917 assert(event != "*-LMB"); 6918 assert(event != "*-LMB-UP"); 6919 6920 assert(event != "C-RMB"); 6921 assert(event != "C-RMBUP"); 6922 assert(event != "C-RMB-UP"); 6923 assert(event == "C-S-RMB"); 6924 assert(event == "*-RMB"); 6925 assert(event != "*-RMB-UP"); 6926 } 6927 6928 /// This gives a few more options to drawing lines and such 6929 struct Pen { 6930 Color color; /// the foreground color 6931 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. 6932 Style style; /// See [Style] 6933 /+ 6934 // From X.h 6935 6936 #define LineSolid 0 6937 #define LineOnOffDash 1 6938 #define LineDoubleDash 2 6939 LineDou- The full path of the line is drawn, but the 6940 bleDash even dashes are filled differently from the 6941 odd dashes (see fill-style) with CapButt 6942 style used where even and odd dashes meet. 6943 6944 6945 6946 /* capStyle */ 6947 6948 #define CapNotLast 0 6949 #define CapButt 1 6950 #define CapRound 2 6951 #define CapProjecting 3 6952 6953 /* joinStyle */ 6954 6955 #define JoinMiter 0 6956 #define JoinRound 1 6957 #define JoinBevel 2 6958 6959 /* fillStyle */ 6960 6961 #define FillSolid 0 6962 #define FillTiled 1 6963 #define FillStippled 2 6964 #define FillOpaqueStippled 3 6965 6966 6967 +/ 6968 /// Style of lines drawn 6969 enum Style { 6970 Solid, /// a solid line 6971 Dashed, /// a dashed line 6972 Dotted, /// a dotted line 6973 } 6974 } 6975 6976 6977 /++ 6978 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 6979 6980 6981 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 6982 6983 $(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.) 6984 6985 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. 6986 6987 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 6988 6989 $(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. 6990 6991 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! 6992 6993 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!) 6994 6995 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 6996 6997 --- 6998 auto image = new Image(256, 256); 6999 scope(exit) destroy(image); 7000 --- 7001 7002 As long as you don't hold on to it outside the scope. 7003 7004 I might change it to be an owned pointer at some point in the future. 7005 7006 ) 7007 7008 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 7009 you can also often get a fair amount of speedup by getting the raw data format and 7010 writing some custom code. 7011 7012 FIXME INSERT EXAMPLES HERE 7013 7014 7015 +/ 7016 final class Image { 7017 /// 7018 this(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 7019 this.width = width; 7020 this.height = height; 7021 this.enableAlpha = enableAlpha; 7022 7023 impl.createImage(width, height, forcexshm, enableAlpha); 7024 } 7025 7026 /// 7027 this(Size size, bool forcexshm=false, bool enableAlpha = false) { 7028 this(size.width, size.height, forcexshm, enableAlpha); 7029 } 7030 7031 private bool suppressDestruction; 7032 7033 version(X11) 7034 this(XImage* handle) { 7035 this.handle = handle; 7036 this.rawData = cast(ubyte*) handle.data; 7037 this.width = handle.width; 7038 this.height = handle.height; 7039 this.enableAlpha = handle.depth == 32; 7040 suppressDestruction = true; 7041 } 7042 7043 ~this() { 7044 if(suppressDestruction) return; 7045 impl.dispose(); 7046 } 7047 7048 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 7049 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 7050 pure const @system nothrow { 7051 /* 7052 To use these to draw a blue rectangle with size WxH at position X,Y... 7053 7054 // make certain that it will fit before we proceed 7055 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! 7056 7057 // gather all the values you'll need up front. These can be kept until the image changes size if you want 7058 // (though calculating them isn't really that expensive). 7059 auto nextLineAdjustment = img.adjustmentForNextLine(); 7060 auto offR = img.redByteOffset(); 7061 auto offB = img.blueByteOffset(); 7062 auto offG = img.greenByteOffset(); 7063 auto bpp = img.bytesPerPixel(); 7064 7065 auto data = img.getDataPointer(); 7066 7067 // figure out the starting byte offset 7068 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 7069 7070 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 7071 7072 // and now our drawing loop for the rectangle 7073 foreach(y; 0 .. H) { 7074 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 7075 foreach(x; 0 .. W) { 7076 // write our color 7077 data[offR] = 0; 7078 data[offG] = 0; 7079 data[offB] = 255; 7080 7081 data += bpp; // moving to the next pixel is just an addition... 7082 } 7083 startOfLine += nextLineAdjustment; 7084 } 7085 7086 7087 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 7088 7089 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 7090 can be made into a bitmask or something so we can write them as *uint... 7091 */ 7092 7093 /// 7094 int offsetForTopLeftPixel() { 7095 version(X11) { 7096 return 0; 7097 } else version(Windows) { 7098 if(enableAlpha) { 7099 return (width * 4) * (height - 1); 7100 } else { 7101 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 7102 } 7103 } else version(OSXCocoa) { 7104 return 0 ; //throw new NotYetImplementedException(); 7105 } else static assert(0, "fill in this info for other OSes"); 7106 } 7107 7108 /// 7109 int offsetForPixel(int x, int y) { 7110 version(X11) { 7111 auto offset = (y * width + x) * 4; 7112 return offset; 7113 } else version(Windows) { 7114 if(enableAlpha) { 7115 auto itemsPerLine = width * 4; 7116 // remember, bmps are upside down 7117 auto offset = itemsPerLine * (height - y - 1) + x * 4; 7118 return offset; 7119 } else { 7120 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 7121 // remember, bmps are upside down 7122 auto offset = itemsPerLine * (height - y - 1) + x * 3; 7123 return offset; 7124 } 7125 } else version(OSXCocoa) { 7126 return 0 ; //throw new NotYetImplementedException(); 7127 } else static assert(0, "fill in this info for other OSes"); 7128 } 7129 7130 /// 7131 int adjustmentForNextLine() { 7132 version(X11) { 7133 return width * 4; 7134 } else version(Windows) { 7135 // windows bmps are upside down, so the adjustment is actually negative 7136 if(enableAlpha) 7137 return - (cast(int) width * 4); 7138 else 7139 return -((cast(int) width * 3 + 3) / 4) * 4; 7140 } else version(OSXCocoa) { 7141 return 0 ; //throw new NotYetImplementedException(); 7142 } else static assert(0, "fill in this info for other OSes"); 7143 } 7144 7145 /// once you have the position of a pixel, use these to get to the proper color 7146 int redByteOffset() { 7147 version(X11) { 7148 return 2; 7149 } else version(Windows) { 7150 return 2; 7151 } else version(OSXCocoa) { 7152 return 0 ; //throw new NotYetImplementedException(); 7153 } else static assert(0, "fill in this info for other OSes"); 7154 } 7155 7156 /// 7157 int greenByteOffset() { 7158 version(X11) { 7159 return 1; 7160 } else version(Windows) { 7161 return 1; 7162 } else version(OSXCocoa) { 7163 return 0 ; //throw new NotYetImplementedException(); 7164 } else static assert(0, "fill in this info for other OSes"); 7165 } 7166 7167 /// 7168 int blueByteOffset() { 7169 version(X11) { 7170 return 0; 7171 } else version(Windows) { 7172 return 0; 7173 } else version(OSXCocoa) { 7174 return 0 ; //throw new NotYetImplementedException(); 7175 } else static assert(0, "fill in this info for other OSes"); 7176 } 7177 7178 /// Only valid if [enableAlpha] is true 7179 int alphaByteOffset() { 7180 version(X11) { 7181 return 3; 7182 } else version(Windows) { 7183 return 3; 7184 } else version(OSXCocoa) { 7185 return 3; //throw new NotYetImplementedException(); 7186 } else static assert(0, "fill in this info for other OSes"); 7187 } 7188 } 7189 7190 /// 7191 final void putPixel(int x, int y, Color c) { 7192 if(x < 0 || x >= width) 7193 return; 7194 if(y < 0 || y >= height) 7195 return; 7196 7197 impl.setPixel(x, y, c); 7198 } 7199 7200 /// 7201 final Color getPixel(int x, int y) { 7202 if(x < 0 || x >= width) 7203 return Color.transparent; 7204 if(y < 0 || y >= height) 7205 return Color.transparent; 7206 7207 version(OSXCocoa) throw new NotYetImplementedException(); else 7208 return impl.getPixel(x, y); 7209 } 7210 7211 /// 7212 final void opIndexAssign(Color c, int x, int y) { 7213 putPixel(x, y, c); 7214 } 7215 7216 /// 7217 TrueColorImage toTrueColorImage() { 7218 auto tci = new TrueColorImage(width, height); 7219 convertToRgbaBytes(tci.imageData.bytes); 7220 return tci; 7221 } 7222 7223 /// 7224 static Image fromMemoryImage(MemoryImage i, bool enableAlpha = false) { 7225 auto tci = i.getAsTrueColorImage(); 7226 auto img = new Image(tci.width, tci.height, false, enableAlpha); 7227 img.setRgbaBytes(tci.imageData.bytes); 7228 return img; 7229 } 7230 7231 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 7232 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 7233 /// if you pass null, it will allocate a new one. 7234 ubyte[] getRgbaBytes(ubyte[] where = null) { 7235 if(where is null) 7236 where = new ubyte[this.width*this.height*4]; 7237 convertToRgbaBytes(where); 7238 return where; 7239 } 7240 7241 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 7242 void setRgbaBytes(in ubyte[] from ) { 7243 assert(from.length == this.width * this.height * 4); 7244 setFromRgbaBytes(from); 7245 } 7246 7247 // FIXME: make properly cross platform by getting rgba right 7248 7249 /// warning: this is not portable across platforms because the data format can change 7250 ubyte* getDataPointer() { 7251 return impl.rawData; 7252 } 7253 7254 /// for use with getDataPointer 7255 final int bytesPerLine() const pure @safe nothrow { 7256 version(Windows) 7257 return enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 7258 else version(X11) 7259 return 4 * width; 7260 else version(OSXCocoa) 7261 return 4 * width; 7262 else static assert(0); 7263 } 7264 7265 /// for use with getDataPointer 7266 final int bytesPerPixel() const pure @safe nothrow { 7267 version(Windows) 7268 return enableAlpha ? 4 : 3; 7269 else version(X11) 7270 return 4; 7271 else version(OSXCocoa) 7272 return 4; 7273 else static assert(0); 7274 } 7275 7276 /// 7277 immutable int width; 7278 7279 /// 7280 immutable int height; 7281 7282 /// 7283 immutable bool enableAlpha; 7284 //private: 7285 mixin NativeImageImplementation!() impl; 7286 } 7287 7288 /// A convenience function to pop up a window displaying the image. 7289 /// If you pass a win, it will draw the image in it. Otherwise, it will 7290 /// create a window with the size of the image and run its event loop, closing 7291 /// when a key is pressed. 7292 void displayImage(Image image, SimpleWindow win = null) { 7293 if(win is null) { 7294 win = new SimpleWindow(image); 7295 { 7296 auto p = win.draw; 7297 p.drawImage(Point(0, 0), image); 7298 } 7299 win.eventLoop(0, 7300 (KeyEvent ev) { 7301 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 7302 } ); 7303 } else { 7304 win.image = image; 7305 } 7306 } 7307 7308 enum FontWeight : int { 7309 dontcare = 0, 7310 thin = 100, 7311 extralight = 200, 7312 light = 300, 7313 regular = 400, 7314 medium = 500, 7315 semibold = 600, 7316 bold = 700, 7317 extrabold = 800, 7318 heavy = 900 7319 } 7320 7321 // FIXME: i need a font cache and it needs to handle disconnects. 7322 7323 /++ 7324 Represents a font loaded off the operating system or the X server. 7325 7326 7327 While the api here is unified cross platform, the fonts are not necessarily 7328 available, even across machines of the same platform, so be sure to always check 7329 for null (using [isNull]) and have a fallback plan. 7330 7331 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 7332 7333 Worst case, a null font will automatically fall back to the default font loaded 7334 for your system. 7335 +/ 7336 class OperatingSystemFont { 7337 // FIXME: when the X Connection is lost, these need to be invalidated! 7338 // that means I need to store the original stuff again to reconstruct it too. 7339 7340 version(X11) { 7341 XFontStruct* font; 7342 XFontSet fontset; 7343 7344 version(with_xft) { 7345 XftFont* xftFont; 7346 bool isXft; 7347 } 7348 } else version(Windows) { 7349 HFONT font; 7350 int width_; 7351 int height_; 7352 } else version(OSXCocoa) { 7353 // FIXME 7354 } else static assert(0); 7355 7356 /++ 7357 Constructs the class and immediately calls [load]. 7358 +/ 7359 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 7360 load(name, size, weight, italic); 7361 } 7362 7363 /++ 7364 Constructs the object, but does nothing. Call one of [load] or [loadDefault] to populate the object. 7365 7366 You can also call the platform-specific [loadXft], [loadCoreX], or [loadWin32] functions if appropriate for you. 7367 7368 History: 7369 Added January 24, 2021. 7370 +/ 7371 this() { 7372 // this space intentionally left blank 7373 } 7374 7375 /++ 7376 Loads specifically with the Xft library - a freetype font from a fontconfig string. 7377 7378 History: 7379 Added November 13, 2020. 7380 +/ 7381 version(with_xft) 7382 bool loadXft(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 7383 unload(); 7384 7385 if(!XftLibrary.attempted) { 7386 XftLibrary.loadDynamicLibrary(); 7387 } 7388 7389 if(!XftLibrary.loadSuccessful) 7390 return false; 7391 7392 auto display = XDisplayConnection.get; 7393 7394 char[256] nameBuffer = void; 7395 int nbp = 0; 7396 7397 void add(in char[] a) { 7398 nameBuffer[nbp .. nbp + a.length] = a[]; 7399 nbp += a.length; 7400 } 7401 add(name); 7402 7403 if(size) { 7404 add(":size="); 7405 add(toInternal!string(size)); 7406 } 7407 if(weight != FontWeight.dontcare) { 7408 add(":weight="); 7409 add(weightToString(weight)); 7410 } 7411 if(italic) 7412 add(":slant=100"); 7413 7414 nameBuffer[nbp] = 0; 7415 7416 this.xftFont = XftFontOpenName( 7417 display, 7418 DefaultScreen(display), 7419 nameBuffer.ptr 7420 ); 7421 7422 this.isXft = true; 7423 7424 if(xftFont !is null) { 7425 isMonospace_ = stringWidth("x") == stringWidth("M"); 7426 ascent_ = xftFont.ascent; 7427 descent_ = xftFont.descent; 7428 } 7429 7430 return !isNull(); 7431 } 7432 7433 /++ 7434 Lists available fonts from the system that match the given pattern, finding names that are suitable for passing to [OperatingSystemFont]'s constructor. 7435 7436 7437 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. 7438 7439 If `pattern` is null, it returns all available font families. 7440 7441 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. 7442 7443 The format of the pattern is platform-specific. 7444 7445 History: 7446 Added May 1, 2021 (dub v9.5) 7447 +/ 7448 static void listFonts(string pattern, bool delegate(in char[] name) handler) { 7449 version(Windows) { 7450 auto hdc = GetDC(null); 7451 scope(exit) ReleaseDC(null, hdc); 7452 LOGFONT logfont; 7453 static extern(Windows) int proc(const LOGFONT* lf, const TEXTMETRIC* tm, DWORD type, LPARAM p) { 7454 auto localHandler = *(cast(typeof(handler)*) p); 7455 return localHandler(lf.lfFaceName[].sliceCString) ? 1 : 0; 7456 } 7457 EnumFontFamiliesEx(hdc, &logfont, &proc, cast(LPARAM) &handler, 0); 7458 } else version(X11) { 7459 //import core.stdc.stdio; 7460 bool done = false; 7461 version(with_xft) { 7462 if(!XftLibrary.attempted) { 7463 XftLibrary.loadDynamicLibrary(); 7464 } 7465 7466 if(!XftLibrary.loadSuccessful) 7467 goto skipXft; 7468 7469 if(!FontConfigLibrary.attempted) 7470 FontConfigLibrary.loadDynamicLibrary(); 7471 if(!FontConfigLibrary.loadSuccessful) 7472 goto skipXft; 7473 7474 { 7475 auto got = XftListFonts(XDisplayConnection.get, 0, null, "family".ptr, "style".ptr, null); 7476 if(got is null) 7477 goto skipXft; 7478 scope(exit) FcFontSetDestroy(got); 7479 7480 auto fontPatterns = got.fonts[0 .. got.nfont]; 7481 foreach(candidate; fontPatterns) { 7482 char* where, whereStyle; 7483 7484 char* pmg = FcNameUnparse(candidate); 7485 7486 //FcPatternGetString(candidate, "family", 0, &where); 7487 //FcPatternGetString(candidate, "style", 0, &whereStyle); 7488 //if(where && whereStyle) { 7489 if(pmg) { 7490 if(!handler(pmg.sliceCString)) 7491 return; 7492 //printf("%s || %s %s\n", pmg, where, whereStyle); 7493 } 7494 } 7495 } 7496 } 7497 7498 skipXft: 7499 7500 if(pattern is null) 7501 pattern = "*"; 7502 7503 int count; 7504 auto coreFontsRaw = XListFonts(XDisplayConnection.get, pattern.toStringz, 10000 /* max return */, &count); 7505 scope(exit) XFreeFontNames(coreFontsRaw); 7506 7507 auto coreFonts = coreFontsRaw[0 .. count]; 7508 7509 foreach(font; coreFonts) { 7510 char[128] tmp; 7511 tmp[0 ..5] = "core:"; 7512 auto cf = font.sliceCString; 7513 if(5 + cf.length > tmp.length) 7514 assert(0, "a font name was too long, sorry i didn't bother implementing a fallback"); 7515 tmp[5 .. 5 + cf.length] = cf; 7516 if(!handler(tmp[0 .. 5 + cf.length])) 7517 return; 7518 } 7519 } 7520 } 7521 7522 // see also: XftLockFace(font) which gives a FT_Face. from /usr/include/X11/Xft/Xft.h line 352 7523 7524 private string weightToString(FontWeight weight) { 7525 with(FontWeight) 7526 final switch(weight) { 7527 case dontcare: return "*"; 7528 case thin: return "extralight"; 7529 case extralight: return "extralight"; 7530 case light: return "light"; 7531 case regular: return "regular"; 7532 case medium: return "medium"; 7533 case semibold: return "demibold"; 7534 case bold: return "bold"; 7535 case extrabold: return "demibold"; 7536 case heavy: return "black"; 7537 } 7538 } 7539 7540 /++ 7541 Loads specifically a Core X font - rendered on the X server without antialiasing. Best performance. 7542 7543 History: 7544 Added November 13, 2020. Before then, this code was integrated in the [load] function. 7545 +/ 7546 version(X11) 7547 bool loadCoreX(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 7548 unload(); 7549 7550 string xfontstr; 7551 7552 if(name.length > 3 && name[0 .. 3] == "-*-") { 7553 // this is kinda a disgusting hack but if the user sends an exact 7554 // string I'd like to honor it... 7555 xfontstr = name; 7556 } else { 7557 string weightstr = weightToString(weight); 7558 string sizestr; 7559 if(size == 0) 7560 sizestr = "*"; 7561 else 7562 sizestr = toInternal!string(size); 7563 xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 7564 } 7565 7566 //import std.stdio; writeln(xfontstr); 7567 7568 auto display = XDisplayConnection.get; 7569 7570 font = XLoadQueryFont(display, xfontstr.ptr); 7571 if(font is null) 7572 return false; 7573 7574 char** lol; 7575 int lol2; 7576 char* lol3; 7577 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 7578 7579 prepareFontInfo(); 7580 7581 return !isNull(); 7582 } 7583 7584 version(X11) 7585 private void prepareFontInfo() { 7586 if(font !is null) { 7587 isMonospace_ = stringWidth("l") == stringWidth("M"); 7588 ascent_ = font.max_bounds.ascent; 7589 descent_ = font.max_bounds.descent; 7590 } 7591 } 7592 7593 /++ 7594 Loads a Windows font. You probably want to use [load] instead to be more generic. 7595 7596 History: 7597 Added November 13, 2020. Before then, this code was integrated in the [load] function. 7598 +/ 7599 version(Windows) 7600 bool loadWin32(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false, HDC hdc = null) { 7601 unload(); 7602 7603 WCharzBuffer buffer = WCharzBuffer(name); 7604 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 7605 7606 prepareFontInfo(hdc); 7607 7608 return !isNull(); 7609 } 7610 7611 version(Windows) 7612 void prepareFontInfo(HDC hdc = null) { 7613 if(font is null) 7614 return; 7615 7616 TEXTMETRIC tm; 7617 auto dc = hdc ? hdc : GetDC(null); 7618 auto orig = SelectObject(dc, font); 7619 GetTextMetrics(dc, &tm); 7620 SelectObject(dc, orig); 7621 if(hdc is null) 7622 ReleaseDC(null, dc); 7623 7624 width_ = tm.tmAveCharWidth; 7625 height_ = tm.tmHeight; 7626 ascent_ = tm.tmAscent; 7627 descent_ = tm.tmDescent; 7628 // 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. 7629 isMonospace_ = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0; 7630 } 7631 7632 7633 /++ 7634 `name` is a font name, but it can also be a more complicated string parsed in an OS-specific way. 7635 7636 On X, you may prefix a name with `core:` to bypass the freetype engine causing this function to forward to [loadCoreX]. Otherwise, 7637 it calls [loadXft] if the library is available. If the library or font is not available on Xft, it falls back on [loadCoreX]. 7638 7639 On Windows, it forwards directly to [loadWin32]. 7640 7641 Params: 7642 name = font name. This is looked up by the operating system and may be interpreted differently across platforms or user machines and their preferences. 7643 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. 7644 weight = approximate boldness, results may vary. 7645 italic = try to get a slanted version of the given font. 7646 7647 History: 7648 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. 7649 +/ 7650 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 7651 version(X11) { 7652 version(with_xft) { 7653 if(name.length > 5 && name[0 .. 5] == "core:") { 7654 goto core; 7655 } 7656 7657 if(loadXft(name, size, weight, italic)) 7658 return true; 7659 // if xft fails, fallback to core to avoid breaking 7660 // code that already depended on this. 7661 } 7662 7663 core: 7664 7665 if(name.length > 5 && name[0 .. 5] == "core:") { 7666 name = name[5 .. $]; 7667 } 7668 7669 return loadCoreX(name, size, weight, italic); 7670 } else version(Windows) { 7671 return loadWin32(name, size, weight, italic); 7672 } else version(OSXCocoa) { 7673 // FIXME 7674 return false; 7675 } else static assert(0); 7676 } 7677 7678 /// 7679 void unload() { 7680 if(isNull()) 7681 return; 7682 7683 version(X11) { 7684 auto display = XDisplayConnection.display; 7685 7686 if(display is null) 7687 return; 7688 7689 version(with_xft) { 7690 if(isXft) { 7691 if(xftFont) 7692 XftFontClose(display, xftFont); 7693 isXft = false; 7694 xftFont = null; 7695 return; 7696 } 7697 } 7698 7699 if(font && font !is ScreenPainterImplementation.defaultfont) 7700 XFreeFont(display, font); 7701 if(fontset && fontset !is ScreenPainterImplementation.defaultfontset) 7702 XFreeFontSet(display, fontset); 7703 7704 font = null; 7705 fontset = null; 7706 } else version(Windows) { 7707 DeleteObject(font); 7708 font = null; 7709 } else version(OSXCocoa) { 7710 // FIXME 7711 } else static assert(0); 7712 } 7713 7714 private bool isMonospace_; 7715 7716 /++ 7717 History: 7718 Added January 16, 2021 7719 +/ 7720 bool isMonospace() { 7721 return isMonospace_; 7722 } 7723 7724 /++ 7725 Returns the average width of the font, conventionally defined as the width of the lowercase 'x' character. 7726 7727 History: 7728 Added March 26, 2020 7729 Documented January 16, 2021 7730 +/ 7731 int averageWidth() { 7732 version(X11) { 7733 return stringWidth("x"); 7734 } else version(Windows) 7735 return width_; 7736 else assert(0); 7737 } 7738 7739 /++ 7740 Returns the width of the string as drawn on the specified window, or the default screen if the window is null. 7741 7742 History: 7743 Added January 16, 2021 7744 +/ 7745 int stringWidth(scope const(char)[] s, SimpleWindow window = null) { 7746 // FIXME: what about tab? 7747 if(isNull) 7748 return 0; 7749 7750 version(X11) { 7751 version(with_xft) 7752 if(isXft && xftFont !is null) { 7753 //return xftFont.max_advance_width; 7754 XGlyphInfo extents; 7755 XftTextExtentsUtf8(XDisplayConnection.get, xftFont, s.ptr, cast(int) s.length, &extents); 7756 //import std.stdio; writeln(extents); 7757 return extents.xOff; 7758 } 7759 if(font is null) 7760 return 0; 7761 else if(fontset) { 7762 XRectangle rect; 7763 Xutf8TextExtents(fontset, s.ptr, cast(int) s.length, null, &rect); 7764 7765 return rect.width; 7766 } else { 7767 return XTextWidth(font, s.ptr, cast(int) s.length); 7768 } 7769 } else version(Windows) { 7770 WCharzBuffer buffer = WCharzBuffer(s); 7771 7772 return stringWidth(buffer.slice, window); 7773 } 7774 else assert(0); 7775 } 7776 7777 version(Windows) 7778 /// ditto 7779 int stringWidth(scope const(wchar)[] s, SimpleWindow window = null) { 7780 if(isNull) 7781 return 0; 7782 version(Windows) { 7783 SIZE size; 7784 7785 prepareContext(window); 7786 scope(exit) releaseContext(); 7787 7788 GetTextExtentPoint32W(dc, s.ptr, cast(int) s.length, &size); 7789 7790 return size.cx; 7791 } else { 7792 // std.conv can do this easily but it is slow to import and i don't think it is worth it 7793 static assert(0, "not implemented yet"); 7794 //return stringWidth(s, window); 7795 } 7796 } 7797 7798 private { 7799 int prepRefcount; 7800 7801 version(Windows) { 7802 HDC dc; 7803 HANDLE orig; 7804 HWND hwnd; 7805 } 7806 } 7807 /++ 7808 [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. 7809 7810 History: 7811 Added January 23, 2021 7812 +/ 7813 void prepareContext(SimpleWindow window = null) { 7814 prepRefcount++; 7815 if(prepRefcount == 1) { 7816 version(Windows) { 7817 hwnd = window is null ? null : window.impl.hwnd; 7818 dc = GetDC(hwnd); 7819 orig = SelectObject(dc, font); 7820 } 7821 } 7822 } 7823 /// ditto 7824 void releaseContext() { 7825 prepRefcount--; 7826 if(prepRefcount == 0) { 7827 version(Windows) { 7828 SelectObject(dc, orig); 7829 ReleaseDC(hwnd, dc); 7830 hwnd = null; 7831 dc = null; 7832 orig = null; 7833 } 7834 } 7835 } 7836 7837 /+ 7838 FIXME: I think I need advance and kerning pair 7839 7840 int advance(dchar from, dchar to) { } // use dchar.init for first item in string 7841 +/ 7842 7843 /++ 7844 Returns the height of the font. 7845 7846 History: 7847 Added March 26, 2020 7848 Documented January 16, 2021 7849 +/ 7850 int height() { 7851 version(X11) { 7852 version(with_xft) 7853 if(isXft && xftFont !is null) { 7854 return xftFont.ascent + xftFont.descent; // i don't use height here because it doesn't include the baseline pixel 7855 } 7856 if(font is null) 7857 return 0; 7858 return font.max_bounds.ascent + font.max_bounds.descent; 7859 } else version(Windows) 7860 return height_; 7861 else assert(0); 7862 } 7863 7864 private int ascent_; 7865 private int descent_; 7866 7867 /++ 7868 Max ascent above the baseline. 7869 7870 History: 7871 Added January 22, 2021 7872 +/ 7873 int ascent() { 7874 return ascent_; 7875 } 7876 7877 /++ 7878 Max descent below the baseline. 7879 7880 History: 7881 Added January 22, 2021 7882 +/ 7883 int descent() { 7884 return descent_; 7885 } 7886 7887 /++ 7888 Loads the default font used by [ScreenPainter] if none others are loaded. 7889 7890 Returns: 7891 This method mutates the `this` object, but then returns `this` for 7892 easy chaining like: 7893 7894 --- 7895 auto font = foo.isNull ? foo : foo.loadDefault 7896 --- 7897 7898 History: 7899 Added previously, but left unimplemented until January 24, 2021. 7900 +/ 7901 OperatingSystemFont loadDefault() { 7902 unload(); 7903 7904 version(X11) { 7905 // another option would be https://tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html 7906 // but meh since sdpy does its own thing, this should be ok too 7907 7908 ScreenPainterImplementation.ensureDefaultFontLoaded(); 7909 this.font = ScreenPainterImplementation.defaultfont; 7910 this.fontset = ScreenPainterImplementation.defaultfontset; 7911 7912 prepareFontInfo(); 7913 } else version(Windows) { 7914 ScreenPainterImplementation.ensureDefaultFontLoaded(); 7915 this.font = ScreenPainterImplementation.defaultGuiFont; 7916 7917 prepareFontInfo(); 7918 } else throw new NotYetImplementedException(); 7919 7920 return this; 7921 } 7922 7923 /// 7924 bool isNull() { 7925 version(OSXCocoa) throw new NotYetImplementedException(); else { 7926 version(with_xft) 7927 if(isXft) 7928 return xftFont is null; 7929 return font is null; 7930 } 7931 } 7932 7933 /* Metrics */ 7934 /+ 7935 GetABCWidth 7936 GetKerningPairs 7937 7938 if I do it right, I can size it all here, and match 7939 what happens when I draw the full string with the OS functions. 7940 7941 subclasses might do the same thing while getting the glyphs on images 7942 struct GlyphInfo { 7943 int glyph; 7944 7945 size_t stringIdxStart; 7946 size_t stringIdxEnd; 7947 7948 Rectangle boundingBox; 7949 } 7950 GlyphInfo[] getCharBoxes() { 7951 // XftTextExtentsUtf8 7952 return null; 7953 7954 } 7955 +/ 7956 7957 ~this() { 7958 unload(); 7959 } 7960 } 7961 7962 version(Windows) 7963 private string sliceCString(const(wchar)[] w) { 7964 return makeUtf8StringFromWindowsString(cast(wchar*) w.ptr); 7965 } 7966 7967 private inout(char)[] sliceCString(inout(char)* s) { 7968 import core.stdc.string; 7969 auto len = strlen(s); 7970 return s[0 .. len]; 7971 } 7972 7973 /** 7974 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 7975 than constructing it directly. Then, it is reference counted so you can pass it 7976 at around and when the last ref goes out of scope, the buffered drawing activities 7977 are all carried out. 7978 7979 7980 Most functions use the outlineColor instead of taking a color themselves. 7981 ScreenPainter is reference counted and draws its buffer to the screen when its 7982 final reference goes out of scope. 7983 */ 7984 struct ScreenPainter { 7985 CapableOfBeingDrawnUpon window; 7986 this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle) { 7987 this.window = window; 7988 if(window.closed) 7989 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 7990 currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 7991 if(window.activeScreenPainter !is null) { 7992 impl = window.activeScreenPainter; 7993 if(impl.referenceCount == 0) { 7994 impl.window = window; 7995 impl.create(handle); 7996 } 7997 impl.referenceCount++; 7998 // writeln("refcount ++ ", impl.referenceCount); 7999 } else { 8000 impl = new ScreenPainterImplementation; 8001 impl.window = window; 8002 impl.create(handle); 8003 impl.referenceCount = 1; 8004 window.activeScreenPainter = impl; 8005 //import std.stdio; writeln("constructed"); 8006 } 8007 8008 copyActiveOriginals(); 8009 } 8010 8011 private Pen originalPen; 8012 private Color originalFillColor; 8013 private arsd.color.Rectangle originalClipRectangle; 8014 void copyActiveOriginals() { 8015 if(impl is null) return; 8016 originalPen = impl._activePen; 8017 originalFillColor = impl._fillColor; 8018 originalClipRectangle = impl._clipRectangle; 8019 } 8020 8021 ~this() { 8022 if(impl is null) return; 8023 impl.referenceCount--; 8024 //writeln("refcount -- ", impl.referenceCount); 8025 if(impl.referenceCount == 0) { 8026 //import std.stdio; writeln("destructed"); 8027 impl.dispose(); 8028 *window.activeScreenPainter = ScreenPainterImplementation.init; 8029 //import std.stdio; writeln("paint finished"); 8030 } else { 8031 // there is still an active reference, reset stuff so the 8032 // next user doesn't get weirdness via the reference 8033 this.rasterOp = RasterOp.normal; 8034 pen = originalPen; 8035 fillColor = originalFillColor; 8036 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 8037 } 8038 } 8039 8040 this(this) { 8041 if(impl is null) return; 8042 impl.referenceCount++; 8043 //writeln("refcount ++ ", impl.referenceCount); 8044 8045 copyActiveOriginals(); 8046 } 8047 8048 private int _originX; 8049 private int _originY; 8050 @property int originX() { return _originX; } 8051 @property int originY() { return _originY; } 8052 @property int originX(int a) { 8053 //currentClipRectangle.left += a - _originX; 8054 //currentClipRectangle.right += a - _originX; 8055 _originX = a; 8056 return _originX; 8057 } 8058 @property int originY(int a) { 8059 //currentClipRectangle.top += a - _originY; 8060 //currentClipRectangle.bottom += a - _originY; 8061 _originY = a; 8062 return _originY; 8063 } 8064 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 8065 private void transform(ref Point p) { 8066 if(impl is null) return; 8067 p.x += _originX; 8068 p.y += _originY; 8069 } 8070 8071 // this needs to be checked BEFORE the originX/Y transformation 8072 private bool isClipped(Point p) { 8073 return !currentClipRectangle.contains(p); 8074 } 8075 private bool isClipped(Point p, int width, int height) { 8076 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 8077 } 8078 private bool isClipped(Point p, Size s) { 8079 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 8080 } 8081 private bool isClipped(Point p, Point p2) { 8082 // need to ensure the end points are actually included inside, so the +1 does that 8083 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 8084 } 8085 8086 8087 /++ 8088 Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 8089 8090 Returns: 8091 The old clip rectangle. 8092 8093 History: 8094 Return value was `void` prior to May 10, 2021. 8095 8096 +/ 8097 arsd.color.Rectangle setClipRectangle(Point pt, int width, int height) { 8098 if(impl is null) return currentClipRectangle; 8099 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 8100 return currentClipRectangle; // no need to do anything 8101 auto old = currentClipRectangle; 8102 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 8103 transform(pt); 8104 8105 impl.setClipRectangle(pt.x, pt.y, width, height); 8106 8107 return old; 8108 } 8109 8110 /// ditto 8111 arsd.color.Rectangle setClipRectangle(arsd.color.Rectangle rect) { 8112 if(impl is null) return currentClipRectangle; 8113 return setClipRectangle(rect.upperLeft, rect.width, rect.height); 8114 } 8115 8116 /// 8117 void setFont(OperatingSystemFont font) { 8118 if(impl is null) return; 8119 impl.setFont(font); 8120 } 8121 8122 /// 8123 int fontHeight() { 8124 if(impl is null) return 0; 8125 return impl.fontHeight(); 8126 } 8127 8128 private Pen activePen; 8129 8130 /// 8131 @property void pen(Pen p) { 8132 if(impl is null) return; 8133 activePen = p; 8134 impl.pen(p); 8135 } 8136 8137 /// 8138 @scriptable 8139 @property void outlineColor(Color c) { 8140 if(impl is null) return; 8141 if(activePen.color == c) 8142 return; 8143 activePen.color = c; 8144 impl.pen(activePen); 8145 } 8146 8147 /// 8148 @scriptable 8149 @property void fillColor(Color c) { 8150 if(impl is null) return; 8151 impl.fillColor(c); 8152 } 8153 8154 /// 8155 @property void rasterOp(RasterOp op) { 8156 if(impl is null) return; 8157 impl.rasterOp(op); 8158 } 8159 8160 8161 void updateDisplay() { 8162 // FIXME this should do what the dtor does 8163 } 8164 8165 /// 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) 8166 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 8167 if(impl is null) return; 8168 if(isClipped(upperLeft, width, height)) return; 8169 transform(upperLeft); 8170 version(Windows) { 8171 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 8172 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 8173 RECT clip = scroll; 8174 RECT uncovered; 8175 HRGN hrgn; 8176 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 8177 throw new Exception("ScrollDC"); 8178 8179 } else version(X11) { 8180 // FIXME: clip stuff outside this rectangle 8181 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 8182 } else version(OSXCocoa) { 8183 throw new NotYetImplementedException(); 8184 } else static assert(0); 8185 } 8186 8187 /// 8188 void clear(Color color = Color.white()) { 8189 if(impl is null) return; 8190 fillColor = color; 8191 outlineColor = color; 8192 drawRectangle(Point(0, 0), window.width, window.height); 8193 } 8194 8195 /++ 8196 Draws a pixmap (represented by the [Sprite] class) on the drawable. 8197 8198 Params: 8199 upperLeft = point on the window where the upper left corner of the image will be drawn 8200 imageUpperLeft = point on the image to start the slice to draw 8201 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. 8202 History: 8203 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 8204 +/ 8205 version(OSXCocoa) {} else // NotYetImplementedException 8206 void drawPixmap(Sprite s, Point upperLeft, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 8207 if(impl is null) return; 8208 if(isClipped(upperLeft, s.width, s.height)) return; 8209 transform(upperLeft); 8210 impl.drawPixmap(s, upperLeft.x, upperLeft.y, imageUpperLeft.x, imageUpperLeft.y, sliceSize.width, sliceSize.height); 8211 } 8212 8213 /// 8214 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 8215 if(impl is null) return; 8216 //if(isClipped(upperLeft, w, h)) return; // FIXME 8217 transform(upperLeft); 8218 if(w == 0 || w > i.width) 8219 w = i.width; 8220 if(h == 0 || h > i.height) 8221 h = i.height; 8222 if(upperLeftOfImage.x < 0) 8223 upperLeftOfImage.x = 0; 8224 if(upperLeftOfImage.y < 0) 8225 upperLeftOfImage.y = 0; 8226 8227 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 8228 } 8229 8230 /// 8231 Size textSize(in char[] text) { 8232 if(impl is null) return Size(0, 0); 8233 return impl.textSize(text); 8234 } 8235 8236 /++ 8237 Draws a string in the window with the set font (see [setFont] to change it). 8238 8239 Params: 8240 upperLeft = the upper left point of the bounding box of the text 8241 text = the string to draw 8242 lowerRight = the lower right point of the bounding box of the text. If 0, 0, there is no lower right bound. 8243 alignment = A [arsd.docs.general_concepts#bitflags|combination] of [TextAlignment] flags 8244 +/ 8245 @scriptable 8246 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 8247 if(impl is null) return; 8248 if(lowerRight.x != 0 || lowerRight.y != 0) { 8249 if(isClipped(upperLeft, lowerRight)) return; 8250 transform(lowerRight); 8251 } else { 8252 if(isClipped(upperLeft, textSize(text))) return; 8253 } 8254 transform(upperLeft); 8255 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 8256 } 8257 8258 /++ 8259 Draws text using a custom font. 8260 8261 This is still MAJOR work in progress. 8262 8263 Creating a [DrawableFont] can be tricky and require additional dependencies. 8264 +/ 8265 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 8266 if(impl is null) return; 8267 if(isClipped(upperLeft, Point(int.max, int.max))) return; 8268 transform(upperLeft); 8269 font.drawString(this, upperLeft, text); 8270 } 8271 8272 version(Windows) 8273 void drawText(Point upperLeft, scope const(wchar)[] text) { 8274 if(impl is null) return; 8275 if(isClipped(upperLeft, Point(int.max, int.max))) return; 8276 transform(upperLeft); 8277 8278 if(text.length && text[$-1] == '\n') 8279 text = text[0 .. $-1]; // tailing newlines are weird on windows... 8280 8281 TextOutW(impl.hdc, upperLeft.x, upperLeft.y, text.ptr, cast(int) text.length); 8282 } 8283 8284 static struct TextDrawingContext { 8285 Point boundingBoxUpperLeft; 8286 Point boundingBoxLowerRight; 8287 8288 Point currentLocation; 8289 8290 Point lastDrewUpperLeft; 8291 Point lastDrewLowerRight; 8292 8293 // how do i do right aligned rich text? 8294 // i kinda want to do a pre-made drawing then right align 8295 // draw the whole block. 8296 // 8297 // That's exactly the diff: inline vs block stuff. 8298 8299 // I need to get coordinates of an inline section out too, 8300 // not just a bounding box, but a series of bounding boxes 8301 // should be ok. Consider what's needed to detect a click 8302 // on a link in the middle of a paragraph breaking a line. 8303 // 8304 // Generally, we should be able to get the rectangles of 8305 // any portion we draw. 8306 // 8307 // It also needs to tell what text is left if it overflows 8308 // out of the box, so we can do stuff like float images around 8309 // it. It should not attempt to draw a letter that would be 8310 // clipped. 8311 // 8312 // I might also turn off word wrap stuff. 8313 } 8314 8315 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 8316 if(impl is null) return; 8317 // FIXME 8318 } 8319 8320 /// Drawing an individual pixel is slow. Avoid it if possible. 8321 void drawPixel(Point where) { 8322 if(impl is null) return; 8323 if(isClipped(where)) return; 8324 transform(where); 8325 impl.drawPixel(where.x, where.y); 8326 } 8327 8328 8329 /// Draws a pen using the current pen / outlineColor 8330 @scriptable 8331 void drawLine(Point starting, Point ending) { 8332 if(impl is null) return; 8333 if(isClipped(starting, ending)) return; 8334 transform(starting); 8335 transform(ending); 8336 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 8337 } 8338 8339 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 8340 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 8341 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 8342 @scriptable 8343 void drawRectangle(Point upperLeft, int width, int height) { 8344 if(impl is null) return; 8345 if(isClipped(upperLeft, width, height)) return; 8346 transform(upperLeft); 8347 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 8348 } 8349 8350 /// ditto 8351 void drawRectangle(Point upperLeft, Size size) { 8352 if(impl is null) return; 8353 if(isClipped(upperLeft, size.width, size.height)) return; 8354 transform(upperLeft); 8355 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 8356 } 8357 8358 /// ditto 8359 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 8360 if(impl is null) return; 8361 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 8362 transform(upperLeft); 8363 transform(lowerRightInclusive); 8364 impl.drawRectangle(upperLeft.x, upperLeft.y, 8365 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 8366 } 8367 8368 // overload added on May 12, 2021 8369 /// ditto 8370 void drawRectangle(Rectangle rect) { 8371 drawRectangle(rect.upperLeft, rect.size); 8372 } 8373 8374 /// Arguments are the points of the bounding rectangle 8375 void drawEllipse(Point upperLeft, Point lowerRight) { 8376 if(impl is null) return; 8377 if(isClipped(upperLeft, lowerRight)) return; 8378 transform(upperLeft); 8379 transform(lowerRight); 8380 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 8381 } 8382 8383 /++ 8384 start and finish are units of degrees * 64 8385 +/ 8386 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 8387 if(impl is null) return; 8388 // FIXME: not actually implemented 8389 if(isClipped(upperLeft, width, height)) return; 8390 transform(upperLeft); 8391 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 8392 } 8393 8394 //this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 8395 void drawCircle(Point upperLeft, int diameter) { 8396 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 8397 } 8398 8399 /// . 8400 void drawPolygon(Point[] vertexes) { 8401 if(impl is null) return; 8402 assert(vertexes.length); 8403 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 8404 foreach(ref vertex; vertexes) { 8405 if(vertex.x < minX) 8406 minX = vertex.x; 8407 if(vertex.y < minY) 8408 minY = vertex.y; 8409 if(vertex.x > maxX) 8410 maxX = vertex.x; 8411 if(vertex.y > maxY) 8412 maxY = vertex.y; 8413 transform(vertex); 8414 } 8415 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 8416 impl.drawPolygon(vertexes); 8417 } 8418 8419 /// ditto 8420 void drawPolygon(Point[] vertexes...) { 8421 if(impl is null) return; 8422 drawPolygon(vertexes); 8423 } 8424 8425 8426 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 8427 8428 //mixin NativeScreenPainterImplementation!() impl; 8429 8430 8431 // HACK: if I mixin the impl directly, it won't let me override the copy 8432 // constructor! The linker complains about there being multiple definitions. 8433 // I'll make the best of it and reference count it though. 8434 ScreenPainterImplementation* impl; 8435 } 8436 8437 // HACK: I need a pointer to the implementation so it's separate 8438 struct ScreenPainterImplementation { 8439 CapableOfBeingDrawnUpon window; 8440 int referenceCount; 8441 mixin NativeScreenPainterImplementation!(); 8442 } 8443 8444 // FIXME: i haven't actually tested the sprite class on MS Windows 8445 8446 /** 8447 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 8448 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 8449 8450 8451 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 8452 though I'm not sure that's ideal and the implementation might change. 8453 8454 You create one by giving a window and an image. It optimizes for that window, 8455 and copies the image into it to use as the initial picture. Creating a sprite 8456 can be quite slow (especially over a network connection) so you should do it 8457 as little as possible and just hold on to your sprite handles after making them. 8458 simpledisplay does try to do its best though, using the XSHM extension if available, 8459 but you should still write your code as if it will always be slow. 8460 8461 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 8462 a fast operation - much faster than drawing the Image itself every time. 8463 8464 `Sprite` represents a scarce resource which should be freed when you 8465 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 8466 after it has been disposed. If you are unsure about this, don't take chances, 8467 just let the garbage collector do it for you. But ideally, you can manage its 8468 lifetime more efficiently. 8469 8470 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 8471 support alpha blending in its drawing at this time. That might change in the 8472 future, but if you need alpha blending right now, use OpenGL instead. See 8473 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 8474 8475 Update: on April 23, 2021, I finally added alpha blending support. You must opt 8476 in by setting the enableAlpha = true in the constructor. 8477 */ 8478 version(OSXCocoa) {} else // NotYetImplementedException 8479 class Sprite : CapableOfBeingDrawnUpon { 8480 8481 /// 8482 ScreenPainter draw() { 8483 return ScreenPainter(this, handle); 8484 } 8485 8486 /++ 8487 Copies the sprite's current state into a [TrueColorImage]. 8488 8489 Be warned: this can be a very slow operation 8490 8491 History: 8492 Actually implemented on March 14, 2021 8493 +/ 8494 TrueColorImage takeScreenshot() { 8495 return trueColorImageFromNativeHandle(handle, width, height); 8496 } 8497 8498 void delegate() paintingFinishedDg() { return null; } 8499 bool closed() { return false; } 8500 ScreenPainterImplementation* activeScreenPainter_; 8501 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 8502 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 8503 8504 version(Windows) 8505 private ubyte* rawData; 8506 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 8507 // ditto on the XPicture stuff 8508 8509 version(X11) { 8510 private static XRenderPictFormat* RGB24; 8511 private static XRenderPictFormat* ARGB32; 8512 8513 private Picture xrenderPicture; 8514 } 8515 8516 this(SimpleWindow win, int width, int height, bool enableAlpha = false) { 8517 this._width = width; 8518 this._height = height; 8519 this.enableAlpha = enableAlpha; 8520 8521 version(X11) { 8522 auto display = XDisplayConnection.get(); 8523 8524 if(enableAlpha) { 8525 if(!XRenderLibrary.loadAttempted) { 8526 XRenderLibrary.loadDynamicLibrary(); 8527 } 8528 8529 if(!XRenderLibrary.loadSuccessful) 8530 throw new Exception("XRender library load failure"); 8531 } 8532 8533 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); 8534 8535 if(enableAlpha) { 8536 if(RGB24 is null) 8537 RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); 8538 if(ARGB32 is null) 8539 ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); 8540 8541 XRenderPictureAttributes attrs; 8542 xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); 8543 } 8544 } else version(Windows) { 8545 version(CRuntime_DigitalMars) { 8546 //if(enableAlpha) 8547 //throw new Exception("Alpha support not available, try recompiling with -m32mscoff"); 8548 } 8549 8550 BITMAPINFO infoheader; 8551 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 8552 infoheader.bmiHeader.biWidth = width; 8553 infoheader.bmiHeader.biHeight = height; 8554 infoheader.bmiHeader.biPlanes = 1; 8555 infoheader.bmiHeader.biBitCount = enableAlpha ? 32 : 24; 8556 infoheader.bmiHeader.biCompression = BI_RGB; 8557 8558 // FIXME: this should prolly be a device dependent bitmap... 8559 handle = CreateDIBSection( 8560 null, 8561 &infoheader, 8562 DIB_RGB_COLORS, 8563 cast(void**) &rawData, 8564 null, 8565 0); 8566 8567 if(handle is null) 8568 throw new Exception("couldn't create pixmap"); 8569 } 8570 } 8571 8572 /// Makes a sprite based on the image with the initial contents from the Image 8573 this(SimpleWindow win, Image i) { 8574 this(win, i.width, i.height, i.enableAlpha); 8575 8576 version(X11) { 8577 auto display = XDisplayConnection.get(); 8578 auto gc = XCreateGC(display, this.handle, 0, null); 8579 scope(exit) XFreeGC(display, gc); 8580 if(i.usingXshm) 8581 XShmPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 8582 else 8583 XPutImage(display, cast(Drawable) handle, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 8584 } else version(Windows) { 8585 auto itemsPerLine = enableAlpha ? (4 * width) : (((cast(int) width * 3 + 3) / 4) * 4); 8586 auto arrLength = itemsPerLine * height; 8587 rawData[0..arrLength] = i.rawData[0..arrLength]; 8588 } else version(OSXCocoa) { 8589 // FIXME: I have no idea if this is even any good 8590 ubyte* rawData; 8591 8592 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 8593 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 8594 colorSpace, 8595 kCGImageAlphaPremultipliedLast 8596 |kCGBitmapByteOrder32Big); 8597 CGColorSpaceRelease(colorSpace); 8598 rawData = CGBitmapContextGetData(context); 8599 8600 auto rdl = (width * height * 4); 8601 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 8602 } else static assert(0); 8603 } 8604 8605 /++ 8606 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 8607 8608 Params: 8609 where = point on the window where the upper left corner of the image will be drawn 8610 imageUpperLeft = point on the image to start the slice to draw 8611 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. 8612 History: 8613 The `imageUpperLeft` and `sliceSize` parameters were added on March 11, 2021 (dub v9.3.0) 8614 +/ 8615 void drawAt(ScreenPainter painter, Point where, Point imageUpperLeft = Point(0, 0), Size sliceSize = Size(0, 0)) { 8616 painter.drawPixmap(this, where, imageUpperLeft, sliceSize); 8617 } 8618 8619 /// Call this when you're ready to get rid of it 8620 void dispose() { 8621 version(X11) { 8622 if(xrenderPicture) { 8623 XRenderFreePicture(XDisplayConnection.get, xrenderPicture); 8624 xrenderPicture = None; 8625 } 8626 if(handle) 8627 XFreePixmap(XDisplayConnection.get(), handle); 8628 handle = None; 8629 } else version(Windows) { 8630 if(handle) 8631 DeleteObject(handle); 8632 handle = null; 8633 } else version(OSXCocoa) { 8634 if(context) 8635 CGContextRelease(context); 8636 context = null; 8637 } else static assert(0); 8638 8639 } 8640 8641 ~this() { 8642 dispose(); 8643 } 8644 8645 /// 8646 final @property int width() { return _width; } 8647 8648 /// 8649 final @property int height() { return _height; } 8650 8651 /// 8652 static Sprite fromMemoryImage(SimpleWindow win, MemoryImage img, bool enableAlpha = false) { 8653 return new Sprite(win, Image.fromMemoryImage(img, enableAlpha)); 8654 } 8655 8656 private: 8657 8658 int _width; 8659 int _height; 8660 bool enableAlpha; 8661 version(X11) 8662 Pixmap handle; 8663 else version(Windows) 8664 HBITMAP handle; 8665 else version(OSXCocoa) 8666 CGContextRef context; 8667 else static assert(0); 8668 } 8669 8670 /++ 8671 NOT IMPLEMENTED 8672 8673 A display-stored image optimized for relatively quick drawing, like 8674 [Sprite], but this one supports alpha channel blending and does NOT 8675 support direct drawing upon it with a [ScreenPainter]. 8676 8677 You can think of it as an [arsd.game.OpenGlTexture] for usage with a 8678 plain [ScreenPainter]... sort of. 8679 8680 On X11, it requires the Xrender extension and library. This is available 8681 almost everywhere though. 8682 8683 History: 8684 Added November 14, 2020 but NOT ACTUALLY IMPLEMENTED 8685 +/ 8686 version(none) 8687 class AlphaSprite { 8688 /++ 8689 Copies the given image into it. 8690 +/ 8691 this(MemoryImage img) { 8692 8693 if(!XRenderLibrary.loadAttempted) { 8694 XRenderLibrary.loadDynamicLibrary(); 8695 8696 // FIXME: this needs to be reconstructed when the X server changes 8697 repopulateX(); 8698 } 8699 if(!XRenderLibrary.loadSuccessful) 8700 throw new Exception("XRender library load failure"); 8701 8702 // I probably need to put the alpha mask in a separate Picture 8703 // ugh 8704 // maybe the Sprite itself can have an alpha bitmask anyway 8705 8706 8707 auto display = XDisplayConnection.get(); 8708 pixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 8709 8710 8711 XRenderPictureAttributes attrs; 8712 8713 handle = XRenderCreatePicture( 8714 XDisplayConnection.get, 8715 pixmap, 8716 RGBA, 8717 0, 8718 &attrs 8719 ); 8720 8721 } 8722 8723 // maybe i'll use the create gradient functions too with static factories.. 8724 8725 void drawAt(ScreenPainter painter, Point where) { 8726 //painter.drawPixmap(this, where); 8727 8728 XRenderPictureAttributes attrs; 8729 8730 auto pic = XRenderCreatePicture( 8731 XDisplayConnection.get, 8732 painter.impl.d, 8733 RGB, 8734 0, 8735 &attrs 8736 ); 8737 8738 XRenderComposite( 8739 XDisplayConnection.get, 8740 3, // PictOpOver 8741 handle, 8742 None, 8743 pic, 8744 0, // src 8745 0, 8746 0, // mask 8747 0, 8748 10, // dest 8749 10, 8750 100, // width 8751 100 8752 ); 8753 8754 /+ 8755 XRenderFreePicture( 8756 XDisplayConnection.get, 8757 pic 8758 ); 8759 8760 XRenderFreePicture( 8761 XDisplayConnection.get, 8762 fill 8763 ); 8764 +/ 8765 // on Windows you can stretch but Xrender still can't :( 8766 } 8767 8768 static XRenderPictFormat* RGB; 8769 static XRenderPictFormat* RGBA; 8770 static void repopulateX() { 8771 auto display = XDisplayConnection.get; 8772 RGB = XRenderFindStandardFormat(display, PictStandardRGB24); 8773 RGBA = XRenderFindStandardFormat(display, PictStandardARGB32); 8774 } 8775 8776 XPixmap pixmap; 8777 Picture handle; 8778 } 8779 8780 /// 8781 interface CapableOfBeingDrawnUpon { 8782 /// 8783 ScreenPainter draw(); 8784 /// 8785 int width(); 8786 /// 8787 int height(); 8788 protected ScreenPainterImplementation* activeScreenPainter(); 8789 protected void activeScreenPainter(ScreenPainterImplementation*); 8790 bool closed(); 8791 8792 void delegate() paintingFinishedDg(); 8793 8794 /// Be warned: this can be a very slow operation 8795 TrueColorImage takeScreenshot(); 8796 } 8797 8798 /// Flushes any pending gui buffers. Necessary if you are using with_eventloop with X - flush after you create your windows but before you call loop() 8799 void flushGui() { 8800 version(X11) { 8801 auto dpy = XDisplayConnection.get(); 8802 XLockDisplay(dpy); 8803 scope(exit) XUnlockDisplay(dpy); 8804 XFlush(dpy); 8805 } 8806 } 8807 8808 /++ 8809 Runs the given code in the GUI thread when its event loop 8810 is available, blocking until it completes. This allows you 8811 to create and manipulate windows from another thread without 8812 invoking undefined behavior. 8813 8814 If this is the gui thread, it runs the code immediately. 8815 8816 If no gui thread exists yet, the current thread is assumed 8817 to be it. Attempting to create windows or run the event loop 8818 in any other thread will cause an assertion failure. 8819 8820 8821 $(TIP 8822 Did you know you can use UFCS on delegate literals? 8823 8824 () { 8825 // code here 8826 }.runInGuiThread; 8827 ) 8828 8829 Returns: 8830 `true` if the function was called, `false` if it was not. 8831 The function may not be called because the gui thread had 8832 already terminated by the time you called this. 8833 8834 History: 8835 Added April 10, 2020 (v7.2.0) 8836 8837 Return value added and implementation tweaked to avoid locking 8838 at program termination on February 24, 2021 (v9.2.1). 8839 +/ 8840 bool runInGuiThread(scope void delegate() dg) @trusted { 8841 claimGuiThread(); 8842 8843 if(thisIsGuiThread) { 8844 dg(); 8845 return true; 8846 } 8847 8848 if(guiThreadTerminating) 8849 return false; 8850 8851 import core.sync.semaphore; 8852 static Semaphore sc; 8853 if(sc is null) 8854 sc = new Semaphore(); 8855 8856 static RunQueueMember* rqm; 8857 if(rqm is null) 8858 rqm = new RunQueueMember; 8859 rqm.dg = cast(typeof(rqm.dg)) dg; 8860 rqm.signal = sc; 8861 rqm.thrown = null; 8862 8863 synchronized(runInGuiThreadLock) { 8864 runInGuiThreadQueue ~= rqm; 8865 } 8866 8867 if(!SimpleWindow.eventWakeUp()) 8868 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 8869 8870 rqm.signal.wait(); 8871 auto t = rqm.thrown; 8872 8873 if(t) 8874 throw t; 8875 8876 return true; 8877 } 8878 8879 private void runPendingRunInGuiThreadDelegates() { 8880 more: 8881 RunQueueMember* next; 8882 synchronized(runInGuiThreadLock) { 8883 if(runInGuiThreadQueue.length) { 8884 next = runInGuiThreadQueue[0]; 8885 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 8886 } else { 8887 next = null; 8888 } 8889 } 8890 8891 if(next) { 8892 try { 8893 next.dg(); 8894 next.thrown = null; 8895 } catch(Throwable t) { 8896 next.thrown = t; 8897 } 8898 8899 next.signal.notify(); 8900 8901 goto more; 8902 } 8903 } 8904 8905 private void claimGuiThread() { 8906 import core.atomic; 8907 if(cas(&guiThreadExists, false, true)) 8908 thisIsGuiThread = true; 8909 } 8910 8911 private struct RunQueueMember { 8912 void delegate() dg; 8913 import core.sync.semaphore; 8914 Semaphore signal; 8915 Throwable thrown; 8916 } 8917 8918 private __gshared RunQueueMember*[] runInGuiThreadQueue; 8919 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 8920 private bool thisIsGuiThread = false; 8921 private shared bool guiThreadExists = false; 8922 private shared bool guiThreadTerminating = false; 8923 8924 private void guiThreadFinalize() { 8925 assert(thisIsGuiThread); 8926 8927 guiThreadTerminating = true; // don't add any more from this point on 8928 runPendingRunInGuiThreadDelegates(); 8929 } 8930 8931 /+ 8932 interface IPromise { 8933 void reportProgress(int current, int max, string message); 8934 8935 /+ // not formally in cuz of templates but still 8936 IPromise Then(); 8937 IPromise Catch(); 8938 IPromise Finally(); 8939 +/ 8940 } 8941 8942 /+ 8943 auto promise = async({ ... }); 8944 promise.Then(whatever). 8945 Then(whateverelse). 8946 Catch((exception) { }); 8947 8948 8949 A promise is run inside a fiber and it looks something like: 8950 8951 try { 8952 auto res = whatever(); 8953 auto res2 = whateverelse(res); 8954 } catch(Exception e) { 8955 { }(e); 8956 } 8957 8958 When a thing succeeds, it is passed as an arg to the next 8959 +/ 8960 class Promise(T) : IPromise { 8961 auto Then() { return null; } 8962 auto Catch() { return null; } 8963 auto Finally() { return null; } 8964 8965 // wait for it to resolve and return the value, or rethrow the error if that occurred. 8966 // cannot be called from the gui thread, but this is caught at runtime instead of compile time. 8967 T await(); 8968 } 8969 8970 interface Task { 8971 } 8972 8973 interface Resolvable(T) : Task { 8974 void run(); 8975 8976 void resolve(T); 8977 8978 Resolvable!T then(void delegate(T)); // returns a new promise 8979 Resolvable!T error(Throwable); // js catch 8980 Resolvable!T completed(); // js finally 8981 8982 } 8983 8984 /++ 8985 Runs `work` in a helper thread and sends its return value back to the main gui 8986 thread as the argument to `uponCompletion`. If `work` throws, the exception is 8987 sent to the `uponThrown` if given, or if null, rethrown from the event loop to 8988 kill the program. 8989 8990 You can call reportProgress(position, max, message) to update your parent window 8991 on your progress. 8992 8993 I should also use `shared` methods. FIXME 8994 8995 History: 8996 Added March 6, 2021 (dub version 9.3). 8997 +/ 8998 void runInWorkerThread(T)(T delegate(Task) work, void delegate(T) uponCompletion) { 8999 uponCompletion(work(null)); 9000 } 9001 9002 +/ 9003 9004 /// Used internal to dispatch events to various classes. 9005 interface CapableOfHandlingNativeEvent { 9006 NativeEventHandler getNativeEventHandler(); 9007 9008 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 9009 9010 version(X11) { 9011 // if this is impossible, you are allowed to just throw from it 9012 // Note: if you call it from another object, set a flag cuz the manger will call you again 9013 void recreateAfterDisconnect(); 9014 // discard any *connection specific* state, but keep enough that you 9015 // can be recreated if possible. discardConnectionState() is always called immediately 9016 // before recreateAfterDisconnect(), so you can set a flag there to decide if 9017 // you need initialization order 9018 void discardConnectionState(); 9019 } 9020 } 9021 9022 version(X11) 9023 /++ 9024 State of keys on mouse events, especially motion. 9025 9026 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 9027 +/ 9028 enum ModifierState : uint { 9029 shift = 1, /// 9030 capsLock = 2, /// 9031 ctrl = 4, /// 9032 alt = 8, /// Not always available on Windows 9033 windows = 64, /// ditto 9034 numLock = 16, /// 9035 9036 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 9037 middleButtonDown = 512, /// ditto 9038 rightButtonDown = 1024, /// ditto 9039 } 9040 else version(Windows) 9041 /// ditto 9042 enum ModifierState : uint { 9043 shift = 4, /// 9044 ctrl = 8, /// 9045 9046 // i'm not sure if the next two are available 9047 alt = 256, /// not always available on Windows 9048 windows = 512, /// ditto 9049 9050 capsLock = 1024, /// 9051 numLock = 2048, /// 9052 9053 leftButtonDown = 1, /// not available on key events 9054 middleButtonDown = 16, /// ditto 9055 rightButtonDown = 2, /// ditto 9056 9057 backButtonDown = 0x20, /// not available on X 9058 forwardButtonDown = 0x40, /// ditto 9059 } 9060 else version(OSXCocoa) 9061 // FIXME FIXME NotYetImplementedException 9062 enum ModifierState : uint { 9063 shift = 1, /// 9064 capsLock = 2, /// 9065 ctrl = 4, /// 9066 alt = 8, /// Not always available on Windows 9067 windows = 64, /// ditto 9068 numLock = 16, /// 9069 9070 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 9071 middleButtonDown = 512, /// ditto 9072 rightButtonDown = 1024, /// ditto 9073 } 9074 9075 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them 9076 enum MouseButton : int { 9077 none = 0, 9078 left = 1, /// 9079 right = 2, /// 9080 middle = 4, /// 9081 wheelUp = 8, /// 9082 wheelDown = 16, /// 9083 backButton = 32, /// often found on the thumb and used for back in browsers 9084 forwardButton = 64, /// often found on the thumb and used for forward in browsers 9085 } 9086 9087 version(X11) { 9088 // FIXME: match ASCII whenever we can. Most of it is already there, 9089 // but there's a few exceptions and mismatches with Windows 9090 9091 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 9092 enum Key { 9093 Escape = 0xff1b, /// 9094 F1 = 0xffbe, /// 9095 F2 = 0xffbf, /// 9096 F3 = 0xffc0, /// 9097 F4 = 0xffc1, /// 9098 F5 = 0xffc2, /// 9099 F6 = 0xffc3, /// 9100 F7 = 0xffc4, /// 9101 F8 = 0xffc5, /// 9102 F9 = 0xffc6, /// 9103 F10 = 0xffc7, /// 9104 F11 = 0xffc8, /// 9105 F12 = 0xffc9, /// 9106 PrintScreen = 0xff61, /// 9107 ScrollLock = 0xff14, /// 9108 Pause = 0xff13, /// 9109 Grave = 0x60, /// The $(BACKTICK) ~ key 9110 // number keys across the top of the keyboard 9111 N1 = 0x31, /// Number key atop the keyboard 9112 N2 = 0x32, /// 9113 N3 = 0x33, /// 9114 N4 = 0x34, /// 9115 N5 = 0x35, /// 9116 N6 = 0x36, /// 9117 N7 = 0x37, /// 9118 N8 = 0x38, /// 9119 N9 = 0x39, /// 9120 N0 = 0x30, /// 9121 Dash = 0x2d, /// 9122 Equals = 0x3d, /// 9123 Backslash = 0x5c, /// The \ | key 9124 Backspace = 0xff08, /// 9125 Insert = 0xff63, /// 9126 Home = 0xff50, /// 9127 PageUp = 0xff55, /// 9128 Delete = 0xffff, /// 9129 End = 0xff57, /// 9130 PageDown = 0xff56, /// 9131 Up = 0xff52, /// 9132 Down = 0xff54, /// 9133 Left = 0xff51, /// 9134 Right = 0xff53, /// 9135 9136 Tab = 0xff09, /// 9137 Q = 0x71, /// 9138 W = 0x77, /// 9139 E = 0x65, /// 9140 R = 0x72, /// 9141 T = 0x74, /// 9142 Y = 0x79, /// 9143 U = 0x75, /// 9144 I = 0x69, /// 9145 O = 0x6f, /// 9146 P = 0x70, /// 9147 LeftBracket = 0x5b, /// the [ { key 9148 RightBracket = 0x5d, /// the ] } key 9149 CapsLock = 0xffe5, /// 9150 A = 0x61, /// 9151 S = 0x73, /// 9152 D = 0x64, /// 9153 F = 0x66, /// 9154 G = 0x67, /// 9155 H = 0x68, /// 9156 J = 0x6a, /// 9157 K = 0x6b, /// 9158 L = 0x6c, /// 9159 Semicolon = 0x3b, /// 9160 Apostrophe = 0x27, /// 9161 Enter = 0xff0d, /// 9162 Shift = 0xffe1, /// 9163 Z = 0x7a, /// 9164 X = 0x78, /// 9165 C = 0x63, /// 9166 V = 0x76, /// 9167 B = 0x62, /// 9168 N = 0x6e, /// 9169 M = 0x6d, /// 9170 Comma = 0x2c, /// 9171 Period = 0x2e, /// 9172 Slash = 0x2f, /// the / ? key 9173 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 9174 Ctrl = 0xffe3, /// 9175 Windows = 0xffeb, /// 9176 Alt = 0xffe9, /// 9177 Space = 0x20, /// 9178 Alt_r = 0xffea, /// ditto of shift_r 9179 Windows_r = 0xffec, /// 9180 Menu = 0xff67, /// 9181 Ctrl_r = 0xffe4, /// 9182 9183 NumLock = 0xff7f, /// 9184 Divide = 0xffaf, /// The / key on the number pad 9185 Multiply = 0xffaa, /// The * key on the number pad 9186 Minus = 0xffad, /// The - key on the number pad 9187 Plus = 0xffab, /// The + key on the number pad 9188 PadEnter = 0xff8d, /// Numberpad enter key 9189 Pad1 = 0xff9c, /// Numberpad keys 9190 Pad2 = 0xff99, /// 9191 Pad3 = 0xff9b, /// 9192 Pad4 = 0xff96, /// 9193 Pad5 = 0xff9d, /// 9194 Pad6 = 0xff98, /// 9195 Pad7 = 0xff95, /// 9196 Pad8 = 0xff97, /// 9197 Pad9 = 0xff9a, /// 9198 Pad0 = 0xff9e, /// 9199 PadDot = 0xff9f, /// 9200 } 9201 } else version(Windows) { 9202 // the character here is for en-us layouts and for illustration only 9203 // if you actually want to get characters, wait for character events 9204 // (the argument to your event handler is simply a dchar) 9205 // those will be converted by the OS for the right locale. 9206 9207 enum Key { 9208 Escape = 0x1b, 9209 F1 = 0x70, 9210 F2 = 0x71, 9211 F3 = 0x72, 9212 F4 = 0x73, 9213 F5 = 0x74, 9214 F6 = 0x75, 9215 F7 = 0x76, 9216 F8 = 0x77, 9217 F9 = 0x78, 9218 F10 = 0x79, 9219 F11 = 0x7a, 9220 F12 = 0x7b, 9221 PrintScreen = 0x2c, 9222 ScrollLock = 0x91, 9223 Pause = 0x13, 9224 Grave = 0xc0, 9225 // number keys across the top of the keyboard 9226 N1 = 0x31, 9227 N2 = 0x32, 9228 N3 = 0x33, 9229 N4 = 0x34, 9230 N5 = 0x35, 9231 N6 = 0x36, 9232 N7 = 0x37, 9233 N8 = 0x38, 9234 N9 = 0x39, 9235 N0 = 0x30, 9236 Dash = 0xbd, 9237 Equals = 0xbb, 9238 Backslash = 0xdc, 9239 Backspace = 0x08, 9240 Insert = 0x2d, 9241 Home = 0x24, 9242 PageUp = 0x21, 9243 Delete = 0x2e, 9244 End = 0x23, 9245 PageDown = 0x22, 9246 Up = 0x26, 9247 Down = 0x28, 9248 Left = 0x25, 9249 Right = 0x27, 9250 9251 Tab = 0x09, 9252 Q = 0x51, 9253 W = 0x57, 9254 E = 0x45, 9255 R = 0x52, 9256 T = 0x54, 9257 Y = 0x59, 9258 U = 0x55, 9259 I = 0x49, 9260 O = 0x4f, 9261 P = 0x50, 9262 LeftBracket = 0xdb, 9263 RightBracket = 0xdd, 9264 CapsLock = 0x14, 9265 A = 0x41, 9266 S = 0x53, 9267 D = 0x44, 9268 F = 0x46, 9269 G = 0x47, 9270 H = 0x48, 9271 J = 0x4a, 9272 K = 0x4b, 9273 L = 0x4c, 9274 Semicolon = 0xba, 9275 Apostrophe = 0xde, 9276 Enter = 0x0d, 9277 Shift = 0x10, 9278 Z = 0x5a, 9279 X = 0x58, 9280 C = 0x43, 9281 V = 0x56, 9282 B = 0x42, 9283 N = 0x4e, 9284 M = 0x4d, 9285 Comma = 0xbc, 9286 Period = 0xbe, 9287 Slash = 0xbf, 9288 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 9289 Ctrl = 0x11, 9290 Windows = 0x5b, 9291 Alt = -5, // FIXME 9292 Space = 0x20, 9293 Alt_r = 0xffea, // ditto of shift_r 9294 Windows_r = 0x5c, // ditto of shift_r 9295 Menu = 0x5d, 9296 Ctrl_r = 0xa3, // ditto of shift_r 9297 9298 NumLock = 0x90, 9299 Divide = 0x6f, 9300 Multiply = 0x6a, 9301 Minus = 0x6d, 9302 Plus = 0x6b, 9303 PadEnter = -8, // FIXME 9304 Pad1 = 0x61, 9305 Pad2 = 0x62, 9306 Pad3 = 0x63, 9307 Pad4 = 0x64, 9308 Pad5 = 0x65, 9309 Pad6 = 0x66, 9310 Pad7 = 0x67, 9311 Pad8 = 0x68, 9312 Pad9 = 0x69, 9313 Pad0 = 0x60, 9314 PadDot = 0x6e, 9315 } 9316 9317 // I'm keeping this around for reference purposes 9318 // ideally all these buttons will be listed for all platforms, 9319 // but now now I'm just focusing on my US keyboard 9320 version(none) 9321 enum Key { 9322 LBUTTON = 0x01, 9323 RBUTTON = 0x02, 9324 CANCEL = 0x03, 9325 MBUTTON = 0x04, 9326 //static if (_WIN32_WINNT > = 0x500) { 9327 XBUTTON1 = 0x05, 9328 XBUTTON2 = 0x06, 9329 //} 9330 BACK = 0x08, 9331 TAB = 0x09, 9332 CLEAR = 0x0C, 9333 RETURN = 0x0D, 9334 SHIFT = 0x10, 9335 CONTROL = 0x11, 9336 MENU = 0x12, 9337 PAUSE = 0x13, 9338 CAPITAL = 0x14, 9339 KANA = 0x15, 9340 HANGEUL = 0x15, 9341 HANGUL = 0x15, 9342 JUNJA = 0x17, 9343 FINAL = 0x18, 9344 HANJA = 0x19, 9345 KANJI = 0x19, 9346 ESCAPE = 0x1B, 9347 CONVERT = 0x1C, 9348 NONCONVERT = 0x1D, 9349 ACCEPT = 0x1E, 9350 MODECHANGE = 0x1F, 9351 SPACE = 0x20, 9352 PRIOR = 0x21, 9353 NEXT = 0x22, 9354 END = 0x23, 9355 HOME = 0x24, 9356 LEFT = 0x25, 9357 UP = 0x26, 9358 RIGHT = 0x27, 9359 DOWN = 0x28, 9360 SELECT = 0x29, 9361 PRINT = 0x2A, 9362 EXECUTE = 0x2B, 9363 SNAPSHOT = 0x2C, 9364 INSERT = 0x2D, 9365 DELETE = 0x2E, 9366 HELP = 0x2F, 9367 LWIN = 0x5B, 9368 RWIN = 0x5C, 9369 APPS = 0x5D, 9370 SLEEP = 0x5F, 9371 NUMPAD0 = 0x60, 9372 NUMPAD1 = 0x61, 9373 NUMPAD2 = 0x62, 9374 NUMPAD3 = 0x63, 9375 NUMPAD4 = 0x64, 9376 NUMPAD5 = 0x65, 9377 NUMPAD6 = 0x66, 9378 NUMPAD7 = 0x67, 9379 NUMPAD8 = 0x68, 9380 NUMPAD9 = 0x69, 9381 MULTIPLY = 0x6A, 9382 ADD = 0x6B, 9383 SEPARATOR = 0x6C, 9384 SUBTRACT = 0x6D, 9385 DECIMAL = 0x6E, 9386 DIVIDE = 0x6F, 9387 F1 = 0x70, 9388 F2 = 0x71, 9389 F3 = 0x72, 9390 F4 = 0x73, 9391 F5 = 0x74, 9392 F6 = 0x75, 9393 F7 = 0x76, 9394 F8 = 0x77, 9395 F9 = 0x78, 9396 F10 = 0x79, 9397 F11 = 0x7A, 9398 F12 = 0x7B, 9399 F13 = 0x7C, 9400 F14 = 0x7D, 9401 F15 = 0x7E, 9402 F16 = 0x7F, 9403 F17 = 0x80, 9404 F18 = 0x81, 9405 F19 = 0x82, 9406 F20 = 0x83, 9407 F21 = 0x84, 9408 F22 = 0x85, 9409 F23 = 0x86, 9410 F24 = 0x87, 9411 NUMLOCK = 0x90, 9412 SCROLL = 0x91, 9413 LSHIFT = 0xA0, 9414 RSHIFT = 0xA1, 9415 LCONTROL = 0xA2, 9416 RCONTROL = 0xA3, 9417 LMENU = 0xA4, 9418 RMENU = 0xA5, 9419 //static if (_WIN32_WINNT > = 0x500) { 9420 BROWSER_BACK = 0xA6, 9421 BROWSER_FORWARD = 0xA7, 9422 BROWSER_REFRESH = 0xA8, 9423 BROWSER_STOP = 0xA9, 9424 BROWSER_SEARCH = 0xAA, 9425 BROWSER_FAVORITES = 0xAB, 9426 BROWSER_HOME = 0xAC, 9427 VOLUME_MUTE = 0xAD, 9428 VOLUME_DOWN = 0xAE, 9429 VOLUME_UP = 0xAF, 9430 MEDIA_NEXT_TRACK = 0xB0, 9431 MEDIA_PREV_TRACK = 0xB1, 9432 MEDIA_STOP = 0xB2, 9433 MEDIA_PLAY_PAUSE = 0xB3, 9434 LAUNCH_MAIL = 0xB4, 9435 LAUNCH_MEDIA_SELECT = 0xB5, 9436 LAUNCH_APP1 = 0xB6, 9437 LAUNCH_APP2 = 0xB7, 9438 //} 9439 OEM_1 = 0xBA, 9440 //static if (_WIN32_WINNT > = 0x500) { 9441 OEM_PLUS = 0xBB, 9442 OEM_COMMA = 0xBC, 9443 OEM_MINUS = 0xBD, 9444 OEM_PERIOD = 0xBE, 9445 //} 9446 OEM_2 = 0xBF, 9447 OEM_3 = 0xC0, 9448 OEM_4 = 0xDB, 9449 OEM_5 = 0xDC, 9450 OEM_6 = 0xDD, 9451 OEM_7 = 0xDE, 9452 OEM_8 = 0xDF, 9453 //static if (_WIN32_WINNT > = 0x500) { 9454 OEM_102 = 0xE2, 9455 //} 9456 PROCESSKEY = 0xE5, 9457 //static if (_WIN32_WINNT > = 0x500) { 9458 PACKET = 0xE7, 9459 //} 9460 ATTN = 0xF6, 9461 CRSEL = 0xF7, 9462 EXSEL = 0xF8, 9463 EREOF = 0xF9, 9464 PLAY = 0xFA, 9465 ZOOM = 0xFB, 9466 NONAME = 0xFC, 9467 PA1 = 0xFD, 9468 OEM_CLEAR = 0xFE, 9469 } 9470 9471 } else version(OSXCocoa) { 9472 // FIXME 9473 enum Key { 9474 Escape = 0x1b, 9475 F1 = 0x70, 9476 F2 = 0x71, 9477 F3 = 0x72, 9478 F4 = 0x73, 9479 F5 = 0x74, 9480 F6 = 0x75, 9481 F7 = 0x76, 9482 F8 = 0x77, 9483 F9 = 0x78, 9484 F10 = 0x79, 9485 F11 = 0x7a, 9486 F12 = 0x7b, 9487 PrintScreen = 0x2c, 9488 ScrollLock = -2, // FIXME 9489 Pause = -3, // FIXME 9490 Grave = 0xc0, 9491 // number keys across the top of the keyboard 9492 N1 = 0x31, 9493 N2 = 0x32, 9494 N3 = 0x33, 9495 N4 = 0x34, 9496 N5 = 0x35, 9497 N6 = 0x36, 9498 N7 = 0x37, 9499 N8 = 0x38, 9500 N9 = 0x39, 9501 N0 = 0x30, 9502 Dash = 0xbd, 9503 Equals = 0xbb, 9504 Backslash = 0xdc, 9505 Backspace = 0x08, 9506 Insert = 0x2d, 9507 Home = 0x24, 9508 PageUp = 0x21, 9509 Delete = 0x2e, 9510 End = 0x23, 9511 PageDown = 0x22, 9512 Up = 0x26, 9513 Down = 0x28, 9514 Left = 0x25, 9515 Right = 0x27, 9516 9517 Tab = 0x09, 9518 Q = 0x51, 9519 W = 0x57, 9520 E = 0x45, 9521 R = 0x52, 9522 T = 0x54, 9523 Y = 0x59, 9524 U = 0x55, 9525 I = 0x49, 9526 O = 0x4f, 9527 P = 0x50, 9528 LeftBracket = 0xdb, 9529 RightBracket = 0xdd, 9530 CapsLock = 0x14, 9531 A = 0x41, 9532 S = 0x53, 9533 D = 0x44, 9534 F = 0x46, 9535 G = 0x47, 9536 H = 0x48, 9537 J = 0x4a, 9538 K = 0x4b, 9539 L = 0x4c, 9540 Semicolon = 0xba, 9541 Apostrophe = 0xde, 9542 Enter = 0x0d, 9543 Shift = 0x10, 9544 Z = 0x5a, 9545 X = 0x58, 9546 C = 0x43, 9547 V = 0x56, 9548 B = 0x42, 9549 N = 0x4e, 9550 M = 0x4d, 9551 Comma = 0xbc, 9552 Period = 0xbe, 9553 Slash = 0xbf, 9554 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 9555 Ctrl = 0x11, 9556 Windows = 0x5b, 9557 Alt = -5, // FIXME 9558 Space = 0x20, 9559 Alt_r = 0xffea, // ditto of shift_r 9560 Windows_r = -6, // FIXME 9561 Menu = 0x5d, 9562 Ctrl_r = -7, // FIXME 9563 9564 NumLock = 0x90, 9565 Divide = 0x6f, 9566 Multiply = 0x6a, 9567 Minus = 0x6d, 9568 Plus = 0x6b, 9569 PadEnter = -8, // FIXME 9570 // FIXME for the rest of these: 9571 Pad1 = 0xff9c, 9572 Pad2 = 0xff99, 9573 Pad3 = 0xff9b, 9574 Pad4 = 0xff96, 9575 Pad5 = 0xff9d, 9576 Pad6 = 0xff98, 9577 Pad7 = 0xff95, 9578 Pad8 = 0xff97, 9579 Pad9 = 0xff9a, 9580 Pad0 = 0xff9e, 9581 PadDot = 0xff9f, 9582 } 9583 9584 } 9585 9586 /* Additional utilities */ 9587 9588 9589 Color fromHsl(real h, real s, real l) { 9590 return arsd.color.fromHsl([h,s,l]); 9591 } 9592 9593 9594 9595 /* ********** What follows is the system-specific implementations *********/ 9596 version(Windows) { 9597 9598 9599 // helpers for making HICONs from MemoryImages 9600 class WindowsIcon { 9601 struct Win32Icon(int colorCount) { 9602 align(1): 9603 uint biSize; 9604 int biWidth; 9605 int biHeight; 9606 ushort biPlanes; 9607 ushort biBitCount; 9608 uint biCompression; 9609 uint biSizeImage; 9610 int biXPelsPerMeter; 9611 int biYPelsPerMeter; 9612 uint biClrUsed; 9613 uint biClrImportant; 9614 RGBQUAD[colorCount] biColors; 9615 /* Pixels: 9616 Uint8 pixels[] 9617 */ 9618 /* Mask: 9619 Uint8 mask[] 9620 */ 9621 9622 ubyte[4096] data; 9623 9624 void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 9625 width = mi.width; 9626 height = mi.height; 9627 9628 auto indexedImage = cast(IndexedImage) mi; 9629 if(indexedImage is null) 9630 indexedImage = quantize(mi.getAsTrueColorImage()); 9631 9632 assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy 9633 assert(height %4 == 0); 9634 9635 int icon_plen = height*((width+3)&~3); 9636 int icon_mlen = height*((((width+7)/8)+3)&~3); 9637 icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 9638 9639 biSize = 40; 9640 biWidth = width; 9641 biHeight = height*2; 9642 biPlanes = 1; 9643 biBitCount = 8; 9644 biSizeImage = icon_plen+icon_mlen; 9645 9646 int offset = 0; 9647 int andOff = icon_plen * 8; // the and offset is in bits 9648 for(int y = height - 1; y >= 0; y--) { 9649 int off2 = y * width; 9650 foreach(x; 0 .. width) { 9651 const b = indexedImage.data[off2 + x]; 9652 data[offset] = b; 9653 offset++; 9654 9655 const andBit = andOff % 8; 9656 const andIdx = andOff / 8; 9657 assert(b < indexedImage.palette.length); 9658 // this is anded to the destination, since and 0 means erase, 9659 // we want that to be opaque, and 1 for transparent 9660 auto transparent = (indexedImage.palette[b].a <= 127); 9661 data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0); 9662 9663 andOff++; 9664 } 9665 9666 andOff += andOff % 32; 9667 } 9668 9669 foreach(idx, entry; indexedImage.palette) { 9670 if(entry.a > 127) { 9671 biColors[idx].rgbBlue = entry.b; 9672 biColors[idx].rgbGreen = entry.g; 9673 biColors[idx].rgbRed = entry.r; 9674 } else { 9675 biColors[idx].rgbBlue = 255; 9676 biColors[idx].rgbGreen = 255; 9677 biColors[idx].rgbRed = 255; 9678 } 9679 } 9680 9681 /* 9682 data[0..icon_plen] = getFlippedUnfilteredDatastream(png); 9683 data[icon_plen..icon_plen+icon_mlen] = getANDMask(png); 9684 //icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0); 9685 auto pngMap = fetchPaletteWin32(png); 9686 biColors[0..pngMap.length] = pngMap[]; 9687 */ 9688 } 9689 } 9690 9691 9692 Win32Icon!(256) icon_win32; 9693 9694 9695 this(MemoryImage mi) { 9696 int icon_len, width, height; 9697 9698 icon_win32.fromMemoryImage(mi, icon_len, width, height); 9699 9700 /* 9701 PNG* png = readPnpngData); 9702 PNGHeader pngh = getHeader(png); 9703 void* icon_win32; 9704 if(pngh.depth == 4) { 9705 auto i = new Win32Icon!(16); 9706 i.fromPNG(png, pngh, icon_len, width, height); 9707 icon_win32 = i; 9708 } 9709 else if(pngh.depth == 8) { 9710 auto i = new Win32Icon!(256); 9711 i.fromPNG(png, pngh, icon_len, width, height); 9712 icon_win32 = i; 9713 } else assert(0); 9714 */ 9715 9716 hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0); 9717 9718 if(hIcon is null) throw new Exception("CreateIconFromResourceEx"); 9719 } 9720 9721 ~this() { 9722 DestroyIcon(hIcon); 9723 } 9724 9725 HICON hIcon; 9726 } 9727 9728 9729 9730 9731 9732 9733 alias int delegate(HWND, UINT, WPARAM, LPARAM) NativeEventHandler; 9734 alias HWND NativeWindowHandle; 9735 9736 extern(Windows) 9737 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 9738 try { 9739 if(SimpleWindow.handleNativeGlobalEvent !is null) { 9740 // it returns zero if the message is handled, so we won't do anything more there 9741 // do I like that though? 9742 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam); 9743 if(ret == 0) 9744 return ret; 9745 } 9746 9747 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 9748 if(window.getNativeEventHandler !is null) { 9749 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam); 9750 if(ret == 0) 9751 return ret; 9752 } 9753 if(auto w = cast(SimpleWindow) (*window)) 9754 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 9755 else 9756 return DefWindowProc(hWnd, iMessage, wParam, lParam); 9757 } else { 9758 return DefWindowProc(hWnd, iMessage, wParam, lParam); 9759 } 9760 } catch (Exception e) { 9761 try { 9762 sdpy_abort(e); 9763 return 0; 9764 } catch(Exception e) { assert(0); } 9765 } 9766 } 9767 9768 void sdpy_abort(Throwable e) nothrow { 9769 try 9770 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 9771 catch(Exception e) 9772 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 9773 ExitProcess(1); 9774 } 9775 9776 mixin template NativeScreenPainterImplementation() { 9777 HDC hdc; 9778 HWND hwnd; 9779 //HDC windowHdc; 9780 HBITMAP oldBmp; 9781 9782 void create(NativeWindowHandle window) { 9783 hwnd = window; 9784 9785 if(auto sw = cast(SimpleWindow) this.window) { 9786 // drawing on a window, double buffer 9787 auto windowHdc = GetDC(hwnd); 9788 9789 auto buffer = sw.impl.buffer; 9790 if(buffer is null) { 9791 hdc = windowHdc; 9792 windowDc = true; 9793 } else { 9794 hdc = CreateCompatibleDC(windowHdc); 9795 9796 ReleaseDC(hwnd, windowHdc); 9797 9798 oldBmp = SelectObject(hdc, buffer); 9799 } 9800 } else { 9801 // drawing on something else, draw directly 9802 hdc = CreateCompatibleDC(null); 9803 SelectObject(hdc, window); 9804 9805 } 9806 9807 // X doesn't draw a text background, so neither should we 9808 SetBkMode(hdc, TRANSPARENT); 9809 9810 ensureDefaultFontLoaded(); 9811 9812 if(defaultGuiFont) { 9813 SelectObject(hdc, defaultGuiFont); 9814 // DeleteObject(defaultGuiFont); 9815 } 9816 } 9817 9818 static HFONT defaultGuiFont; 9819 static void ensureDefaultFontLoaded() { 9820 static bool triedDefaultGuiFont = false; 9821 if(!triedDefaultGuiFont) { 9822 NONCLIENTMETRICS params; 9823 params.cbSize = params.sizeof; 9824 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 9825 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 9826 } 9827 triedDefaultGuiFont = true; 9828 } 9829 } 9830 9831 void setFont(OperatingSystemFont font) { 9832 if(font && font.font) { 9833 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 9834 // error... how to handle tho? 9835 } 9836 } 9837 else if(defaultGuiFont) 9838 SelectObject(hdc, defaultGuiFont); 9839 } 9840 9841 arsd.color.Rectangle _clipRectangle; 9842 9843 void setClipRectangle(int x, int y, int width, int height) { 9844 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 9845 9846 if(width == 0 || height == 0) { 9847 SelectClipRgn(hdc, null); 9848 } else { 9849 auto region = CreateRectRgn(x, y, x + width, y + height); 9850 SelectClipRgn(hdc, region); 9851 DeleteObject(region); 9852 } 9853 } 9854 9855 9856 // just because we can on Windows... 9857 //void create(Image image); 9858 9859 void dispose() { 9860 // FIXME: this.window.width/height is probably wrong 9861 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 9862 // ReleaseDC(hwnd, windowHdc); 9863 9864 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 9865 if(cast(SimpleWindow) this.window) 9866 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 9867 9868 if(originalPen !is null) 9869 SelectObject(hdc, originalPen); 9870 if(currentPen !is null) 9871 DeleteObject(currentPen); 9872 if(originalBrush !is null) 9873 SelectObject(hdc, originalBrush); 9874 if(currentBrush !is null) 9875 DeleteObject(currentBrush); 9876 9877 SelectObject(hdc, oldBmp); 9878 9879 if(windowDc) 9880 ReleaseDC(hwnd, hdc); 9881 else 9882 DeleteDC(hdc); 9883 9884 if(window.paintingFinishedDg !is null) 9885 window.paintingFinishedDg()(); 9886 } 9887 9888 bool windowDc; 9889 HPEN originalPen; 9890 HPEN currentPen; 9891 9892 Pen _activePen; 9893 9894 @property void pen(Pen p) { 9895 _activePen = p; 9896 9897 HPEN pen; 9898 if(p.color.a == 0) { 9899 pen = GetStockObject(NULL_PEN); 9900 } else { 9901 int style = PS_SOLID; 9902 final switch(p.style) { 9903 case Pen.Style.Solid: 9904 style = PS_SOLID; 9905 break; 9906 case Pen.Style.Dashed: 9907 style = PS_DASH; 9908 break; 9909 case Pen.Style.Dotted: 9910 style = PS_DOT; 9911 break; 9912 } 9913 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 9914 } 9915 auto orig = SelectObject(hdc, pen); 9916 if(originalPen is null) 9917 originalPen = orig; 9918 9919 if(currentPen !is null) 9920 DeleteObject(currentPen); 9921 9922 currentPen = pen; 9923 9924 // the outline is like a foreground since it's done that way on X 9925 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 9926 9927 } 9928 9929 @property void rasterOp(RasterOp op) { 9930 int mode; 9931 final switch(op) { 9932 case RasterOp.normal: 9933 mode = R2_COPYPEN; 9934 break; 9935 case RasterOp.xor: 9936 mode = R2_XORPEN; 9937 break; 9938 } 9939 SetROP2(hdc, mode); 9940 } 9941 9942 HBRUSH originalBrush; 9943 HBRUSH currentBrush; 9944 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 9945 @property void fillColor(Color c) { 9946 if(c == _fillColor) 9947 return; 9948 _fillColor = c; 9949 HBRUSH brush; 9950 if(c.a == 0) { 9951 brush = GetStockObject(HOLLOW_BRUSH); 9952 } else { 9953 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 9954 } 9955 auto orig = SelectObject(hdc, brush); 9956 if(originalBrush is null) 9957 originalBrush = orig; 9958 9959 if(currentBrush !is null) 9960 DeleteObject(currentBrush); 9961 9962 currentBrush = brush; 9963 9964 // background color is NOT set because X doesn't draw text backgrounds 9965 // SetBkColor(hdc, RGB(255, 255, 255)); 9966 } 9967 9968 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 9969 BITMAP bm; 9970 9971 HDC hdcMem = CreateCompatibleDC(hdc); 9972 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 9973 9974 GetObject(i.handle, bm.sizeof, &bm); 9975 9976 // or should I AlphaBlend!??!?! 9977 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 9978 9979 SelectObject(hdcMem, hbmOld); 9980 DeleteDC(hdcMem); 9981 } 9982 9983 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 9984 BITMAP bm; 9985 9986 HDC hdcMem = CreateCompatibleDC(hdc); 9987 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 9988 9989 GetObject(s.handle, bm.sizeof, &bm); 9990 9991 version(CRuntime_DigitalMars) goto noalpha; 9992 9993 // or should I AlphaBlend!??!?! note it is supposed to be premultiplied http://www.fengyuan.com/article/alphablend.html 9994 if(s.enableAlpha) { 9995 auto dw = w ? w : bm.bmWidth; 9996 auto dh = h ? h : bm.bmHeight; 9997 BLENDFUNCTION bf; 9998 bf.BlendOp = AC_SRC_OVER; 9999 bf.SourceConstantAlpha = 255; 10000 bf.AlphaFormat = AC_SRC_ALPHA; 10001 AlphaBlend(hdc, x, y, dw, dh, hdcMem, ix, iy, dw, dh, bf); 10002 } else { 10003 noalpha: 10004 BitBlt(hdc, x, y, w ? w : bm.bmWidth, h ? h : bm.bmHeight, hdcMem, ix, iy, SRCCOPY); 10005 } 10006 10007 SelectObject(hdcMem, hbmOld); 10008 DeleteDC(hdcMem); 10009 } 10010 10011 Size textSize(scope const(char)[] text) { 10012 bool dummyX; 10013 if(text.length == 0) { 10014 text = " "; 10015 dummyX = true; 10016 } 10017 RECT rect; 10018 WCharzBuffer buffer = WCharzBuffer(text); 10019 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT); 10020 return Size(dummyX ? 0 : rect.right, rect.bottom); 10021 } 10022 10023 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 10024 if(text.length && text[$-1] == '\n') 10025 text = text[0 .. $-1]; // tailing newlines are weird on windows... 10026 if(text.length && text[$-1] == '\r') 10027 text = text[0 .. $-1]; 10028 10029 WCharzBuffer buffer = WCharzBuffer(text, WindowsStringConversionFlags.convertNewLines); 10030 if(x2 == 0 && y2 == 0) 10031 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 10032 else { 10033 RECT rect; 10034 rect.left = x; 10035 rect.top = y; 10036 rect.right = x2; 10037 rect.bottom = y2; 10038 10039 uint mode = DT_LEFT; 10040 if(alignment & TextAlignment.Right) 10041 mode = DT_RIGHT; 10042 else if(alignment & TextAlignment.Center) 10043 mode = DT_CENTER; 10044 10045 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 10046 if(alignment & TextAlignment.VerticalCenter) 10047 mode |= DT_VCENTER | DT_SINGLELINE; 10048 10049 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode); 10050 } 10051 10052 /* 10053 uint mode; 10054 10055 if(alignment & TextAlignment.Center) 10056 mode = TA_CENTER; 10057 10058 SetTextAlign(hdc, mode); 10059 */ 10060 } 10061 10062 int fontHeight() { 10063 TEXTMETRIC metric; 10064 if(GetTextMetricsW(hdc, &metric)) { 10065 return metric.tmHeight; 10066 } 10067 10068 return 16; // idk just guessing here, maybe we should throw 10069 } 10070 10071 void drawPixel(int x, int y) { 10072 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 10073 } 10074 10075 // The basic shapes, outlined 10076 10077 void drawLine(int x1, int y1, int x2, int y2) { 10078 MoveToEx(hdc, x1, y1, null); 10079 LineTo(hdc, x2, y2); 10080 } 10081 10082 void drawRectangle(int x, int y, int width, int height) { 10083 // FIXME: with a wider pen this might not draw quite right. im not sure. 10084 gdi.Rectangle(hdc, x, y, x + width, y + height); 10085 } 10086 10087 /// Arguments are the points of the bounding rectangle 10088 void drawEllipse(int x1, int y1, int x2, int y2) { 10089 Ellipse(hdc, x1, y1, x2, y2); 10090 } 10091 10092 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 10093 if((start % (360*64)) == (finish % (360*64))) 10094 drawEllipse(x1, y1, x1 + width, y1 + height); 10095 else { 10096 import core.stdc.math; 10097 float startAngle = start * 64 * 180 / 3.14159265; 10098 float endAngle = finish * 64 * 180 / 3.14159265; 10099 Arc(hdc, x1, y1, x1 + width, y1 + height, 10100 cast(int)(cos(startAngle) * width / 2 + x1), 10101 cast(int)(sin(startAngle) * height / 2 + y1), 10102 cast(int)(cos(endAngle) * width / 2 + x1), 10103 cast(int)(sin(endAngle) * height / 2 + y1), 10104 ); 10105 } 10106 } 10107 10108 void drawPolygon(Point[] vertexes) { 10109 POINT[] points; 10110 points.length = vertexes.length; 10111 10112 foreach(i, p; vertexes) { 10113 points[i].x = p.x; 10114 points[i].y = p.y; 10115 } 10116 10117 Polygon(hdc, points.ptr, cast(int) points.length); 10118 } 10119 } 10120 10121 10122 // Mix this into the SimpleWindow class 10123 mixin template NativeSimpleWindowImplementation() { 10124 int curHidden = 0; // counter 10125 __gshared static bool[string] knownWinClasses; 10126 static bool altPressed = false; 10127 10128 HANDLE oldCursor; 10129 10130 void hideCursor () { 10131 if(curHidden == 0) 10132 oldCursor = SetCursor(null); 10133 ++curHidden; 10134 } 10135 10136 void showCursor () { 10137 --curHidden; 10138 if(curHidden == 0) { 10139 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 10140 } 10141 } 10142 10143 10144 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 10145 10146 void setMinSize (int minwidth, int minheight) { 10147 minWidth = minwidth; 10148 minHeight = minheight; 10149 } 10150 void setMaxSize (int maxwidth, int maxheight) { 10151 maxWidth = maxwidth; 10152 maxHeight = maxheight; 10153 } 10154 10155 // FIXME i'm not sure that Windows has this functionality 10156 // though it is nonessential anyway. 10157 void setResizeGranularity (int granx, int grany) {} 10158 10159 ScreenPainter getPainter() { 10160 return ScreenPainter(this, hwnd); 10161 } 10162 10163 HBITMAP buffer; 10164 10165 void setTitle(string title) { 10166 WCharzBuffer bfr = WCharzBuffer(title); 10167 SetWindowTextW(hwnd, bfr.ptr); 10168 } 10169 10170 string getTitle() { 10171 auto len = GetWindowTextLengthW(hwnd); 10172 if (!len) 10173 return null; 10174 wchar[256] tmpBuffer; 10175 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 10176 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 10177 auto str = buffer[0 .. len2]; 10178 return makeUtf8StringFromWindowsString(str); 10179 } 10180 10181 void move(int x, int y) { 10182 RECT rect; 10183 GetWindowRect(hwnd, &rect); 10184 // move it while maintaining the same size... 10185 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 10186 } 10187 10188 void resize(int w, int h) { 10189 RECT rect; 10190 GetWindowRect(hwnd, &rect); 10191 10192 RECT client; 10193 GetClientRect(hwnd, &client); 10194 10195 rect.right = rect.right - client.right + w; 10196 rect.bottom = rect.bottom - client.bottom + h; 10197 10198 // same position, new size for the client rectangle 10199 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 10200 10201 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 10202 } 10203 10204 void moveResize (int x, int y, int w, int h) { 10205 // what's given is the client rectangle, we need to adjust 10206 10207 RECT rect; 10208 rect.left = x; 10209 rect.top = y; 10210 rect.right = w + x; 10211 rect.bottom = h + y; 10212 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 10213 throw new Exception("AdjustWindowRect"); 10214 10215 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 10216 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 10217 if (windowResized !is null) windowResized(w, h); 10218 } 10219 10220 version(without_opengl) {} else { 10221 HGLRC ghRC; 10222 HDC ghDC; 10223 } 10224 10225 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 10226 string cnamec; 10227 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 10228 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 10229 cnamec = "DSimpleWindow"; 10230 } else { 10231 cnamec = sdpyWindowClass; 10232 } 10233 10234 WCharzBuffer cn = WCharzBuffer(cnamec); 10235 10236 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 10237 10238 if(cnamec !in knownWinClasses) { 10239 WNDCLASSEX wc; 10240 10241 // FIXME: I might be able to use cbWndExtra to hold the pointer back 10242 // to the object. Maybe. 10243 wc.cbSize = wc.sizeof; 10244 wc.cbClsExtra = 0; 10245 wc.cbWndExtra = 0; 10246 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 10247 wc.hCursor = LoadCursorW(null, IDC_ARROW); 10248 wc.hIcon = LoadIcon(hInstance, null); 10249 wc.hInstance = hInstance; 10250 wc.lpfnWndProc = &WndProc; 10251 wc.lpszClassName = cn.ptr; 10252 wc.hIconSm = null; 10253 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 10254 if(!RegisterClassExW(&wc)) 10255 throw new WindowsApiException("RegisterClassExW"); 10256 knownWinClasses[cnamec] = true; 10257 } 10258 10259 int style; 10260 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 10261 10262 // FIXME: windowType and customizationFlags 10263 final switch(windowType) { 10264 case WindowTypes.normal: 10265 style = WS_OVERLAPPEDWINDOW; 10266 break; 10267 case WindowTypes.undecorated: 10268 style = WS_POPUP | WS_SYSMENU; 10269 break; 10270 case WindowTypes.eventOnly: 10271 _hidden = true; 10272 break; 10273 case WindowTypes.dropdownMenu: 10274 case WindowTypes.popupMenu: 10275 case WindowTypes.notification: 10276 style = WS_POPUP; 10277 flags |= WS_EX_NOACTIVATE; 10278 break; 10279 case WindowTypes.nestedChild: 10280 style = WS_CHILD; 10281 break; 10282 } 10283 10284 if ((customizationFlags & WindowFlags.extraComposite) != 0) 10285 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 10286 10287 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 10288 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 10289 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 10290 10291 if ((customizationFlags & WindowFlags.extraComposite) != 0) 10292 setOpacity(255); 10293 10294 SimpleWindow.nativeMapping[hwnd] = this; 10295 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 10296 10297 if(windowType == WindowTypes.eventOnly) 10298 return; 10299 10300 HDC hdc = GetDC(hwnd); 10301 10302 10303 version(without_opengl) {} 10304 else { 10305 if(opengl == OpenGlOptions.yes) { 10306 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 10307 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 10308 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 10309 ghDC = hdc; 10310 PIXELFORMATDESCRIPTOR pfd; 10311 10312 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 10313 pfd.nVersion = 1; 10314 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 10315 pfd.dwLayerMask = PFD_MAIN_PLANE; 10316 pfd.iPixelType = PFD_TYPE_RGBA; 10317 pfd.cColorBits = 24; 10318 pfd.cDepthBits = 24; 10319 pfd.cAccumBits = 0; 10320 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 10321 10322 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 10323 10324 if (pixelformat == 0) 10325 throw new WindowsApiException("ChoosePixelFormat"); 10326 10327 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 10328 throw new WindowsApiException("SetPixelFormat"); 10329 10330 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 10331 // windoze is idiotic: we have to have OpenGL context to get function addresses 10332 // so we will create fake context to get that stupid address 10333 auto tmpcc = wglCreateContext(ghDC); 10334 if (tmpcc !is null) { 10335 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 10336 wglMakeCurrent(ghDC, tmpcc); 10337 wglInitOtherFunctions(); 10338 } 10339 } 10340 10341 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 10342 int[9] contextAttribs = [ 10343 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 10344 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 10345 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 10346 // for modern context, set "forward compatibility" flag too 10347 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 10348 0/*None*/, 10349 ]; 10350 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 10351 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 10352 // activate fallback mode 10353 // 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; 10354 ghRC = wglCreateContext(ghDC); 10355 } 10356 if (ghRC is null) 10357 throw new WindowsApiException("wglCreateContextAttribsARB"); 10358 } else { 10359 // try to do at least something 10360 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 10361 sdpyOpenGLContextVersion = 0; 10362 ghRC = wglCreateContext(ghDC); 10363 } 10364 if (ghRC is null) 10365 throw new WindowsApiException("wglCreateContext"); 10366 } 10367 } 10368 } 10369 10370 if(opengl == OpenGlOptions.no) { 10371 buffer = CreateCompatibleBitmap(hdc, width, height); 10372 10373 auto hdcBmp = CreateCompatibleDC(hdc); 10374 // make sure it's filled with a blank slate 10375 auto oldBmp = SelectObject(hdcBmp, buffer); 10376 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 10377 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 10378 gdi.Rectangle(hdcBmp, 0, 0, width, height); 10379 SelectObject(hdcBmp, oldBmp); 10380 SelectObject(hdcBmp, oldBrush); 10381 SelectObject(hdcBmp, oldPen); 10382 DeleteDC(hdcBmp); 10383 10384 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 10385 } 10386 10387 // We want the window's client area to match the image size 10388 RECT rcClient, rcWindow; 10389 POINT ptDiff; 10390 GetClientRect(hwnd, &rcClient); 10391 GetWindowRect(hwnd, &rcWindow); 10392 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 10393 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 10394 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 10395 10396 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 10397 ShowWindow(hwnd, SW_SHOWNORMAL); 10398 } else { 10399 _hidden = true; 10400 } 10401 this._visibleForTheFirstTimeCalled = false; // hack! 10402 } 10403 10404 10405 void dispose() { 10406 if(buffer) 10407 DeleteObject(buffer); 10408 } 10409 10410 void closeWindow() { 10411 DestroyWindow(hwnd); 10412 } 10413 10414 bool setOpacity(ubyte alpha) { 10415 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 10416 } 10417 10418 HANDLE currentCursor; 10419 10420 // returns zero if it recognized the event 10421 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 10422 MouseEvent mouse; 10423 10424 void mouseEvent(bool isScreen, ulong mods) { 10425 auto x = LOWORD(lParam); 10426 auto y = HIWORD(lParam); 10427 if(isScreen) { 10428 POINT p; 10429 p.x = x; 10430 p.y = y; 10431 ScreenToClient(hwnd, &p); 10432 x = cast(ushort) p.x; 10433 y = cast(ushort) p.y; 10434 } 10435 mouse.x = x + offsetX; 10436 mouse.y = y + offsetY; 10437 10438 wind.mdx(mouse); 10439 mouse.modifierState = cast(int) mods; 10440 mouse.window = wind; 10441 10442 if(wind.handleMouseEvent) 10443 wind.handleMouseEvent(mouse); 10444 } 10445 10446 switch(msg) { 10447 case WM_GETMINMAXINFO: 10448 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 10449 10450 if(wind.minWidth > 0) { 10451 RECT rect; 10452 rect.left = 100; 10453 rect.top = 100; 10454 rect.right = wind.minWidth + 100; 10455 rect.bottom = wind.minHeight + 100; 10456 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 10457 throw new WindowsApiException("AdjustWindowRect"); 10458 10459 mmi.ptMinTrackSize.x = rect.right - rect.left; 10460 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 10461 } 10462 10463 if(wind.maxWidth < int.max) { 10464 RECT rect; 10465 rect.left = 100; 10466 rect.top = 100; 10467 rect.right = wind.maxWidth + 100; 10468 rect.bottom = wind.maxHeight + 100; 10469 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 10470 throw new WindowsApiException("AdjustWindowRect"); 10471 10472 mmi.ptMaxTrackSize.x = rect.right - rect.left; 10473 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 10474 } 10475 break; 10476 case WM_CHAR: 10477 wchar c = cast(wchar) wParam; 10478 if(wind.handleCharEvent) 10479 wind.handleCharEvent(cast(dchar) c); 10480 break; 10481 case WM_SETFOCUS: 10482 case WM_KILLFOCUS: 10483 wind._focused = (msg == WM_SETFOCUS); 10484 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 10485 if(wind.onFocusChange) 10486 wind.onFocusChange(msg == WM_SETFOCUS); 10487 break; 10488 case WM_SYSKEYDOWN: 10489 goto case; 10490 case WM_SYSKEYUP: 10491 if(lParam & (1 << 29)) { 10492 goto case; 10493 } else { 10494 // no window has keyboard focus 10495 goto default; 10496 } 10497 case WM_KEYDOWN: 10498 case WM_KEYUP: 10499 KeyEvent ev; 10500 ev.key = cast(Key) wParam; 10501 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 10502 if (wParam == 0x12) ev.key = Key.Alt; // windows does it this way 10503 10504 ev.hardwareCode = (lParam & 0xff0000) >> 16; 10505 10506 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 10507 ev.modifierState |= ModifierState.shift; 10508 //k8: this doesn't work; thanks for nothing, windows 10509 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 10510 ev.modifierState |= ModifierState.alt;*/ 10511 if (wParam == 0x12) altPressed = (msg == WM_SYSKEYDOWN); 10512 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 10513 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 10514 ev.modifierState |= ModifierState.ctrl; 10515 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 10516 ev.modifierState |= ModifierState.windows; 10517 if(GetKeyState(Key.NumLock)) 10518 ev.modifierState |= ModifierState.numLock; 10519 if(GetKeyState(Key.CapsLock)) 10520 ev.modifierState |= ModifierState.capsLock; 10521 10522 /+ 10523 // we always want to send the character too, so let's convert it 10524 ubyte[256] state; 10525 wchar[16] buffer; 10526 GetKeyboardState(state.ptr); 10527 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 10528 10529 foreach(dchar d; buffer) { 10530 ev.character = d; 10531 break; 10532 } 10533 +/ 10534 10535 ev.window = wind; 10536 if(wind.handleKeyEvent) 10537 wind.handleKeyEvent(ev); 10538 break; 10539 case 0x020a /*WM_MOUSEWHEEL*/: 10540 // send click 10541 mouse.type = cast(MouseEventType) 1; 10542 mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp); 10543 mouseEvent(true, LOWORD(wParam)); 10544 10545 // also send release 10546 mouse.type = cast(MouseEventType) 2; 10547 mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp); 10548 mouseEvent(true, LOWORD(wParam)); 10549 break; 10550 case WM_MOUSEMOVE: 10551 mouse.type = cast(MouseEventType) 0; 10552 mouseEvent(false, wParam); 10553 break; 10554 case WM_LBUTTONDOWN: 10555 case WM_LBUTTONDBLCLK: 10556 mouse.type = cast(MouseEventType) 1; 10557 mouse.button = MouseButton.left; 10558 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 10559 mouseEvent(false, wParam); 10560 break; 10561 case WM_LBUTTONUP: 10562 mouse.type = cast(MouseEventType) 2; 10563 mouse.button = MouseButton.left; 10564 mouseEvent(false, wParam); 10565 break; 10566 case WM_RBUTTONDOWN: 10567 case WM_RBUTTONDBLCLK: 10568 mouse.type = cast(MouseEventType) 1; 10569 mouse.button = MouseButton.right; 10570 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 10571 mouseEvent(false, wParam); 10572 break; 10573 case WM_RBUTTONUP: 10574 mouse.type = cast(MouseEventType) 2; 10575 mouse.button = MouseButton.right; 10576 mouseEvent(false, wParam); 10577 break; 10578 case WM_MBUTTONDOWN: 10579 case WM_MBUTTONDBLCLK: 10580 mouse.type = cast(MouseEventType) 1; 10581 mouse.button = MouseButton.middle; 10582 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 10583 mouseEvent(false, wParam); 10584 break; 10585 case WM_MBUTTONUP: 10586 mouse.type = cast(MouseEventType) 2; 10587 mouse.button = MouseButton.middle; 10588 mouseEvent(false, wParam); 10589 break; 10590 case WM_XBUTTONDOWN: 10591 case WM_XBUTTONDBLCLK: 10592 mouse.type = cast(MouseEventType) 1; 10593 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 10594 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 10595 mouseEvent(false, wParam); 10596 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 10597 case WM_XBUTTONUP: 10598 mouse.type = cast(MouseEventType) 2; 10599 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 10600 mouseEvent(false, wParam); 10601 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 10602 10603 default: return 1; 10604 } 10605 return 0; 10606 } 10607 10608 HWND hwnd; 10609 int oldWidth; 10610 int oldHeight; 10611 bool inSizeMove; 10612 10613 // the extern(Windows) wndproc should just forward to this 10614 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 10615 assert(hwnd is this.hwnd); 10616 10617 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 10618 switch(msg) { 10619 case WM_SETCURSOR: 10620 if(cast(HWND) wParam !is hwnd) 10621 return 0; // further processing elsewhere 10622 10623 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 10624 SetCursor(this.curHidden > 0 ? null : currentCursor); 10625 return 1; 10626 } else { 10627 return DefWindowProc(hwnd, msg, wParam, lParam); 10628 } 10629 //break; 10630 10631 case WM_CLOSE: 10632 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 10633 break; 10634 case WM_DESTROY: 10635 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 10636 SimpleWindow.nativeMapping.remove(hwnd); 10637 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 10638 10639 bool anyImportant = false; 10640 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 10641 if(w.beingOpenKeepsAppOpen) { 10642 anyImportant = true; 10643 break; 10644 } 10645 if(!anyImportant) { 10646 PostQuitMessage(0); 10647 } 10648 break; 10649 case WM_SIZE: 10650 if(wParam == 1 /* SIZE_MINIMIZED */) 10651 break; 10652 _width = LOWORD(lParam); 10653 _height = HIWORD(lParam); 10654 10655 // I want to avoid tearing in the windows (my code is inefficient 10656 // so this is a hack around that) so while sizing, we don't trigger, 10657 // but we do want to trigger on events like mazimize. 10658 if(!inSizeMove) 10659 goto size_changed; 10660 break; 10661 // I don't like the tearing I get when redrawing on WM_SIZE 10662 // (I know there's other ways to fix that but I don't like that behavior anyway) 10663 // so instead it is going to redraw only at the end of a size. 10664 case 0x0231: /* WM_ENTERSIZEMOVE */ 10665 oldWidth = this.width; 10666 oldHeight = this.height; 10667 inSizeMove = true; 10668 break; 10669 case 0x0232: /* WM_EXITSIZEMOVE */ 10670 inSizeMove = false; 10671 // nothing relevant changed, don't bother redrawing 10672 if(oldWidth == width && oldHeight == height) 10673 break; 10674 10675 size_changed: 10676 10677 // note: OpenGL windows don't use a backing bmp, so no need to change them 10678 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 10679 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 10680 // gotta get the double buffer bmp to match the window 10681 // FIXME: could this be more efficient? It isn't really necessary to make 10682 // a new buffer if we're sizing down at least. 10683 auto hdc = GetDC(hwnd); 10684 auto oldBuffer = buffer; 10685 buffer = CreateCompatibleBitmap(hdc, width, height); 10686 10687 auto hdcBmp = CreateCompatibleDC(hdc); 10688 auto oldBmp = SelectObject(hdcBmp, buffer); 10689 10690 auto hdcOldBmp = CreateCompatibleDC(hdc); 10691 auto oldOldBmp = SelectObject(hdcOldBmp, oldBmp); 10692 10693 BitBlt(hdcBmp, 0, 0, width, height, hdcOldBmp, oldWidth, oldHeight, SRCCOPY); 10694 10695 SelectObject(hdcOldBmp, oldOldBmp); 10696 DeleteDC(hdcOldBmp); 10697 10698 SelectObject(hdcBmp, oldBmp); 10699 DeleteDC(hdcBmp); 10700 10701 ReleaseDC(hwnd, hdc); 10702 10703 DeleteObject(oldBuffer); 10704 } 10705 10706 version(without_opengl) {} else 10707 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 10708 glViewport(0, 0, width, height); 10709 } 10710 10711 if(windowResized !is null) 10712 windowResized(width, height); 10713 break; 10714 case WM_ERASEBKGND: 10715 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 10716 if (!this._visibleForTheFirstTimeCalled) { 10717 this._visibleForTheFirstTimeCalled = true; 10718 if (this.visibleForTheFirstTime !is null) { 10719 version(without_opengl) {} else { 10720 if(openglMode == OpenGlOptions.yes) { 10721 this.setAsCurrentOpenGlContextNT(); 10722 glViewport(0, 0, width, height); 10723 } 10724 } 10725 this.visibleForTheFirstTime(); 10726 } 10727 } 10728 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 10729 version(without_opengl) {} else { 10730 if (openglMode == OpenGlOptions.yes) return 1; 10731 } 10732 // call windows default handler, so it can paint standard controls 10733 goto default; 10734 case WM_CTLCOLORBTN: 10735 case WM_CTLCOLORSTATIC: 10736 SetBkMode(cast(HDC) wParam, TRANSPARENT); 10737 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 10738 GetSysColorBrush(COLOR_3DFACE); 10739 //break; 10740 case WM_SHOWWINDOW: 10741 this._visible = (wParam != 0); 10742 if (!this._visibleForTheFirstTimeCalled && this._visible) { 10743 this._visibleForTheFirstTimeCalled = true; 10744 if (this.visibleForTheFirstTime !is null) { 10745 version(without_opengl) {} else { 10746 if(openglMode == OpenGlOptions.yes) { 10747 this.setAsCurrentOpenGlContextNT(); 10748 glViewport(0, 0, width, height); 10749 } 10750 } 10751 this.visibleForTheFirstTime(); 10752 } 10753 } 10754 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 10755 break; 10756 case WM_PAINT: { 10757 if (!this._visibleForTheFirstTimeCalled) { 10758 this._visibleForTheFirstTimeCalled = true; 10759 if (this.visibleForTheFirstTime !is null) { 10760 version(without_opengl) {} else { 10761 if(openglMode == OpenGlOptions.yes) { 10762 this.setAsCurrentOpenGlContextNT(); 10763 glViewport(0, 0, width, height); 10764 } 10765 } 10766 this.visibleForTheFirstTime(); 10767 } 10768 } 10769 10770 BITMAP bm; 10771 PAINTSTRUCT ps; 10772 10773 HDC hdc = BeginPaint(hwnd, &ps); 10774 10775 if(openglMode == OpenGlOptions.no) { 10776 10777 HDC hdcMem = CreateCompatibleDC(hdc); 10778 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 10779 10780 GetObject(buffer, bm.sizeof, &bm); 10781 10782 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 10783 if(resizability == Resizability.automaticallyScaleIfPossible) 10784 StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 10785 else 10786 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 10787 10788 SelectObject(hdcMem, hbmOld); 10789 DeleteDC(hdcMem); 10790 EndPaint(hwnd, &ps); 10791 } else { 10792 EndPaint(hwnd, &ps); 10793 version(without_opengl) {} else 10794 redrawOpenGlSceneNow(); 10795 } 10796 } break; 10797 default: 10798 return DefWindowProc(hwnd, msg, wParam, lParam); 10799 } 10800 return 0; 10801 10802 } 10803 } 10804 10805 mixin template NativeImageImplementation() { 10806 HBITMAP handle; 10807 ubyte* rawData; 10808 10809 final: 10810 10811 Color getPixel(int x, int y) { 10812 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 10813 // remember, bmps are upside down 10814 auto offset = itemsPerLine * (height - y - 1) + x * 3; 10815 10816 Color c; 10817 if(enableAlpha) 10818 c.a = rawData[offset + 3]; 10819 else 10820 c.a = 255; 10821 c.b = rawData[offset + 0]; 10822 c.g = rawData[offset + 1]; 10823 c.r = rawData[offset + 2]; 10824 c.unPremultiply(); 10825 return c; 10826 } 10827 10828 void setPixel(int x, int y, Color c) { 10829 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 10830 // remember, bmps are upside down 10831 auto offset = itemsPerLine * (height - y - 1) + x * 3; 10832 10833 if(enableAlpha) 10834 c.premultiply(); 10835 10836 rawData[offset + 0] = c.b; 10837 rawData[offset + 1] = c.g; 10838 rawData[offset + 2] = c.r; 10839 if(enableAlpha) 10840 rawData[offset + 3] = c.a; 10841 } 10842 10843 void convertToRgbaBytes(ubyte[] where) { 10844 assert(where.length == this.width * this.height * 4); 10845 10846 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 10847 int idx = 0; 10848 int offset = itemsPerLine * (height - 1); 10849 // remember, bmps are upside down 10850 for(int y = height - 1; y >= 0; y--) { 10851 auto offsetStart = offset; 10852 for(int x = 0; x < width; x++) { 10853 where[idx + 0] = rawData[offset + 2]; // r 10854 where[idx + 1] = rawData[offset + 1]; // g 10855 where[idx + 2] = rawData[offset + 0]; // b 10856 if(enableAlpha) { 10857 where[idx + 3] = rawData[offset + 3]; // a 10858 unPremultiplyRgba(where[idx .. idx + 4]); 10859 offset++; 10860 } else 10861 where[idx + 3] = 255; // a 10862 idx += 4; 10863 offset += 3; 10864 } 10865 10866 offset = offsetStart - itemsPerLine; 10867 } 10868 } 10869 10870 void setFromRgbaBytes(in ubyte[] what) { 10871 assert(what.length == this.width * this.height * 4); 10872 10873 auto itemsPerLine = enableAlpha ? (width * 4) : (((cast(int) width * 3 + 3) / 4) * 4); 10874 int idx = 0; 10875 int offset = itemsPerLine * (height - 1); 10876 // remember, bmps are upside down 10877 for(int y = height - 1; y >= 0; y--) { 10878 auto offsetStart = offset; 10879 for(int x = 0; x < width; x++) { 10880 if(enableAlpha) { 10881 auto a = what[idx + 3]; 10882 10883 rawData[offset + 2] = (a * what[idx + 0]) / 255; // r 10884 rawData[offset + 1] = (a * what[idx + 1]) / 255; // g 10885 rawData[offset + 0] = (a * what[idx + 2]) / 255; // b 10886 rawData[offset + 3] = a; // a 10887 premultiplyBgra(rawData[offset .. offset + 4]); 10888 offset++; 10889 } else { 10890 rawData[offset + 2] = what[idx + 0]; // r 10891 rawData[offset + 1] = what[idx + 1]; // g 10892 rawData[offset + 0] = what[idx + 2]; // b 10893 } 10894 idx += 4; 10895 offset += 3; 10896 } 10897 10898 offset = offsetStart - itemsPerLine; 10899 } 10900 } 10901 10902 10903 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 10904 BITMAPINFO infoheader; 10905 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 10906 infoheader.bmiHeader.biWidth = width; 10907 infoheader.bmiHeader.biHeight = height; 10908 infoheader.bmiHeader.biPlanes = 1; 10909 infoheader.bmiHeader.biBitCount = enableAlpha ? 32: 24; 10910 infoheader.bmiHeader.biCompression = BI_RGB; 10911 10912 handle = CreateDIBSection( 10913 null, 10914 &infoheader, 10915 DIB_RGB_COLORS, 10916 cast(void**) &rawData, 10917 null, 10918 0); 10919 if(handle is null) 10920 throw new WindowsApiException("create image failed"); 10921 10922 } 10923 10924 void dispose() { 10925 DeleteObject(handle); 10926 } 10927 } 10928 10929 enum KEY_ESCAPE = 27; 10930 } 10931 version(X11) { 10932 /// This is the default font used. You might change this before doing anything else with 10933 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 10934 /// for cross-platform compatibility. 10935 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 10936 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 10937 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 10938 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 10939 10940 alias int delegate(XEvent) NativeEventHandler; 10941 alias Window NativeWindowHandle; 10942 10943 enum KEY_ESCAPE = 9; 10944 10945 mixin template NativeScreenPainterImplementation() { 10946 Display* display; 10947 Drawable d; 10948 Drawable destiny; 10949 10950 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 10951 GC gc; 10952 10953 __gshared bool fontAttempted; 10954 10955 __gshared XFontStruct* defaultfont; 10956 __gshared XFontSet defaultfontset; 10957 10958 XFontStruct* font; 10959 XFontSet fontset; 10960 10961 void create(NativeWindowHandle window) { 10962 this.display = XDisplayConnection.get(); 10963 10964 Drawable buffer = None; 10965 if(auto sw = cast(SimpleWindow) this.window) { 10966 buffer = sw.impl.buffer; 10967 this.destiny = cast(Drawable) window; 10968 } else { 10969 buffer = cast(Drawable) window; 10970 this.destiny = None; 10971 } 10972 10973 this.d = cast(Drawable) buffer; 10974 10975 auto dgc = DefaultGC(display, DefaultScreen(display)); 10976 10977 this.gc = XCreateGC(display, d, 0, null); 10978 10979 XCopyGC(display, dgc, 0xffffffff, this.gc); 10980 10981 ensureDefaultFontLoaded(); 10982 10983 font = defaultfont; 10984 fontset = defaultfontset; 10985 10986 if(font) { 10987 XSetFont(display, gc, font.fid); 10988 } 10989 } 10990 10991 static void ensureDefaultFontLoaded() { 10992 if(!fontAttempted) { 10993 auto display = XDisplayConnection.get; 10994 auto font = XLoadQueryFont(display, xfontstr.ptr); 10995 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 10996 if(font is null) { 10997 xfontstr = "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"; 10998 font = XLoadQueryFont(display, xfontstr.ptr); 10999 } 11000 11001 char** lol; 11002 int lol2; 11003 char* lol3; 11004 auto fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 11005 11006 fontAttempted = true; 11007 11008 defaultfont = font; 11009 defaultfontset = fontset; 11010 } 11011 } 11012 11013 arsd.color.Rectangle _clipRectangle; 11014 void setClipRectangle(int x, int y, int width, int height) { 11015 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 11016 if(width == 0 || height == 0) { 11017 XSetClipMask(display, gc, None); 11018 11019 version(with_xft) { 11020 if(xftFont is null || xftDraw is null) 11021 return; 11022 XftDrawSetClip(xftDraw, null); 11023 } 11024 } else { 11025 XRectangle[1] rects; 11026 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 11027 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 11028 11029 version(with_xft) { 11030 if(xftFont is null || xftDraw is null) 11031 return; 11032 XftDrawSetClipRectangles(xftDraw, 0, 0, rects.ptr, 1); 11033 } 11034 } 11035 } 11036 11037 version(with_xft) { 11038 XftFont* xftFont; 11039 XftDraw* xftDraw; 11040 11041 XftColor xftColor; 11042 11043 void updateXftColor() { 11044 if(xftFont is null) 11045 return; 11046 11047 // not bothering with XftColorFree since p sure i don't need it on 24 bit displays.... 11048 XRenderColor colorIn = XRenderColor(_outlineColor.r * 255, _outlineColor.g * 255, _outlineColor.b * 255, _outlineColor.a * 255); 11049 11050 XftColorAllocValue( 11051 display, 11052 DefaultVisual(display, DefaultScreen(display)), 11053 DefaultColormap(display, 0), 11054 &colorIn, 11055 &xftColor 11056 ); 11057 } 11058 } 11059 11060 void setFont(OperatingSystemFont font) { 11061 version(with_xft) { 11062 if(font && font.isXft && font.xftFont) 11063 this.xftFont = font.xftFont; 11064 else 11065 this.xftFont = null; 11066 11067 if(this.xftFont) { 11068 if(xftDraw is null) { 11069 xftDraw = XftDrawCreate( 11070 display, 11071 d, 11072 DefaultVisual(display, DefaultScreen(display)), 11073 DefaultColormap(display, 0) 11074 ); 11075 11076 updateXftColor(); 11077 } 11078 11079 return; 11080 } 11081 } 11082 11083 if(font && font.font) { 11084 this.font = font.font; 11085 this.fontset = font.fontset; 11086 XSetFont(display, gc, font.font.fid); 11087 } else { 11088 this.font = defaultfont; 11089 this.fontset = defaultfontset; 11090 } 11091 11092 } 11093 11094 private Picture xrenderPicturePainter; 11095 11096 void dispose() { 11097 this.rasterOp = RasterOp.normal; 11098 11099 if(xrenderPicturePainter) { 11100 XRenderFreePicture(display, xrenderPicturePainter); 11101 xrenderPicturePainter = None; 11102 } 11103 11104 // FIXME: this.window.width/height is probably wrong 11105 11106 // src x,y then dest x, y 11107 if(destiny != None) { 11108 XSetClipMask(display, gc, None); 11109 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 11110 } 11111 11112 XFreeGC(display, gc); 11113 11114 version(with_xft) 11115 if(xftDraw) { 11116 XftDrawDestroy(xftDraw); 11117 xftDraw = null; 11118 } 11119 11120 /+ 11121 // this should prolly legit never be used since if it destroys the font handle from a OperatingSystemFont, it also ruins a reusable resource. 11122 if(font && font !is defaultfont) { 11123 XFreeFont(display, font); 11124 font = null; 11125 } 11126 if(fontset && fontset !is defaultfontset) { 11127 XFreeFontSet(display, fontset); 11128 fontset = null; 11129 } 11130 +/ 11131 XFlush(display); 11132 11133 if(window.paintingFinishedDg !is null) 11134 window.paintingFinishedDg()(); 11135 } 11136 11137 bool backgroundIsNotTransparent = true; 11138 bool foregroundIsNotTransparent = true; 11139 11140 bool _penInitialized = false; 11141 Pen _activePen; 11142 11143 Color _outlineColor; 11144 Color _fillColor; 11145 11146 @property void pen(Pen p) { 11147 if(_penInitialized && p == _activePen) { 11148 return; 11149 } 11150 _penInitialized = true; 11151 _activePen = p; 11152 _outlineColor = p.color; 11153 11154 int style; 11155 11156 byte dashLength; 11157 11158 final switch(p.style) { 11159 case Pen.Style.Solid: 11160 style = 0 /*LineSolid*/; 11161 break; 11162 case Pen.Style.Dashed: 11163 style = 1 /*LineOnOffDash*/; 11164 dashLength = 4; 11165 break; 11166 case Pen.Style.Dotted: 11167 style = 1 /*LineOnOffDash*/; 11168 dashLength = 1; 11169 break; 11170 } 11171 11172 XSetLineAttributes(display, gc, p.width, style, 0, 0); 11173 if(dashLength) 11174 XSetDashes(display, gc, 0, &dashLength, 1); 11175 11176 if(p.color.a == 0) { 11177 foregroundIsNotTransparent = false; 11178 return; 11179 } 11180 11181 foregroundIsNotTransparent = true; 11182 11183 XSetForeground(display, gc, colorToX(p.color, display)); 11184 11185 version(with_xft) 11186 updateXftColor(); 11187 } 11188 11189 RasterOp _currentRasterOp; 11190 bool _currentRasterOpInitialized = false; 11191 @property void rasterOp(RasterOp op) { 11192 if(_currentRasterOpInitialized && _currentRasterOp == op) 11193 return; 11194 _currentRasterOp = op; 11195 _currentRasterOpInitialized = true; 11196 int mode; 11197 final switch(op) { 11198 case RasterOp.normal: 11199 mode = GXcopy; 11200 break; 11201 case RasterOp.xor: 11202 mode = GXxor; 11203 break; 11204 } 11205 XSetFunction(display, gc, mode); 11206 } 11207 11208 11209 bool _fillColorInitialized = false; 11210 11211 @property void fillColor(Color c) { 11212 if(_fillColorInitialized && _fillColor == c) 11213 return; // already good, no need to waste time calling it 11214 _fillColor = c; 11215 _fillColorInitialized = true; 11216 if(c.a == 0) { 11217 backgroundIsNotTransparent = false; 11218 return; 11219 } 11220 11221 backgroundIsNotTransparent = true; 11222 11223 XSetBackground(display, gc, colorToX(c, display)); 11224 11225 } 11226 11227 void swapColors() { 11228 auto tmp = _fillColor; 11229 fillColor = _outlineColor; 11230 auto newPen = _activePen; 11231 newPen.color = tmp; 11232 pen(newPen); 11233 } 11234 11235 uint colorToX(Color c, Display* display) { 11236 auto visual = DefaultVisual(display, DefaultScreen(display)); 11237 import core.bitop; 11238 uint color = 0; 11239 { 11240 auto startBit = bsf(visual.red_mask); 11241 auto lastBit = bsr(visual.red_mask); 11242 auto r = cast(uint) c.r; 11243 r >>= 7 - (lastBit - startBit); 11244 r <<= startBit; 11245 color |= r; 11246 } 11247 { 11248 auto startBit = bsf(visual.green_mask); 11249 auto lastBit = bsr(visual.green_mask); 11250 auto g = cast(uint) c.g; 11251 g >>= 7 - (lastBit - startBit); 11252 g <<= startBit; 11253 color |= g; 11254 } 11255 { 11256 auto startBit = bsf(visual.blue_mask); 11257 auto lastBit = bsr(visual.blue_mask); 11258 auto b = cast(uint) c.b; 11259 b >>= 7 - (lastBit - startBit); 11260 b <<= startBit; 11261 color |= b; 11262 } 11263 11264 11265 11266 return color; 11267 } 11268 11269 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 11270 // source x, source y 11271 if(ix >= i.width) return; 11272 if(iy >= i.height) return; 11273 if(ix + w > i.width) w = i.width - ix; 11274 if(iy + h > i.height) h = i.height - iy; 11275 if(i.usingXshm) 11276 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 11277 else 11278 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 11279 } 11280 11281 void drawPixmap(Sprite s, int x, int y, int ix, int iy, int w, int h) { 11282 if(s.enableAlpha) { 11283 // the Sprite must be created first, meaning if we're here, XRender is already loaded 11284 if(this.xrenderPicturePainter == None) { 11285 XRenderPictureAttributes attrs; 11286 // FIXME: I can prolly reuse this as long as the pixmap itself is valid. 11287 xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs); 11288 } 11289 11290 XRenderComposite( 11291 display, 11292 3, // PicOpOver 11293 s.xrenderPicture, 11294 None, 11295 this.xrenderPicturePainter, 11296 ix, 11297 iy, 11298 0, 11299 0, 11300 x, 11301 y, 11302 w ? w : s.width, 11303 h ? h : s.height 11304 ); 11305 } else { 11306 XCopyArea(display, s.handle, d, gc, ix, iy, w ? w : s.width, h ? h : s.height, x, y); 11307 } 11308 } 11309 11310 int fontHeight() { 11311 version(with_xft) 11312 if(xftFont !is null) 11313 return xftFont.height; 11314 if(font) 11315 return font.max_bounds.ascent + font.max_bounds.descent; 11316 return 12; // pretty common default... 11317 } 11318 11319 int textWidth(in char[] line) { 11320 version(with_xft) 11321 if(xftFont) { 11322 if(line.length == 0) 11323 return 0; 11324 XGlyphInfo extents; 11325 XftTextExtentsUtf8(display, xftFont, line.ptr, cast(int) line.length, &extents); 11326 return extents.width; 11327 } 11328 11329 if(fontset) { 11330 if(line.length == 0) 11331 return 0; 11332 XRectangle rect; 11333 Xutf8TextExtents(fontset, line.ptr, cast(int) line.length, null, &rect); 11334 11335 return rect.width; 11336 } 11337 11338 if(font) 11339 // FIXME: unicode 11340 return XTextWidth( font, line.ptr, cast(int) line.length); 11341 else 11342 return fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 11343 } 11344 11345 Size textSize(in char[] text) { 11346 auto maxWidth = 0; 11347 auto lineHeight = fontHeight; 11348 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 11349 foreach(line; text.split('\n')) { 11350 int textWidth = this.textWidth(line); 11351 if(textWidth > maxWidth) 11352 maxWidth = textWidth; 11353 h += lineHeight + 4; 11354 } 11355 return Size(maxWidth, h); 11356 } 11357 11358 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 11359 const(char)[] text; 11360 version(with_xft) 11361 if(xftFont) { 11362 text = originalText; 11363 goto loaded; 11364 } 11365 11366 if(fontset) 11367 text = originalText; 11368 else { 11369 text.reserve(originalText.length); 11370 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 11371 // then strip the rest so there isn't garbage 11372 foreach(dchar ch; originalText) 11373 if(ch < 256) 11374 text ~= cast(ubyte) ch; 11375 else 11376 text ~= 191; // FIXME: using a random character (upside down question mark) to fill the space 11377 } 11378 loaded: 11379 if(text.length == 0) 11380 return; 11381 11382 // FIXME: should we clip it to the bounding box? 11383 int textHeight = fontHeight; 11384 11385 auto lines = text.split('\n'); 11386 11387 const lineHeight = textHeight; 11388 textHeight *= lines.length; 11389 11390 int cy = y; 11391 11392 if(alignment & TextAlignment.VerticalBottom) { 11393 assert(y2); 11394 auto h = y2 - y; 11395 if(h > textHeight) { 11396 cy += h - textHeight; 11397 cy -= lineHeight / 2; 11398 } 11399 } else if(alignment & TextAlignment.VerticalCenter) { 11400 assert(y2); 11401 auto h = y2 - y; 11402 if(textHeight < h) { 11403 cy += (h - textHeight) / 2; 11404 //cy -= lineHeight / 4; 11405 } 11406 } 11407 11408 foreach(line; text.split('\n')) { 11409 int textWidth = this.textWidth(line); 11410 11411 int px = x, py = cy; 11412 11413 if(alignment & TextAlignment.Center) { 11414 assert(x2); 11415 auto w = x2 - x; 11416 if(w > textWidth) 11417 px += (w - textWidth) / 2; 11418 } else if(alignment & TextAlignment.Right) { 11419 assert(x2); 11420 auto pos = x2 - textWidth; 11421 if(pos > x) 11422 px = pos; 11423 } 11424 11425 version(with_xft) 11426 if(xftFont) { 11427 XftDrawStringUtf8(xftDraw, &xftColor, xftFont, px, py + xftFont.ascent, line.ptr, cast(int) line.length); 11428 11429 goto carry_on; 11430 } 11431 11432 if(fontset) 11433 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 11434 11435 else 11436 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 11437 carry_on: 11438 cy += lineHeight + 4; 11439 } 11440 } 11441 11442 void drawPixel(int x, int y) { 11443 XDrawPoint(display, d, gc, x, y); 11444 } 11445 11446 // The basic shapes, outlined 11447 11448 void drawLine(int x1, int y1, int x2, int y2) { 11449 if(foregroundIsNotTransparent) 11450 XDrawLine(display, d, gc, x1, y1, x2, y2); 11451 } 11452 11453 void drawRectangle(int x, int y, int width, int height) { 11454 if(backgroundIsNotTransparent) { 11455 swapColors(); 11456 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 11457 swapColors(); 11458 } 11459 // 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 11460 if(foregroundIsNotTransparent) 11461 XDrawRectangle(display, d, gc, x + _activePen.width / 2, y + _activePen.width / 2, width - 1 - _activePen.width / 2, height - 1 - _activePen.width / 2); 11462 } 11463 11464 /// Arguments are the points of the bounding rectangle 11465 void drawEllipse(int x1, int y1, int x2, int y2) { 11466 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 11467 } 11468 11469 // NOTE: start and finish are in units of degrees * 64 11470 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 11471 if(backgroundIsNotTransparent) { 11472 swapColors(); 11473 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 11474 swapColors(); 11475 } 11476 if(foregroundIsNotTransparent) 11477 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 11478 } 11479 11480 void drawPolygon(Point[] vertexes) { 11481 XPoint[16] pointsBuffer; 11482 XPoint[] points; 11483 if(vertexes.length <= pointsBuffer.length) 11484 points = pointsBuffer[0 .. vertexes.length]; 11485 else 11486 points.length = vertexes.length; 11487 11488 foreach(i, p; vertexes) { 11489 points[i].x = cast(short) p.x; 11490 points[i].y = cast(short) p.y; 11491 } 11492 11493 if(backgroundIsNotTransparent) { 11494 swapColors(); 11495 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 11496 swapColors(); 11497 } 11498 if(foregroundIsNotTransparent) { 11499 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 11500 } 11501 } 11502 } 11503 11504 /* XRender { */ 11505 11506 struct XRenderColor { 11507 ushort red; 11508 ushort green; 11509 ushort blue; 11510 ushort alpha; 11511 } 11512 11513 alias Picture = XID; 11514 alias PictFormat = XID; 11515 11516 struct XGlyphInfo { 11517 ushort width; 11518 ushort height; 11519 short x; 11520 short y; 11521 short xOff; 11522 short yOff; 11523 } 11524 11525 struct XRenderDirectFormat { 11526 short red; 11527 short redMask; 11528 short green; 11529 short greenMask; 11530 short blue; 11531 short blueMask; 11532 short alpha; 11533 short alphaMask; 11534 } 11535 11536 struct XRenderPictFormat { 11537 PictFormat id; 11538 int type; 11539 int depth; 11540 XRenderDirectFormat direct; 11541 Colormap colormap; 11542 } 11543 11544 enum PictFormatID = (1 << 0); 11545 enum PictFormatType = (1 << 1); 11546 enum PictFormatDepth = (1 << 2); 11547 enum PictFormatRed = (1 << 3); 11548 enum PictFormatRedMask =(1 << 4); 11549 enum PictFormatGreen = (1 << 5); 11550 enum PictFormatGreenMask=(1 << 6); 11551 enum PictFormatBlue = (1 << 7); 11552 enum PictFormatBlueMask =(1 << 8); 11553 enum PictFormatAlpha = (1 << 9); 11554 enum PictFormatAlphaMask=(1 << 10); 11555 enum PictFormatColormap =(1 << 11); 11556 11557 struct XRenderPictureAttributes { 11558 int repeat; 11559 Picture alpha_map; 11560 int alpha_x_origin; 11561 int alpha_y_origin; 11562 int clip_x_origin; 11563 int clip_y_origin; 11564 Pixmap clip_mask; 11565 Bool graphics_exposures; 11566 int subwindow_mode; 11567 int poly_edge; 11568 int poly_mode; 11569 Atom dither; 11570 Bool component_alpha; 11571 } 11572 11573 alias int XFixed; 11574 11575 struct XPointFixed { 11576 XFixed x, y; 11577 } 11578 11579 struct XCircle { 11580 XFixed x; 11581 XFixed y; 11582 XFixed radius; 11583 } 11584 11585 struct XTransform { 11586 XFixed[3][3] matrix; 11587 } 11588 11589 struct XFilters { 11590 int nfilter; 11591 char **filter; 11592 int nalias; 11593 short *alias_; 11594 } 11595 11596 struct XIndexValue { 11597 c_ulong pixel; 11598 ushort red, green, blue, alpha; 11599 } 11600 11601 struct XAnimCursor { 11602 Cursor cursor; 11603 c_ulong delay; 11604 } 11605 11606 struct XLinearGradient { 11607 XPointFixed p1; 11608 XPointFixed p2; 11609 } 11610 11611 struct XRadialGradient { 11612 XCircle inner; 11613 XCircle outer; 11614 } 11615 11616 struct XConicalGradient { 11617 XPointFixed center; 11618 XFixed angle; /* in degrees */ 11619 } 11620 11621 enum PictStandardARGB32 = 0; 11622 enum PictStandardRGB24 = 1; 11623 enum PictStandardA8 = 2; 11624 enum PictStandardA4 = 3; 11625 enum PictStandardA1 = 4; 11626 enum PictStandardNUM = 5; 11627 11628 interface XRender { 11629 extern(C) @nogc: 11630 11631 Bool XRenderQueryExtension (Display *dpy, int *event_basep, int *error_basep); 11632 11633 Status XRenderQueryVersion (Display *dpy, 11634 int *major_versionp, 11635 int *minor_versionp); 11636 11637 Status XRenderQueryFormats (Display *dpy); 11638 11639 int XRenderQuerySubpixelOrder (Display *dpy, int screen); 11640 11641 Bool XRenderSetSubpixelOrder (Display *dpy, int screen, int subpixel); 11642 11643 XRenderPictFormat * 11644 XRenderFindVisualFormat (Display *dpy, const Visual *visual); 11645 11646 XRenderPictFormat * 11647 XRenderFindFormat (Display *dpy, 11648 c_ulong mask, 11649 const XRenderPictFormat *templ, 11650 int count); 11651 XRenderPictFormat * 11652 XRenderFindStandardFormat (Display *dpy, 11653 int format); 11654 11655 XIndexValue * 11656 XRenderQueryPictIndexValues(Display *dpy, 11657 const XRenderPictFormat *format, 11658 int *num); 11659 11660 Picture XRenderCreatePicture( 11661 Display *dpy, 11662 Drawable drawable, 11663 const XRenderPictFormat *format, 11664 c_ulong valuemask, 11665 const XRenderPictureAttributes *attributes); 11666 11667 void XRenderChangePicture (Display *dpy, 11668 Picture picture, 11669 c_ulong valuemask, 11670 const XRenderPictureAttributes *attributes); 11671 11672 void 11673 XRenderSetPictureClipRectangles (Display *dpy, 11674 Picture picture, 11675 int xOrigin, 11676 int yOrigin, 11677 const XRectangle *rects, 11678 int n); 11679 11680 void 11681 XRenderSetPictureClipRegion (Display *dpy, 11682 Picture picture, 11683 Region r); 11684 11685 void 11686 XRenderSetPictureTransform (Display *dpy, 11687 Picture picture, 11688 XTransform *transform); 11689 11690 void 11691 XRenderFreePicture (Display *dpy, 11692 Picture picture); 11693 11694 void 11695 XRenderComposite (Display *dpy, 11696 int op, 11697 Picture src, 11698 Picture mask, 11699 Picture dst, 11700 int src_x, 11701 int src_y, 11702 int mask_x, 11703 int mask_y, 11704 int dst_x, 11705 int dst_y, 11706 uint width, 11707 uint height); 11708 11709 11710 Picture XRenderCreateSolidFill (Display *dpy, 11711 const XRenderColor *color); 11712 11713 Picture XRenderCreateLinearGradient (Display *dpy, 11714 const XLinearGradient *gradient, 11715 const XFixed *stops, 11716 const XRenderColor *colors, 11717 int nstops); 11718 11719 Picture XRenderCreateRadialGradient (Display *dpy, 11720 const XRadialGradient *gradient, 11721 const XFixed *stops, 11722 const XRenderColor *colors, 11723 int nstops); 11724 11725 Picture XRenderCreateConicalGradient (Display *dpy, 11726 const XConicalGradient *gradient, 11727 const XFixed *stops, 11728 const XRenderColor *colors, 11729 int nstops); 11730 11731 11732 11733 Cursor 11734 XRenderCreateCursor (Display *dpy, 11735 Picture source, 11736 uint x, 11737 uint y); 11738 11739 XFilters * 11740 XRenderQueryFilters (Display *dpy, Drawable drawable); 11741 11742 void 11743 XRenderSetPictureFilter (Display *dpy, 11744 Picture picture, 11745 const char *filter, 11746 XFixed *params, 11747 int nparams); 11748 11749 Cursor 11750 XRenderCreateAnimCursor (Display *dpy, 11751 int ncursor, 11752 XAnimCursor *cursors); 11753 } 11754 11755 mixin DynamicLoad!(XRender, "Xrender", 1, false, true) XRenderLibrary; 11756 11757 11758 11759 /* XRender } */ 11760 11761 /* Xft { */ 11762 11763 // actually freetype 11764 alias void FT_Face; 11765 11766 // actually fontconfig 11767 private alias FcBool = int; 11768 alias void FcCharSet; 11769 alias void FcPattern; 11770 alias void FcResult; 11771 enum FcEndian { FcEndianBig, FcEndianLittle } 11772 struct FcFontSet { 11773 int nfont; 11774 int sfont; 11775 FcPattern** fonts; 11776 } 11777 11778 // actually XRegion 11779 struct BOX { 11780 short x1, x2, y1, y2; 11781 } 11782 struct _XRegion { 11783 c_long size; 11784 c_long numRects; 11785 BOX* rects; 11786 BOX extents; 11787 } 11788 11789 alias Region = _XRegion*; 11790 11791 // ok actually Xft 11792 11793 struct XftFontInfo; 11794 11795 struct XftFont { 11796 int ascent; 11797 int descent; 11798 int height; 11799 int max_advance_width; 11800 FcCharSet* charset; 11801 FcPattern* pattern; 11802 } 11803 11804 struct XftDraw; 11805 11806 struct XftColor { 11807 c_ulong pixel; 11808 XRenderColor color; 11809 } 11810 11811 struct XftCharSpec { 11812 dchar ucs4; 11813 short x; 11814 short y; 11815 } 11816 11817 struct XftCharFontSpec { 11818 XftFont *font; 11819 dchar ucs4; 11820 short x; 11821 short y; 11822 } 11823 11824 struct XftGlyphSpec { 11825 uint glyph; 11826 short x; 11827 short y; 11828 } 11829 11830 struct XftGlyphFontSpec { 11831 XftFont *font; 11832 uint glyph; 11833 short x; 11834 short y; 11835 } 11836 11837 interface Xft { 11838 extern(C) @nogc pure: 11839 11840 Bool XftColorAllocName (Display *dpy, 11841 const Visual *visual, 11842 Colormap cmap, 11843 const char *name, 11844 XftColor *result); 11845 11846 Bool XftColorAllocValue (Display *dpy, 11847 Visual *visual, 11848 Colormap cmap, 11849 const XRenderColor *color, 11850 XftColor *result); 11851 11852 void XftColorFree (Display *dpy, 11853 Visual *visual, 11854 Colormap cmap, 11855 XftColor *color); 11856 11857 Bool XftDefaultHasRender (Display *dpy); 11858 11859 Bool XftDefaultSet (Display *dpy, FcPattern *defaults); 11860 11861 void XftDefaultSubstitute (Display *dpy, int screen, FcPattern *pattern); 11862 11863 XftDraw * XftDrawCreate (Display *dpy, 11864 Drawable drawable, 11865 Visual *visual, 11866 Colormap colormap); 11867 11868 XftDraw * XftDrawCreateBitmap (Display *dpy, 11869 Pixmap bitmap); 11870 11871 XftDraw * XftDrawCreateAlpha (Display *dpy, 11872 Pixmap pixmap, 11873 int depth); 11874 11875 void XftDrawChange (XftDraw *draw, 11876 Drawable drawable); 11877 11878 Display * XftDrawDisplay (XftDraw *draw); 11879 11880 Drawable XftDrawDrawable (XftDraw *draw); 11881 11882 Colormap XftDrawColormap (XftDraw *draw); 11883 11884 Visual * XftDrawVisual (XftDraw *draw); 11885 11886 void XftDrawDestroy (XftDraw *draw); 11887 11888 Picture XftDrawPicture (XftDraw *draw); 11889 11890 Picture XftDrawSrcPicture (XftDraw *draw, const XftColor *color); 11891 11892 void XftDrawGlyphs (XftDraw *draw, 11893 const XftColor *color, 11894 XftFont *pub, 11895 int x, 11896 int y, 11897 const uint *glyphs, 11898 int nglyphs); 11899 11900 void XftDrawString8 (XftDraw *draw, 11901 const XftColor *color, 11902 XftFont *pub, 11903 int x, 11904 int y, 11905 const char *string, 11906 int len); 11907 11908 void XftDrawString16 (XftDraw *draw, 11909 const XftColor *color, 11910 XftFont *pub, 11911 int x, 11912 int y, 11913 const wchar *string, 11914 int len); 11915 11916 void XftDrawString32 (XftDraw *draw, 11917 const XftColor *color, 11918 XftFont *pub, 11919 int x, 11920 int y, 11921 const dchar *string, 11922 int len); 11923 11924 void XftDrawStringUtf8 (XftDraw *draw, 11925 const XftColor *color, 11926 XftFont *pub, 11927 int x, 11928 int y, 11929 const char *string, 11930 int len); 11931 void XftDrawStringUtf16 (XftDraw *draw, 11932 const XftColor *color, 11933 XftFont *pub, 11934 int x, 11935 int y, 11936 const char *string, 11937 FcEndian endian, 11938 int len); 11939 11940 void XftDrawCharSpec (XftDraw *draw, 11941 const XftColor *color, 11942 XftFont *pub, 11943 const XftCharSpec *chars, 11944 int len); 11945 11946 void XftDrawCharFontSpec (XftDraw *draw, 11947 const XftColor *color, 11948 const XftCharFontSpec *chars, 11949 int len); 11950 11951 void XftDrawGlyphSpec (XftDraw *draw, 11952 const XftColor *color, 11953 XftFont *pub, 11954 const XftGlyphSpec *glyphs, 11955 int len); 11956 11957 void XftDrawGlyphFontSpec (XftDraw *draw, 11958 const XftColor *color, 11959 const XftGlyphFontSpec *glyphs, 11960 int len); 11961 11962 void XftDrawRect (XftDraw *draw, 11963 const XftColor *color, 11964 int x, 11965 int y, 11966 uint width, 11967 uint height); 11968 11969 Bool XftDrawSetClip (XftDraw *draw, 11970 Region r); 11971 11972 11973 Bool XftDrawSetClipRectangles (XftDraw *draw, 11974 int xOrigin, 11975 int yOrigin, 11976 const XRectangle *rects, 11977 int n); 11978 11979 void XftDrawSetSubwindowMode (XftDraw *draw, 11980 int mode); 11981 11982 void XftGlyphExtents (Display *dpy, 11983 XftFont *pub, 11984 const uint *glyphs, 11985 int nglyphs, 11986 XGlyphInfo *extents); 11987 11988 void XftTextExtents8 (Display *dpy, 11989 XftFont *pub, 11990 const char *string, 11991 int len, 11992 XGlyphInfo *extents); 11993 11994 void XftTextExtents16 (Display *dpy, 11995 XftFont *pub, 11996 const wchar *string, 11997 int len, 11998 XGlyphInfo *extents); 11999 12000 void XftTextExtents32 (Display *dpy, 12001 XftFont *pub, 12002 const dchar *string, 12003 int len, 12004 XGlyphInfo *extents); 12005 12006 void XftTextExtentsUtf8 (Display *dpy, 12007 XftFont *pub, 12008 const char *string, 12009 int len, 12010 XGlyphInfo *extents); 12011 12012 void XftTextExtentsUtf16 (Display *dpy, 12013 XftFont *pub, 12014 const char *string, 12015 FcEndian endian, 12016 int len, 12017 XGlyphInfo *extents); 12018 12019 FcPattern * XftFontMatch (Display *dpy, 12020 int screen, 12021 const FcPattern *pattern, 12022 FcResult *result); 12023 12024 XftFont * XftFontOpen (Display *dpy, int screen, ...); 12025 12026 XftFont * XftFontOpenName (Display *dpy, int screen, const char *name); 12027 12028 XftFont * XftFontOpenXlfd (Display *dpy, int screen, const char *xlfd); 12029 12030 FT_Face XftLockFace (XftFont *pub); 12031 12032 void XftUnlockFace (XftFont *pub); 12033 12034 XftFontInfo * XftFontInfoCreate (Display *dpy, const FcPattern *pattern); 12035 12036 void XftFontInfoDestroy (Display *dpy, XftFontInfo *fi); 12037 12038 dchar XftFontInfoHash (const XftFontInfo *fi); 12039 12040 FcBool XftFontInfoEqual (const XftFontInfo *a, const XftFontInfo *b); 12041 12042 XftFont * XftFontOpenInfo (Display *dpy, 12043 FcPattern *pattern, 12044 XftFontInfo *fi); 12045 12046 XftFont * XftFontOpenPattern (Display *dpy, FcPattern *pattern); 12047 12048 XftFont * XftFontCopy (Display *dpy, XftFont *pub); 12049 12050 void XftFontClose (Display *dpy, XftFont *pub); 12051 12052 FcBool XftInitFtLibrary(); 12053 void XftFontLoadGlyphs (Display *dpy, 12054 XftFont *pub, 12055 FcBool need_bitmaps, 12056 const uint *glyphs, 12057 int nglyph); 12058 12059 void XftFontUnloadGlyphs (Display *dpy, 12060 XftFont *pub, 12061 const uint *glyphs, 12062 int nglyph); 12063 12064 FcBool XftFontCheckGlyph (Display *dpy, 12065 XftFont *pub, 12066 FcBool need_bitmaps, 12067 uint glyph, 12068 uint *missing, 12069 int *nmissing); 12070 12071 FcBool XftCharExists (Display *dpy, 12072 XftFont *pub, 12073 dchar ucs4); 12074 12075 uint XftCharIndex (Display *dpy, 12076 XftFont *pub, 12077 dchar ucs4); 12078 FcBool XftInit (const char *config); 12079 12080 int XftGetVersion (); 12081 12082 FcFontSet * XftListFonts (Display *dpy, 12083 int screen, 12084 ...); 12085 12086 FcPattern *XftNameParse (const char *name); 12087 12088 void XftGlyphRender (Display *dpy, 12089 int op, 12090 Picture src, 12091 XftFont *pub, 12092 Picture dst, 12093 int srcx, 12094 int srcy, 12095 int x, 12096 int y, 12097 const uint *glyphs, 12098 int nglyphs); 12099 12100 void XftGlyphSpecRender (Display *dpy, 12101 int op, 12102 Picture src, 12103 XftFont *pub, 12104 Picture dst, 12105 int srcx, 12106 int srcy, 12107 const XftGlyphSpec *glyphs, 12108 int nglyphs); 12109 12110 void XftCharSpecRender (Display *dpy, 12111 int op, 12112 Picture src, 12113 XftFont *pub, 12114 Picture dst, 12115 int srcx, 12116 int srcy, 12117 const XftCharSpec *chars, 12118 int len); 12119 void XftGlyphFontSpecRender (Display *dpy, 12120 int op, 12121 Picture src, 12122 Picture dst, 12123 int srcx, 12124 int srcy, 12125 const XftGlyphFontSpec *glyphs, 12126 int nglyphs); 12127 12128 void XftCharFontSpecRender (Display *dpy, 12129 int op, 12130 Picture src, 12131 Picture dst, 12132 int srcx, 12133 int srcy, 12134 const XftCharFontSpec *chars, 12135 int len); 12136 12137 void XftTextRender8 (Display *dpy, 12138 int op, 12139 Picture src, 12140 XftFont *pub, 12141 Picture dst, 12142 int srcx, 12143 int srcy, 12144 int x, 12145 int y, 12146 const char *string, 12147 int len); 12148 void XftTextRender16 (Display *dpy, 12149 int op, 12150 Picture src, 12151 XftFont *pub, 12152 Picture dst, 12153 int srcx, 12154 int srcy, 12155 int x, 12156 int y, 12157 const wchar *string, 12158 int len); 12159 12160 void XftTextRender16BE (Display *dpy, 12161 int op, 12162 Picture src, 12163 XftFont *pub, 12164 Picture dst, 12165 int srcx, 12166 int srcy, 12167 int x, 12168 int y, 12169 const char *string, 12170 int len); 12171 12172 void XftTextRender16LE (Display *dpy, 12173 int op, 12174 Picture src, 12175 XftFont *pub, 12176 Picture dst, 12177 int srcx, 12178 int srcy, 12179 int x, 12180 int y, 12181 const char *string, 12182 int len); 12183 12184 void XftTextRender32 (Display *dpy, 12185 int op, 12186 Picture src, 12187 XftFont *pub, 12188 Picture dst, 12189 int srcx, 12190 int srcy, 12191 int x, 12192 int y, 12193 const dchar *string, 12194 int len); 12195 12196 void XftTextRender32BE (Display *dpy, 12197 int op, 12198 Picture src, 12199 XftFont *pub, 12200 Picture dst, 12201 int srcx, 12202 int srcy, 12203 int x, 12204 int y, 12205 const char *string, 12206 int len); 12207 12208 void XftTextRender32LE (Display *dpy, 12209 int op, 12210 Picture src, 12211 XftFont *pub, 12212 Picture dst, 12213 int srcx, 12214 int srcy, 12215 int x, 12216 int y, 12217 const char *string, 12218 int len); 12219 12220 void XftTextRenderUtf8 (Display *dpy, 12221 int op, 12222 Picture src, 12223 XftFont *pub, 12224 Picture dst, 12225 int srcx, 12226 int srcy, 12227 int x, 12228 int y, 12229 const char *string, 12230 int len); 12231 12232 void XftTextRenderUtf16 (Display *dpy, 12233 int op, 12234 Picture src, 12235 XftFont *pub, 12236 Picture dst, 12237 int srcx, 12238 int srcy, 12239 int x, 12240 int y, 12241 const char *string, 12242 FcEndian endian, 12243 int len); 12244 FcPattern * XftXlfdParse (const char *xlfd_orig, Bool ignore_scalable, Bool complete); 12245 12246 } 12247 12248 interface FontConfig { 12249 extern(C) @nogc pure: 12250 int FcPatternGetString(const FcPattern *p, const char *object, int n, char ** s); 12251 void FcFontSetDestroy(FcFontSet*); 12252 char* FcNameUnparse(const FcPattern *); 12253 } 12254 12255 mixin DynamicLoad!(Xft, "Xft", 2) XftLibrary; 12256 mixin DynamicLoad!(FontConfig, "fontconfig", 1) FontConfigLibrary; 12257 12258 12259 /* Xft } */ 12260 12261 class XDisconnectException : Exception { 12262 bool userRequested; 12263 this(bool userRequested = true) { 12264 this.userRequested = userRequested; 12265 super("X disconnected"); 12266 } 12267 } 12268 12269 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a Display* 12270 class XDisplayConnection { 12271 private __gshared Display* display; 12272 private __gshared XIM xim; 12273 private __gshared char* displayName; 12274 12275 private __gshared int connectionSequence_; 12276 private __gshared bool isLocal_; 12277 12278 /// use this for lazy caching when reconnection 12279 static int connectionSequenceNumber() { return connectionSequence_; } 12280 12281 /++ 12282 Guesses if the connection appears to be local. 12283 12284 History: 12285 Added June 3, 2021 12286 +/ 12287 static @property bool isLocal() nothrow @trusted @nogc { 12288 return isLocal_; 12289 } 12290 12291 /// Attempts recreation of state, may require application assistance 12292 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 12293 /// then call this, and if successful, reenter the loop. 12294 static void discardAndRecreate(string newDisplayString = null) { 12295 if(insideXEventLoop) 12296 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 12297 12298 // 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 12299 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 12300 12301 foreach(handle; chnenhm) { 12302 handle.discardConnectionState(); 12303 } 12304 12305 discardState(); 12306 12307 if(newDisplayString !is null) 12308 setDisplayName(newDisplayString); 12309 12310 auto display = get(); 12311 12312 foreach(handle; chnenhm) { 12313 handle.recreateAfterDisconnect(); 12314 } 12315 } 12316 12317 private __gshared EventMask rootEventMask; 12318 12319 /++ 12320 Requests the specified input from the root window on the connection, in addition to any other request. 12321 12322 12323 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. 12324 12325 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 12326 +/ 12327 static void addRootInput(EventMask mask) { 12328 auto old = rootEventMask; 12329 rootEventMask |= mask; 12330 get(); // to ensure display connected 12331 if(display !is null && rootEventMask != old) 12332 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 12333 } 12334 12335 static void discardState() { 12336 freeImages(); 12337 12338 foreach(atomPtr; interredAtoms) 12339 *atomPtr = 0; 12340 interredAtoms = null; 12341 interredAtoms.assumeSafeAppend(); 12342 12343 ScreenPainterImplementation.fontAttempted = false; 12344 ScreenPainterImplementation.defaultfont = null; 12345 ScreenPainterImplementation.defaultfontset = null; 12346 12347 Image.impl.xshmQueryCompleted = false; 12348 Image.impl._xshmAvailable = false; 12349 12350 SimpleWindow.nativeMapping = null; 12351 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 12352 // GlobalHotkeyManager 12353 12354 display = null; 12355 xim = null; 12356 } 12357 12358 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 12359 private static void createXIM () { 12360 import core.stdc.locale : setlocale, LC_ALL; 12361 import core.stdc.stdio : stderr, fprintf; 12362 import core.stdc.stdlib : free; 12363 import core.stdc.string : strdup; 12364 12365 static immutable string[3] mtry = [ null, "@im=local", "@im=" ]; 12366 12367 auto olocale = strdup(setlocale(LC_ALL, null)); 12368 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 12369 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 12370 12371 //fprintf(stderr, "opening IM...\n"); 12372 foreach (string s; mtry) { 12373 if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 12374 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 12375 } 12376 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 12377 } 12378 12379 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 12380 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 12381 static struct ImgList { 12382 size_t img; // class; hide it from GC 12383 ImgList* next; 12384 } 12385 12386 static __gshared ImgList* imglist = null; 12387 static __gshared bool imglistLocked = false; // true: don't register and unregister images 12388 12389 static void registerImage (Image img) { 12390 if (!imglistLocked && img !is null) { 12391 import core.stdc.stdlib : malloc; 12392 auto it = cast(ImgList*)malloc(ImgList.sizeof); 12393 assert(it !is null); // do proper checks 12394 it.img = cast(size_t)cast(void*)img; 12395 it.next = imglist; 12396 imglist = it; 12397 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 12398 } 12399 } 12400 12401 static void unregisterImage (Image img) { 12402 if (!imglistLocked && img !is null) { 12403 import core.stdc.stdlib : free; 12404 ImgList* prev = null; 12405 ImgList* cur = imglist; 12406 while (cur !is null) { 12407 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 12408 prev = cur; 12409 cur = cur.next; 12410 } 12411 if (cur !is null) { 12412 if (prev is null) imglist = cur.next; else prev.next = cur.next; 12413 free(cur); 12414 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 12415 } else { 12416 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 12417 } 12418 } 12419 } 12420 12421 static void freeImages () { // needed for discardAndRecreate 12422 imglistLocked = true; 12423 scope(exit) imglistLocked = false; 12424 ImgList* cur = imglist; 12425 ImgList* next = null; 12426 while (cur !is null) { 12427 import core.stdc.stdlib : free; 12428 next = cur.next; 12429 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 12430 (cast(Image)cast(void*)cur.img).dispose(); 12431 free(cur); 12432 cur = next; 12433 } 12434 imglist = null; 12435 } 12436 12437 /// can be used to override normal handling of display name 12438 /// from environment and/or command line 12439 static setDisplayName(string newDisplayName) { 12440 displayName = cast(char*) (newDisplayName ~ '\0'); 12441 } 12442 12443 /// resets to the default display string 12444 static resetDisplayName() { 12445 displayName = null; 12446 } 12447 12448 /// 12449 static Display* get() { 12450 if(display is null) { 12451 if(!librariesSuccessfullyLoaded) 12452 throw new Exception("Unable to load X11 client libraries"); 12453 display = XOpenDisplay(displayName); 12454 12455 isLocal_ = false; 12456 12457 connectionSequence_++; 12458 if(display is null) 12459 throw new Exception("Unable to open X display"); 12460 12461 auto str = display.display_name; 12462 // this is a bit of a hack but like if it looks like a unix socket we assume it is local 12463 // and otherwise it probably isn't 12464 if(str is null || (str[0] != ':' && str[0] != '/')) 12465 isLocal_ = false; 12466 else 12467 isLocal_ = true; 12468 12469 //XSetErrorHandler(&adrlogger); 12470 //XSynchronize(display, true); 12471 12472 12473 XSetIOErrorHandler(&x11ioerrCB); 12474 Bool sup; 12475 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 12476 createXIM(); 12477 version(with_eventloop) { 12478 import arsd.eventloop; 12479 addFileEventListeners(display.fd, &eventListener, null, null); 12480 } 12481 } 12482 12483 return display; 12484 } 12485 12486 extern(C) 12487 static int x11ioerrCB(Display* dpy) { 12488 throw new XDisconnectException(false); 12489 } 12490 12491 version(with_eventloop) { 12492 import arsd.eventloop; 12493 static void eventListener(OsFileHandle fd) { 12494 //this.mtLock(); 12495 //scope(exit) this.mtUnlock(); 12496 while(XPending(display)) 12497 doXNextEvent(display); 12498 } 12499 } 12500 12501 // close connection on program exit -- we need this to properly free all images 12502 static ~this () { 12503 // the gui thread must clean up after itself or else Xlib might deadlock 12504 // using this flag on any thread destruction is the easiest way i know of 12505 // (shared static this is run by the LAST thread to exit, which may not be 12506 // the gui thread, and normal static this run by ALL threads, so we gotta check.) 12507 if(thisIsGuiThread) 12508 close(); 12509 } 12510 12511 /// 12512 static void close() { 12513 if(display is null) 12514 return; 12515 12516 version(with_eventloop) { 12517 import arsd.eventloop; 12518 removeFileEventListeners(display.fd); 12519 } 12520 12521 // now remove all registered images to prevent shared memory leaks 12522 freeImages(); 12523 12524 // tbh I don't know why it is doing this but like if this happens to run 12525 // from the other thread there's frequent hanging inside here. 12526 if(thisIsGuiThread) 12527 XCloseDisplay(display); 12528 display = null; 12529 } 12530 } 12531 12532 mixin template NativeImageImplementation() { 12533 XImage* handle; 12534 ubyte* rawData; 12535 12536 XShmSegmentInfo shminfo; 12537 12538 __gshared bool xshmQueryCompleted; 12539 __gshared bool _xshmAvailable; 12540 public static @property bool xshmAvailable() { 12541 if(!xshmQueryCompleted) { 12542 int i1, i2, i3; 12543 xshmQueryCompleted = true; 12544 12545 if(!XDisplayConnection.isLocal) 12546 _xshmAvailable = false; 12547 else 12548 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 12549 } 12550 return _xshmAvailable; 12551 } 12552 12553 bool usingXshm; 12554 final: 12555 12556 private __gshared bool xshmfailed; 12557 12558 void createImage(int width, int height, bool forcexshm=false, bool enableAlpha = false) { 12559 auto display = XDisplayConnection.get(); 12560 assert(display !is null); 12561 auto screen = DefaultScreen(display); 12562 12563 // it will only use shared memory for somewhat largish images, 12564 // since otherwise we risk wasting shared memory handles on a lot of little ones 12565 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 12566 12567 12568 // it is possible for the query extension to return true, the DISPLAY check to pass, yet 12569 // the actual use still fails. For example, if the program is in a container and permission denied 12570 // on shared memory, or if it is a local thing forwarded to a remote server, etc. 12571 // 12572 // If it does fail, we need to detect it now, abort the xshm and fall back to core protocol. 12573 12574 12575 // synchronize so preexisting buffers are clear 12576 XSync(display, false); 12577 xshmfailed = false; 12578 12579 auto oldErrorHandler = XSetErrorHandler(&XShmErrorHandler); 12580 12581 12582 usingXshm = true; 12583 handle = XShmCreateImage( 12584 display, 12585 DefaultVisual(display, screen), 12586 enableAlpha ? 32: 24, 12587 ImageFormat.ZPixmap, 12588 null, 12589 &shminfo, 12590 width, height); 12591 if(handle is null) 12592 goto abortXshm1; 12593 12594 if(handle.bytes_per_line != 4 * width) 12595 goto abortXshm2; 12596 12597 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 12598 if(shminfo.shmid < 0) 12599 goto abortXshm3; 12600 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 12601 if(rawData == cast(ubyte*) -1) 12602 goto abortXshm4; 12603 shminfo.readOnly = 0; 12604 XShmAttach(display, &shminfo); 12605 12606 // and now to the final error check to ensure it actually worked. 12607 XSync(display, false); 12608 if(xshmfailed) 12609 goto abortXshm5; 12610 12611 XSetErrorHandler(oldErrorHandler); 12612 12613 XDisplayConnection.registerImage(this); 12614 // if I don't flush here there's a chance the dtor will run before the 12615 // ctor and lead to a bad value X error. While this hurts the efficiency 12616 // it is local anyway so prolly better to keep it simple 12617 XFlush(display); 12618 12619 return; 12620 12621 abortXshm5: 12622 shmdt(shminfo.shmaddr); 12623 rawData = null; 12624 12625 abortXshm4: 12626 shmctl(shminfo.shmid, IPC_RMID, null); 12627 12628 abortXshm3: 12629 // nothing needed, the shmget failed so there's nothing to free 12630 12631 abortXshm2: 12632 XDestroyImage(handle); 12633 handle = null; 12634 12635 abortXshm1: 12636 XSetErrorHandler(oldErrorHandler); 12637 usingXshm = false; 12638 handle = null; 12639 12640 shminfo = typeof(shminfo).init; 12641 12642 _xshmAvailable = false; // don't try again in the future 12643 12644 //import std.stdio; writeln("fallingback"); 12645 12646 goto fallback; 12647 12648 } else { 12649 fallback: 12650 12651 if (forcexshm) throw new Exception("can't create XShm Image"); 12652 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 12653 import core.stdc.stdlib : malloc; 12654 rawData = cast(ubyte*) malloc(width * height * 4); 12655 12656 handle = XCreateImage( 12657 display, 12658 DefaultVisual(display, screen), 12659 enableAlpha ? 32 : 24, // bpp 12660 ImageFormat.ZPixmap, 12661 0, // offset 12662 rawData, 12663 width, height, 12664 enableAlpha ? 32 : 8 /* FIXME */, 4 * width); // padding, bytes per line 12665 } 12666 } 12667 12668 void dispose() { 12669 // note: this calls free(rawData) for us 12670 if(handle) { 12671 if (usingXshm) { 12672 XDisplayConnection.unregisterImage(this); 12673 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 12674 } 12675 XDestroyImage(handle); 12676 if(usingXshm) { 12677 shmdt(shminfo.shmaddr); 12678 shmctl(shminfo.shmid, IPC_RMID, null); 12679 } 12680 handle = null; 12681 } 12682 } 12683 12684 Color getPixel(int x, int y) { 12685 auto offset = (y * width + x) * 4; 12686 Color c; 12687 c.a = enableAlpha ? rawData[offset + 3] : 255; 12688 c.b = rawData[offset + 0]; 12689 c.g = rawData[offset + 1]; 12690 c.r = rawData[offset + 2]; 12691 if(enableAlpha) 12692 c.unPremultiply; 12693 return c; 12694 } 12695 12696 void setPixel(int x, int y, Color c) { 12697 if(enableAlpha) 12698 c.premultiply(); 12699 auto offset = (y * width + x) * 4; 12700 rawData[offset + 0] = c.b; 12701 rawData[offset + 1] = c.g; 12702 rawData[offset + 2] = c.r; 12703 if(enableAlpha) 12704 rawData[offset + 3] = c.a; 12705 } 12706 12707 void convertToRgbaBytes(ubyte[] where) { 12708 assert(where.length == this.width * this.height * 4); 12709 12710 // if rawData had a length.... 12711 //assert(rawData.length == where.length); 12712 for(int idx = 0; idx < where.length; idx += 4) { 12713 where[idx + 0] = rawData[idx + 2]; // r 12714 where[idx + 1] = rawData[idx + 1]; // g 12715 where[idx + 2] = rawData[idx + 0]; // b 12716 where[idx + 3] = enableAlpha ? rawData[idx + 3] : 255; // a 12717 12718 if(enableAlpha) 12719 unPremultiplyRgba(where[idx .. idx + 4]); 12720 } 12721 } 12722 12723 void setFromRgbaBytes(in ubyte[] where) { 12724 assert(where.length == this.width * this.height * 4); 12725 12726 // if rawData had a length.... 12727 //assert(rawData.length == where.length); 12728 for(int idx = 0; idx < where.length; idx += 4) { 12729 rawData[idx + 2] = where[idx + 0]; // r 12730 rawData[idx + 1] = where[idx + 1]; // g 12731 rawData[idx + 0] = where[idx + 2]; // b 12732 if(enableAlpha) { 12733 rawData[idx + 3] = where[idx + 3]; // a 12734 premultiplyBgra(rawData[idx .. idx + 4]); 12735 } 12736 } 12737 } 12738 12739 } 12740 12741 mixin template NativeSimpleWindowImplementation() { 12742 GC gc; 12743 Window window; 12744 Display* display; 12745 12746 Pixmap buffer; 12747 int bufferw, bufferh; // size of the buffer; can be bigger than window 12748 XIC xic; // input context 12749 int curHidden = 0; // counter 12750 Cursor blankCurPtr = 0; 12751 int cursorSequenceNumber = 0; 12752 int warpEventCount = 0; // number of mouse movement events to eat 12753 12754 __gshared X11SetSelectionHandler[Atom] setSelectionHandlers; 12755 X11GetSelectionHandler[Atom] getSelectionHandlers; 12756 12757 version(without_opengl) {} else 12758 GLXContext glc; 12759 12760 private void fixFixedSize(bool forced=false) (int width, int height) { 12761 if (forced || this.resizability == Resizability.fixedSize) { 12762 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 12763 XSizeHints sh; 12764 static if (!forced) { 12765 c_long spr; 12766 XGetWMNormalHints(display, window, &sh, &spr); 12767 sh.flags |= PMaxSize | PMinSize; 12768 } else { 12769 sh.flags = PMaxSize | PMinSize; 12770 } 12771 sh.min_width = width; 12772 sh.min_height = height; 12773 sh.max_width = width; 12774 sh.max_height = height; 12775 XSetWMNormalHints(display, window, &sh); 12776 //XFlush(display); 12777 } 12778 } 12779 12780 ScreenPainter getPainter() { 12781 return ScreenPainter(this, window); 12782 } 12783 12784 void move(int x, int y) { 12785 XMoveWindow(display, window, x, y); 12786 } 12787 12788 void resize(int w, int h) { 12789 if (w < 1) w = 1; 12790 if (h < 1) h = 1; 12791 XResizeWindow(display, window, w, h); 12792 12793 // calling this now to avoid waiting for the server to 12794 // acknowledge the resize; draws without returning to the 12795 // event loop will thus actually work. the server's event 12796 // btw might overrule this and resize it again 12797 recordX11Resize(display, this, w, h); 12798 12799 // FIXME: do we need to set this as the opengl context to do the glViewport change? 12800 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 12801 } 12802 12803 void moveResize (int x, int y, int w, int h) { 12804 if (w < 1) w = 1; 12805 if (h < 1) h = 1; 12806 XMoveResizeWindow(display, window, x, y, w, h); 12807 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 12808 } 12809 12810 void hideCursor () { 12811 if (curHidden++ == 0) { 12812 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 12813 static const(char)[1] cmbmp = 0; 12814 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 12815 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 12816 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 12817 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 12818 XFreePixmap(display, pm); 12819 } 12820 XDefineCursor(display, window, blankCurPtr); 12821 } 12822 } 12823 12824 void showCursor () { 12825 if (--curHidden == 0) XUndefineCursor(display, window); 12826 } 12827 12828 void warpMouse (int x, int y) { 12829 // here i will send dummy "ignore next mouse motion" event, 12830 // 'cause `XWarpPointer()` sends synthesised mouse motion, 12831 // and we don't need to report it to the user (as warping is 12832 // used when the user needs movement deltas). 12833 //XClientMessageEvent xclient; 12834 XEvent e; 12835 e.xclient.type = EventType.ClientMessage; 12836 e.xclient.window = window; 12837 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 12838 e.xclient.format = 32; 12839 e.xclient.data.l[0] = 0; 12840 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 12841 //{ 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]); } 12842 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 12843 // now warp pointer... 12844 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 12845 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 12846 // ...and flush 12847 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 12848 XFlush(display); 12849 } 12850 12851 void sendDummyEvent () { 12852 // here i will send dummy event to ping event queue 12853 XEvent e; 12854 e.xclient.type = EventType.ClientMessage; 12855 e.xclient.window = window; 12856 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 12857 e.xclient.format = 32; 12858 e.xclient.data.l[0] = 0; 12859 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 12860 XFlush(display); 12861 } 12862 12863 void setTitle(string title) { 12864 if (title.ptr is null) title = ""; 12865 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 12866 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 12867 XTextProperty windowName; 12868 windowName.value = title.ptr; 12869 windowName.encoding = XA_UTF8; //XA_STRING; 12870 windowName.format = 8; 12871 windowName.nitems = cast(uint)title.length; 12872 XSetWMName(display, window, &windowName); 12873 char[1024] namebuf = 0; 12874 auto maxlen = namebuf.length-1; 12875 if (maxlen > title.length) maxlen = title.length; 12876 namebuf[0..maxlen] = title[0..maxlen]; 12877 XStoreName(display, window, namebuf.ptr); 12878 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 12879 flushGui(); // without this OpenGL windows has a LONG delay before changing title 12880 } 12881 12882 string[] getTitles() { 12883 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 12884 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 12885 XTextProperty textProp; 12886 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 12887 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 12888 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 12889 } else 12890 return []; 12891 } else 12892 return null; 12893 } 12894 12895 string getTitle() { 12896 auto titles = getTitles(); 12897 return titles.length ? titles[0] : null; 12898 } 12899 12900 void setMinSize (int minwidth, int minheight) { 12901 import core.stdc.config : c_long; 12902 if (minwidth < 1) minwidth = 1; 12903 if (minheight < 1) minheight = 1; 12904 XSizeHints sh; 12905 c_long spr; 12906 XGetWMNormalHints(display, window, &sh, &spr); 12907 sh.min_width = minwidth; 12908 sh.min_height = minheight; 12909 sh.flags |= PMinSize; 12910 XSetWMNormalHints(display, window, &sh); 12911 flushGui(); 12912 } 12913 12914 void setMaxSize (int maxwidth, int maxheight) { 12915 import core.stdc.config : c_long; 12916 if (maxwidth < 1) maxwidth = 1; 12917 if (maxheight < 1) maxheight = 1; 12918 XSizeHints sh; 12919 c_long spr; 12920 XGetWMNormalHints(display, window, &sh, &spr); 12921 sh.max_width = maxwidth; 12922 sh.max_height = maxheight; 12923 sh.flags |= PMaxSize; 12924 XSetWMNormalHints(display, window, &sh); 12925 flushGui(); 12926 } 12927 12928 void setResizeGranularity (int granx, int grany) { 12929 import core.stdc.config : c_long; 12930 if (granx < 1) granx = 1; 12931 if (grany < 1) grany = 1; 12932 XSizeHints sh; 12933 c_long spr; 12934 XGetWMNormalHints(display, window, &sh, &spr); 12935 sh.width_inc = granx; 12936 sh.height_inc = grany; 12937 sh.flags |= PResizeInc; 12938 XSetWMNormalHints(display, window, &sh); 12939 flushGui(); 12940 } 12941 12942 void setOpacity (uint opacity) { 12943 arch_ulong o = opacity; 12944 if (opacity == uint.max) 12945 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 12946 else 12947 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 12948 XA_CARDINAL, 32, PropModeReplace, &o, 1); 12949 } 12950 12951 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 12952 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 12953 display = XDisplayConnection.get(); 12954 auto screen = DefaultScreen(display); 12955 12956 version(without_opengl) {} 12957 else { 12958 if(opengl == OpenGlOptions.yes) { 12959 GLXFBConfig fbconf = null; 12960 XVisualInfo* vi = null; 12961 bool useLegacy = false; 12962 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 12963 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 12964 int[23] visualAttribs = [ 12965 GLX_X_RENDERABLE , 1/*True*/, 12966 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 12967 GLX_RENDER_TYPE , GLX_RGBA_BIT, 12968 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 12969 GLX_RED_SIZE , 8, 12970 GLX_GREEN_SIZE , 8, 12971 GLX_BLUE_SIZE , 8, 12972 GLX_ALPHA_SIZE , 8, 12973 GLX_DEPTH_SIZE , 24, 12974 GLX_STENCIL_SIZE , 8, 12975 GLX_DOUBLEBUFFER , 1/*True*/, 12976 0/*None*/, 12977 ]; 12978 int fbcount; 12979 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 12980 if (fbcount == 0) { 12981 useLegacy = true; // try to do at least something 12982 } else { 12983 // pick the FB config/visual with the most samples per pixel 12984 int bestidx = -1, bestns = -1; 12985 foreach (int fbi; 0..fbcount) { 12986 int sb, samples; 12987 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 12988 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 12989 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 12990 } 12991 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 12992 fbconf = fbc[bestidx]; 12993 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 12994 XFree(fbc); 12995 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 12996 } 12997 } 12998 if (vi is null || useLegacy) { 12999 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 13000 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 13001 useLegacy = true; 13002 } 13003 if (vi is null) throw new Exception("no open gl visual found"); 13004 13005 XSetWindowAttributes swa; 13006 auto root = RootWindow(display, screen); 13007 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 13008 13009 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 13010 0, 0, width, height, 13011 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap, &swa); 13012 13013 // now try to use `glXCreateContextAttribsARB()` if it's here 13014 if (!useLegacy) { 13015 // request fairly advanced context, even with stencil buffer! 13016 int[9] contextAttribs = [ 13017 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 13018 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 13019 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 13020 // for modern context, set "forward compatibility" flag too 13021 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 13022 0/*None*/, 13023 ]; 13024 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 13025 if (glc is null && sdpyOpenGLContextAllowFallback) { 13026 sdpyOpenGLContextVersion = 0; 13027 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 13028 } 13029 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 13030 } else { 13031 // fallback to old GLX call 13032 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 13033 sdpyOpenGLContextVersion = 0; 13034 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 13035 } 13036 } 13037 // sync to ensure any errors generated are processed 13038 XSync(display, 0/*False*/); 13039 //{ import core.stdc.stdio; printf("ogl is here\n"); } 13040 if(glc is null) 13041 throw new Exception("glc"); 13042 } 13043 } 13044 13045 if(opengl == OpenGlOptions.no) { 13046 13047 bool overrideRedirect = false; 13048 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification) 13049 overrideRedirect = true; 13050 13051 XSetWindowAttributes swa; 13052 swa.background_pixel = WhitePixel(display, screen); 13053 swa.border_pixel = BlackPixel(display, screen); 13054 swa.override_redirect = overrideRedirect; 13055 auto root = RootWindow(display, screen); 13056 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 13057 13058 window = XCreateWindow(display, (windowType != WindowTypes.nestedChild || parent is null) ? root : parent.impl.window, 13059 0, 0, width, height, 13060 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap | CWBackPixel | CWBorderPixel | CWOverrideRedirect, &swa); 13061 13062 13063 13064 /* 13065 window = XCreateSimpleWindow( 13066 display, 13067 parent is null ? RootWindow(display, screen) : parent.impl.window, 13068 0, 0, // x, y 13069 width, height, 13070 1, // border width 13071 BlackPixel(display, screen), // border 13072 WhitePixel(display, screen)); // background 13073 */ 13074 13075 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 13076 bufferw = width; 13077 bufferh = height; 13078 13079 gc = DefaultGC(display, screen); 13080 13081 // clear out the buffer to get us started... 13082 XSetForeground(display, gc, WhitePixel(display, screen)); 13083 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 13084 XSetForeground(display, gc, BlackPixel(display, screen)); 13085 } 13086 13087 // input context 13088 //TODO: create this only for top-level windows, and reuse that? 13089 if (XDisplayConnection.xim !is null) { 13090 xic = XCreateIC(XDisplayConnection.xim, 13091 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 13092 /*XNClientWindow*/"clientWindow".ptr, window, 13093 /*XNFocusWindow*/"focusWindow".ptr, window, 13094 null); 13095 if (xic is null) { 13096 import core.stdc.stdio : stderr, fprintf; 13097 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 13098 } 13099 } 13100 13101 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 13102 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 13103 // window class 13104 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 13105 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 13106 XClassHint klass; 13107 XWMHints wh; 13108 XSizeHints size; 13109 klass.res_name = sdpyWindowClassStr; 13110 klass.res_class = sdpyWindowClassStr; 13111 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 13112 } 13113 13114 setTitle(title); 13115 SimpleWindow.nativeMapping[window] = this; 13116 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 13117 13118 // This gives our window a close button 13119 if (windowType != WindowTypes.eventOnly) { 13120 // FIXME: actually implement the WM_TAKE_FOCUS correctly 13121 //Atom[2] atoms = [GetAtom!"WM_DELETE_WINDOW"(display), GetAtom!"WM_TAKE_FOCUS"(display)]; 13122 Atom[1] atoms = [GetAtom!"WM_DELETE_WINDOW"(display)]; 13123 XSetWMProtocols(display, window, atoms.ptr, cast(int) atoms.length); 13124 } 13125 13126 // FIXME: windowType and customizationFlags 13127 Atom[8] wsatoms; // here, due to goto 13128 int wmsacount = 0; // here, due to goto 13129 13130 try 13131 final switch(windowType) { 13132 case WindowTypes.normal: 13133 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 13134 break; 13135 case WindowTypes.undecorated: 13136 motifHideDecorations(); 13137 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 13138 break; 13139 case WindowTypes.eventOnly: 13140 _hidden = true; 13141 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 13142 goto hiddenWindow; 13143 //break; 13144 case WindowTypes.nestedChild: 13145 // handled in XCreateWindow calls 13146 break; 13147 13148 case WindowTypes.dropdownMenu: 13149 motifHideDecorations(); 13150 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 13151 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 13152 break; 13153 case WindowTypes.popupMenu: 13154 motifHideDecorations(); 13155 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 13156 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 13157 break; 13158 case WindowTypes.notification: 13159 motifHideDecorations(); 13160 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 13161 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 13162 break; 13163 /+ 13164 case WindowTypes.menu: 13165 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 13166 motifHideDecorations(); 13167 break; 13168 case WindowTypes.desktop: 13169 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 13170 break; 13171 case WindowTypes.dock: 13172 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 13173 break; 13174 case WindowTypes.toolbar: 13175 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 13176 break; 13177 case WindowTypes.menu: 13178 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 13179 break; 13180 case WindowTypes.utility: 13181 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 13182 break; 13183 case WindowTypes.splash: 13184 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 13185 break; 13186 case WindowTypes.dialog: 13187 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 13188 break; 13189 case WindowTypes.tooltip: 13190 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 13191 break; 13192 case WindowTypes.notification: 13193 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 13194 break; 13195 case WindowTypes.combo: 13196 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 13197 break; 13198 case WindowTypes.dnd: 13199 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 13200 break; 13201 +/ 13202 } 13203 catch(Exception e) { 13204 // XInternAtom failed, prolly a WM 13205 // that doesn't support these things 13206 } 13207 13208 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 13209 // the two following flags may be ignored by WM 13210 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 13211 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 13212 13213 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 13214 13215 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 13216 13217 // What would be ideal here is if they only were 13218 // selected if there was actually an event handler 13219 // for them... 13220 13221 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 13222 13223 hiddenWindow: 13224 13225 // set the pid property for lookup later by window managers 13226 // a standard convenience 13227 import core.sys.posix.unistd; 13228 arch_ulong pid = getpid(); 13229 13230 XChangeProperty( 13231 display, 13232 impl.window, 13233 GetAtom!("_NET_WM_PID", true)(display), 13234 XA_CARDINAL, 13235 32 /* bits */, 13236 0 /*PropModeReplace*/, 13237 &pid, 13238 1); 13239 13240 if(isTransient && parent) { // customizationFlags & WindowFlags.transient) { 13241 if(parent is null) assert(0); 13242 XChangeProperty( 13243 display, 13244 impl.window, 13245 GetAtom!("WM_TRANSIENT_FOR", true)(display), 13246 XA_WINDOW, 13247 32 /* bits */, 13248 0 /*PropModeReplace*/, 13249 &parent.impl.window, 13250 1); 13251 13252 } 13253 13254 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 13255 XMapWindow(display, window); 13256 } else { 13257 _hidden = true; 13258 } 13259 } 13260 13261 void selectDefaultInput(bool forceIncludeMouseMotion) { 13262 auto mask = EventMask.ExposureMask | 13263 EventMask.KeyPressMask | 13264 EventMask.KeyReleaseMask | 13265 EventMask.PropertyChangeMask | 13266 EventMask.FocusChangeMask | 13267 EventMask.StructureNotifyMask | 13268 EventMask.VisibilityChangeMask 13269 | EventMask.ButtonPressMask 13270 | EventMask.ButtonReleaseMask 13271 ; 13272 13273 // xshm is our shortcut for local connections 13274 if(XDisplayConnection.isLocal || forceIncludeMouseMotion) 13275 mask |= EventMask.PointerMotionMask; 13276 else 13277 mask |= EventMask.ButtonMotionMask; 13278 13279 XSelectInput(display, window, mask); 13280 } 13281 13282 13283 void setNetWMWindowType(Atom type) { 13284 Atom[2] atoms; 13285 13286 atoms[0] = type; 13287 // generic fallback 13288 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 13289 13290 XChangeProperty( 13291 display, 13292 impl.window, 13293 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 13294 XA_ATOM, 13295 32 /* bits */, 13296 0 /*PropModeReplace*/, 13297 atoms.ptr, 13298 cast(int) atoms.length); 13299 } 13300 13301 void motifHideDecorations() { 13302 MwmHints hints; 13303 hints.flags = MWM_HINTS_DECORATIONS; 13304 13305 XChangeProperty( 13306 display, 13307 impl.window, 13308 GetAtom!"_MOTIF_WM_HINTS"(display), 13309 GetAtom!"_MOTIF_WM_HINTS"(display), 13310 32 /* bits */, 13311 0 /*PropModeReplace*/, 13312 &hints, 13313 hints.sizeof / 4); 13314 } 13315 13316 /*k8: unused 13317 void createOpenGlContext() { 13318 13319 } 13320 */ 13321 13322 void closeWindow() { 13323 // I can't close this or a child window closing will 13324 // break events for everyone. So I'm just leaking it right 13325 // now and that is probably perfectly fine... 13326 version(none) 13327 if (customEventFDRead != -1) { 13328 import core.sys.posix.unistd : close; 13329 auto same = customEventFDRead == customEventFDWrite; 13330 13331 close(customEventFDRead); 13332 if(!same) 13333 close(customEventFDWrite); 13334 customEventFDRead = -1; 13335 customEventFDWrite = -1; 13336 } 13337 if(buffer) 13338 XFreePixmap(display, buffer); 13339 bufferw = bufferh = 0; 13340 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 13341 XDestroyWindow(display, window); 13342 XFlush(display); 13343 } 13344 13345 void dispose() { 13346 } 13347 13348 bool destroyed = false; 13349 } 13350 13351 bool insideXEventLoop; 13352 } 13353 13354 version(X11) { 13355 13356 int mouseDoubleClickTimeout = 350; /// double click timeout. X only, you probably shouldn't change this. 13357 13358 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 13359 if(width != win.width || height != win.height) { 13360 win._width = width; 13361 win._height = height; 13362 13363 if(win.openglMode == OpenGlOptions.no) { 13364 // FIXME: could this be more efficient? 13365 13366 if (win.bufferw < width || win.bufferh < height) { 13367 //{ 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); } 13368 // grow the internal buffer to match the window... 13369 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 13370 { 13371 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 13372 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 13373 scope(exit) XFreeGC(win.display, xgc); 13374 XSetClipMask(win.display, xgc, None); 13375 XSetForeground(win.display, xgc, 0); 13376 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 13377 } 13378 XCopyArea(display, 13379 cast(Drawable) win.buffer, 13380 cast(Drawable) newPixmap, 13381 win.gc, 0, 0, 13382 win.bufferw < width ? win.bufferw : win.width, 13383 win.bufferh < height ? win.bufferh : win.height, 13384 0, 0); 13385 13386 XFreePixmap(display, win.buffer); 13387 win.buffer = newPixmap; 13388 win.bufferw = width; 13389 win.bufferh = height; 13390 } 13391 13392 // clear unused parts of the buffer 13393 if (win.bufferw > width || win.bufferh > height) { 13394 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 13395 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 13396 scope(exit) XFreeGC(win.display, xgc); 13397 XSetClipMask(win.display, xgc, None); 13398 XSetForeground(win.display, xgc, 0); 13399 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 13400 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 13401 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 13402 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 13403 } 13404 13405 } 13406 13407 version(without_opengl) {} else 13408 if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) { 13409 glViewport(0, 0, width, height); 13410 } 13411 13412 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 13413 13414 if(win.windowResized !is null) { 13415 XUnlockDisplay(display); 13416 scope(exit) XLockDisplay(display); 13417 win.windowResized(width, height); 13418 } 13419 } 13420 } 13421 13422 13423 /// Platform-specific, you might use it when doing a custom event loop 13424 bool doXNextEvent(Display* display) { 13425 bool done; 13426 XEvent e; 13427 XNextEvent(display, &e); 13428 version(sddddd) { 13429 import std.stdio, std.conv : to; 13430 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 13431 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 13432 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 13433 } 13434 } 13435 13436 // filter out compose events 13437 if (XFilterEvent(&e, None)) { 13438 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 13439 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 13440 return false; 13441 } 13442 // process keyboard mapping changes 13443 if (e.type == EventType.KeymapNotify) { 13444 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 13445 XRefreshKeyboardMapping(&e.xmapping); 13446 return false; 13447 } 13448 13449 version(with_eventloop) 13450 import arsd.eventloop; 13451 13452 if(SimpleWindow.handleNativeGlobalEvent !is null) { 13453 // see windows impl's comments 13454 XUnlockDisplay(display); 13455 scope(exit) XLockDisplay(display); 13456 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 13457 if(ret == 0) 13458 return done; 13459 } 13460 13461 13462 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 13463 if(win.getNativeEventHandler !is null) { 13464 XUnlockDisplay(display); 13465 scope(exit) XLockDisplay(display); 13466 auto ret = win.getNativeEventHandler()(e); 13467 if(ret == 0) 13468 return done; 13469 } 13470 } 13471 13472 switch(e.type) { 13473 case EventType.SelectionClear: 13474 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) { 13475 // FIXME so it is supposed to finish any in progress transfers... but idk... 13476 //import std.stdio; writeln("SelectionClear"); 13477 SimpleWindow.impl.setSelectionHandlers.remove(e.xselectionclear.selection); 13478 } 13479 break; 13480 case EventType.SelectionRequest: 13481 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 13482 if(auto ssh = e.xselectionrequest.selection in SimpleWindow.impl.setSelectionHandlers) { 13483 // import std.stdio; printf("SelectionRequest %s\n", XGetAtomName(e.xselectionrequest.display, e.xselectionrequest.target)); 13484 XUnlockDisplay(display); 13485 scope(exit) XLockDisplay(display); 13486 (*ssh).handleRequest(e); 13487 } 13488 break; 13489 case EventType.PropertyNotify: 13490 // import std.stdio; printf("PropertyNotify %s %d\n", XGetAtomName(e.xproperty.display, e.xproperty.atom), e.xproperty.state); 13491 13492 foreach(ssh; SimpleWindow.impl.setSelectionHandlers) { 13493 if(ssh.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyDelete) 13494 ssh.sendMoreIncr(&e.xproperty); 13495 } 13496 13497 13498 if(auto win = e.xproperty.window in SimpleWindow.nativeMapping) 13499 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 13500 if(handler.matchesIncr(e.xproperty.window, e.xproperty.atom) && e.xproperty.state == PropertyNotification.PropertyNewValue) { 13501 Atom target; 13502 int format; 13503 arch_ulong bytesafter, length; 13504 void* value; 13505 13506 ubyte[] s; 13507 Atom targetToKeep; 13508 13509 XGetWindowProperty( 13510 e.xproperty.display, 13511 e.xproperty.window, 13512 e.xproperty.atom, 13513 0, 13514 100000 /* length */, 13515 true, /* erase it to signal we got it and want more */ 13516 0 /*AnyPropertyType*/, 13517 &target, &format, &length, &bytesafter, &value); 13518 13519 if(!targetToKeep) 13520 targetToKeep = target; 13521 13522 auto id = (cast(ubyte*) value)[0 .. length]; 13523 13524 handler.handleIncrData(targetToKeep, id); 13525 13526 XFree(value); 13527 } 13528 } 13529 break; 13530 case EventType.SelectionNotify: 13531 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 13532 if(auto handler = e.xproperty.atom in win.getSelectionHandlers) { 13533 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 13534 XUnlockDisplay(display); 13535 scope(exit) XLockDisplay(display); 13536 handler.handleData(None, null); 13537 } else { 13538 Atom target; 13539 int format; 13540 arch_ulong bytesafter, length; 13541 void* value; 13542 XGetWindowProperty( 13543 e.xselection.display, 13544 e.xselection.requestor, 13545 e.xselection.property, 13546 0, 13547 100000 /* length */, 13548 //false, /* don't erase it */ 13549 true, /* do erase it lol */ 13550 0 /*AnyPropertyType*/, 13551 &target, &format, &length, &bytesafter, &value); 13552 13553 // FIXME: I don't have to copy it now since it is in char[] instead of string 13554 13555 { 13556 XUnlockDisplay(display); 13557 scope(exit) XLockDisplay(display); 13558 13559 if(target == XA_ATOM) { 13560 // initial request, see what they are able to work with and request the best one 13561 // we can handle, if available 13562 13563 Atom[] answer = (cast(Atom*) value)[0 .. length]; 13564 Atom best = handler.findBestFormat(answer); 13565 13566 /+ 13567 writeln("got ", answer); 13568 foreach(a; answer) 13569 printf("%s\n", XGetAtomName(display, a)); 13570 writeln("best ", best); 13571 +/ 13572 13573 if(best != None) { 13574 // actually request the best format 13575 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 13576 } 13577 } else if(target == GetAtom!"INCR"(display)) { 13578 // incremental 13579 13580 handler.prepareIncremental(e.xselection.requestor, e.xselection.property); 13581 13582 // signal the sending program that we see 13583 // the incr and are ready to receive more. 13584 XDeleteProperty( 13585 e.xselection.display, 13586 e.xselection.requestor, 13587 e.xselection.property); 13588 } else { 13589 // unsupported type... maybe, forward 13590 handler.handleData(target, cast(ubyte[]) value[0 .. length]); 13591 } 13592 } 13593 XFree(value); 13594 /* 13595 XDeleteProperty( 13596 e.xselection.display, 13597 e.xselection.requestor, 13598 e.xselection.property); 13599 */ 13600 } 13601 } 13602 break; 13603 case EventType.ConfigureNotify: 13604 auto event = e.xconfigure; 13605 if(auto win = event.window in SimpleWindow.nativeMapping) { 13606 //version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); } 13607 13608 recordX11Resize(display, *win, event.width, event.height); 13609 } 13610 break; 13611 case EventType.Expose: 13612 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 13613 // if it is closing from a popup menu, it can get 13614 // an Expose event right by the end and trigger a 13615 // BadDrawable error ... we'll just check 13616 // closed to handle that. 13617 if((*win).closed) break; 13618 if((*win).openglMode == OpenGlOptions.no) { 13619 bool doCopy = true;// e.xexpose.count == 0; // the count is better if we copy all area but meh 13620 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 13621 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); 13622 } else { 13623 // need to redraw the scene somehow 13624 if(e.xexpose.count == 0) { // only do the last one since redrawOpenGlSceneNow always does it all 13625 XUnlockDisplay(display); 13626 scope(exit) XLockDisplay(display); 13627 version(without_opengl) {} else 13628 win.redrawOpenGlSceneSoon(); 13629 } 13630 } 13631 } 13632 break; 13633 case EventType.FocusIn: 13634 case EventType.FocusOut: 13635 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 13636 if (win.xic !is null) { 13637 //{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); } 13638 if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic); 13639 } 13640 13641 win._focused = e.type == EventType.FocusIn; 13642 13643 if(win.demandingAttention) 13644 demandAttention(*win, false); 13645 13646 if(win.onFocusChange) { 13647 XUnlockDisplay(display); 13648 scope(exit) XLockDisplay(display); 13649 win.onFocusChange(e.type == EventType.FocusIn); 13650 } 13651 } 13652 break; 13653 case EventType.VisibilityNotify: 13654 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 13655 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 13656 if (win.visibilityChanged !is null) { 13657 XUnlockDisplay(display); 13658 scope(exit) XLockDisplay(display); 13659 win.visibilityChanged(false); 13660 } 13661 } else { 13662 if (win.visibilityChanged !is null) { 13663 XUnlockDisplay(display); 13664 scope(exit) XLockDisplay(display); 13665 win.visibilityChanged(true); 13666 } 13667 } 13668 } 13669 break; 13670 case EventType.ClientMessage: 13671 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 13672 // "ignore next mouse motion" event, increment ignore counter for teh window 13673 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 13674 ++(*win).warpEventCount; 13675 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 13676 } else { 13677 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 13678 } 13679 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 13680 // user clicked the close button on the window manager 13681 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 13682 XUnlockDisplay(display); 13683 scope(exit) XLockDisplay(display); 13684 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 13685 } 13686 13687 } else if(e.xclient.data.l[0] == GetAtom!"WM_TAKE_FOCUS"(e.xany.display)) { 13688 import std.stdio; writeln("HAPPENED"); 13689 // user clicked the close button on the window manager 13690 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 13691 XUnlockDisplay(display); 13692 scope(exit) XLockDisplay(display); 13693 13694 // FIXME: so this is actually supposed to focus to a relevant child window if appropriate 13695 XSetInputFocus(display, e.xclient.window, RevertToParent, e.xclient.data.l[1]); 13696 } 13697 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 13698 foreach(nai; NotificationAreaIcon.activeIcons) 13699 nai.newManager(); 13700 } else if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 13701 13702 bool xDragWindow = true; 13703 if(xDragWindow && e.xclient.message_type == GetAtom!"XdndStatus"(e.xany.display)) { 13704 //XDefineCursor(display, xDragWindow.impl.window, 13705 //import std.stdio; writeln("XdndStatus ", e.xclient.data.l); 13706 } 13707 if(auto dh = win.dropHandler) { 13708 13709 static Atom[3] xFormatsBuffer; 13710 static Atom[] xFormats; 13711 13712 void resetXFormats() { 13713 xFormatsBuffer[] = 0; 13714 xFormats = xFormatsBuffer[]; 13715 } 13716 13717 if(e.xclient.message_type == GetAtom!"XdndEnter"(e.xany.display)) { 13718 // on Windows it is supposed to return the effect you actually do FIXME 13719 13720 auto sourceWindow = e.xclient.data.l[0]; 13721 13722 xFormatsBuffer[0] = e.xclient.data.l[2]; 13723 xFormatsBuffer[1] = e.xclient.data.l[3]; 13724 xFormatsBuffer[2] = e.xclient.data.l[4]; 13725 13726 if(e.xclient.data.l[1] & 1) { 13727 // can just grab it all but like we don't necessarily need them... 13728 xFormats = cast(Atom[]) getX11PropertyData(sourceWindow, GetAtom!"XdndTypeList"(display), XA_ATOM); 13729 } else { 13730 int len; 13731 foreach(fmt; xFormatsBuffer) 13732 if(fmt) len++; 13733 xFormats = xFormatsBuffer[0 .. len]; 13734 } 13735 13736 auto pkg = DropPackage(*win, e.xclient.data.l[0], 0, xFormats); 13737 13738 dh.dragEnter(&pkg); 13739 } else if(e.xclient.message_type == GetAtom!"XdndPosition"(e.xany.display)) { 13740 13741 auto pack = e.xclient.data.l[2]; 13742 13743 auto result = dh.dragOver(Point((pack & 0xffff0000) >> 16, pack & 0xffff)); // FIXME: translate screen coordinates back to window coords 13744 13745 13746 XClientMessageEvent xclient; 13747 13748 xclient.type = EventType.ClientMessage; 13749 xclient.window = e.xclient.data.l[0]; 13750 xclient.message_type = GetAtom!"XdndStatus"(display); 13751 xclient.format = 32; 13752 xclient.data.l[0] = win.impl.window; 13753 xclient.data.l[1] = (result.action != DragAndDropAction.none) ? 1 : 0; // will accept 13754 auto r = result.consistentWithin; 13755 xclient.data.l[2] = ((cast(short) r.left) << 16) | (cast(short) r.top); 13756 xclient.data.l[3] = ((cast(short) r.width) << 16) | (cast(short) r.height); 13757 xclient.data.l[4] = dndActionAtom(e.xany.display, result.action); 13758 13759 XSendEvent( 13760 display, 13761 e.xclient.data.l[0], 13762 false, 13763 EventMask.NoEventMask, 13764 cast(XEvent*) &xclient 13765 ); 13766 13767 13768 } else if(e.xclient.message_type == GetAtom!"XdndLeave"(e.xany.display)) { 13769 //import std.stdio; writeln("XdndLeave"); 13770 // drop cancelled. 13771 // data.l[0] is the source window 13772 dh.dragLeave(); 13773 13774 resetXFormats(); 13775 } else if(e.xclient.message_type == GetAtom!"XdndDrop"(e.xany.display)) { 13776 // drop happening, should fetch data, then send finished 13777 //import std.stdio; writeln("XdndDrop"); 13778 13779 auto pkg = DropPackage(*win, e.xclient.data.l[0], e.xclient.data.l[2], xFormats); 13780 13781 dh.drop(&pkg); 13782 13783 resetXFormats(); 13784 } else if(e.xclient.message_type == GetAtom!"XdndFinished"(e.xany.display)) { 13785 // import std.stdio; writeln("XdndFinished"); 13786 13787 dh.finish(); 13788 } 13789 13790 } 13791 } 13792 break; 13793 case EventType.MapNotify: 13794 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 13795 (*win)._visible = true; 13796 if (!(*win)._visibleForTheFirstTimeCalled) { 13797 (*win)._visibleForTheFirstTimeCalled = true; 13798 if ((*win).visibleForTheFirstTime !is null) { 13799 XUnlockDisplay(display); 13800 scope(exit) XLockDisplay(display); 13801 version(without_opengl) {} else { 13802 if((*win).openglMode == OpenGlOptions.yes) { 13803 (*win).setAsCurrentOpenGlContextNT(); 13804 glViewport(0, 0, (*win).width, (*win).height); 13805 } 13806 } 13807 (*win).visibleForTheFirstTime(); 13808 } 13809 } 13810 if ((*win).visibilityChanged !is null) { 13811 XUnlockDisplay(display); 13812 scope(exit) XLockDisplay(display); 13813 (*win).visibilityChanged(true); 13814 } 13815 } 13816 break; 13817 case EventType.UnmapNotify: 13818 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 13819 win._visible = false; 13820 if (win.visibilityChanged !is null) { 13821 XUnlockDisplay(display); 13822 scope(exit) XLockDisplay(display); 13823 win.visibilityChanged(false); 13824 } 13825 } 13826 break; 13827 case EventType.DestroyNotify: 13828 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 13829 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 13830 win._closed = true; // just in case 13831 win.destroyed = true; 13832 if (win.xic !is null) { 13833 XDestroyIC(win.xic); 13834 win.xic = null; // just in calse 13835 } 13836 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 13837 bool anyImportant = false; 13838 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 13839 if(w.beingOpenKeepsAppOpen) { 13840 anyImportant = true; 13841 break; 13842 } 13843 if(!anyImportant) 13844 done = true; 13845 } 13846 auto window = e.xdestroywindow.window; 13847 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 13848 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 13849 13850 version(with_eventloop) { 13851 if(done) exit(); 13852 } 13853 break; 13854 13855 case EventType.MotionNotify: 13856 MouseEvent mouse; 13857 auto event = e.xmotion; 13858 13859 mouse.type = MouseEventType.motion; 13860 mouse.x = event.x; 13861 mouse.y = event.y; 13862 mouse.modifierState = event.state; 13863 13864 mouse.timestamp = event.time; 13865 13866 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 13867 mouse.window = *win; 13868 if (win.warpEventCount > 0) { 13869 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 13870 --(*win).warpEventCount; 13871 (*win).mdx(mouse); // so deltas will be correctly updated 13872 } else { 13873 win.warpEventCount = 0; // just in case 13874 (*win).mdx(mouse); 13875 if((*win).handleMouseEvent) { 13876 XUnlockDisplay(display); 13877 scope(exit) XLockDisplay(display); 13878 (*win).handleMouseEvent(mouse); 13879 } 13880 } 13881 } 13882 13883 version(with_eventloop) 13884 send(mouse); 13885 break; 13886 case EventType.ButtonPress: 13887 case EventType.ButtonRelease: 13888 MouseEvent mouse; 13889 auto event = e.xbutton; 13890 13891 mouse.timestamp = event.time; 13892 13893 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 13894 mouse.x = event.x; 13895 mouse.y = event.y; 13896 13897 static Time lastMouseDownTime = 0; 13898 13899 mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 13900 if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time; 13901 13902 switch(event.button) { 13903 case 1: mouse.button = MouseButton.left; break; // left 13904 case 2: mouse.button = MouseButton.middle; break; // middle 13905 case 3: mouse.button = MouseButton.right; break; // right 13906 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 13907 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 13908 case 6: break; // idk 13909 case 7: break; // idk 13910 case 8: mouse.button = MouseButton.backButton; break; 13911 case 9: mouse.button = MouseButton.forwardButton; break; 13912 default: 13913 } 13914 13915 // FIXME: double check this 13916 mouse.modifierState = event.state; 13917 13918 //mouse.modifierState = event.detail; 13919 13920 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 13921 mouse.window = *win; 13922 (*win).mdx(mouse); 13923 if((*win).handleMouseEvent) { 13924 XUnlockDisplay(display); 13925 scope(exit) XLockDisplay(display); 13926 (*win).handleMouseEvent(mouse); 13927 } 13928 } 13929 version(with_eventloop) 13930 send(mouse); 13931 break; 13932 13933 case EventType.KeyPress: 13934 case EventType.KeyRelease: 13935 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 13936 KeyEvent ke; 13937 ke.pressed = e.type == EventType.KeyPress; 13938 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 13939 13940 auto sym = XKeycodeToKeysym( 13941 XDisplayConnection.get(), 13942 e.xkey.keycode, 13943 0); 13944 13945 ke.key = cast(Key) sym;//e.xkey.keycode; 13946 13947 ke.modifierState = e.xkey.state; 13948 13949 // import std.stdio; writefln("%x", sym); 13950 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 13951 int charbuflen = 0; // return value of XwcLookupString 13952 if (ke.pressed) { 13953 auto win = e.xkey.window in SimpleWindow.nativeMapping; 13954 if (win !is null && win.xic !is null) { 13955 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 13956 Status status; 13957 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 13958 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 13959 } else { 13960 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 13961 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 13962 char[16] buffer; 13963 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 13964 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 13965 } 13966 } 13967 13968 // if there's no char, subst one 13969 if (charbuflen == 0) { 13970 switch (sym) { 13971 case 0xff09: charbuf[charbuflen++] = '\t'; break; 13972 case 0xff8d: // keypad enter 13973 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 13974 default : // ignore 13975 } 13976 } 13977 13978 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 13979 ke.window = *win; 13980 13981 13982 if(win.inputProxy) 13983 win = &win.inputProxy; 13984 13985 // char events are separate since they are on Windows too 13986 // also, xcompose can generate long char sequences 13987 // don't send char events if Meta and/or Hyper is pressed 13988 // TODO: ctrl+char should only send control chars; not yet 13989 if ((e.xkey.state&ModifierState.ctrl) != 0) { 13990 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 13991 } 13992 13993 dchar[32] charsComingBuffer; 13994 int charsComingPosition; 13995 dchar[] charsComing = charsComingBuffer[]; 13996 13997 if (ke.pressed && charbuflen > 0) { 13998 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 13999 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 14000 if(charsComingPosition >= charsComing.length) 14001 charsComing.length = charsComingPosition + 8; 14002 14003 charsComing[charsComingPosition++] = ch; 14004 } 14005 14006 charsComing = charsComing[0 .. charsComingPosition]; 14007 } else { 14008 charsComing = null; 14009 } 14010 14011 ke.charsPossible = charsComing; 14012 14013 if (win.handleKeyEvent) { 14014 XUnlockDisplay(display); 14015 scope(exit) XLockDisplay(display); 14016 win.handleKeyEvent(ke); 14017 } 14018 14019 // Super and alt modifier keys never actually send the chars, they are assumed to be special. 14020 if ((e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0 && win.handleCharEvent) { 14021 XUnlockDisplay(display); 14022 scope(exit) XLockDisplay(display); 14023 foreach(ch; charsComing) 14024 win.handleCharEvent(ch); 14025 } 14026 } 14027 14028 version(with_eventloop) 14029 send(ke); 14030 break; 14031 default: 14032 } 14033 14034 return done; 14035 } 14036 } 14037 14038 /* *************************************** */ 14039 /* Done with simpledisplay stuff */ 14040 /* *************************************** */ 14041 14042 // Necessary C library bindings follow 14043 version(Windows) {} else 14044 version(X11) { 14045 14046 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 14047 14048 // X11 bindings needed here 14049 /* 14050 A little of this is from the bindings project on 14051 D Source and some of it is copy/paste from the C 14052 header. 14053 14054 The DSource listing consistently used D's long 14055 where C used long. That's wrong - C long is 32 bit, so 14056 it should be int in D. I changed that here. 14057 14058 Note: 14059 This isn't complete, just took what I needed for myself. 14060 */ 14061 14062 import core.stdc.stddef : wchar_t; 14063 14064 interface XLib { 14065 extern(C) nothrow @nogc { 14066 char* XResourceManagerString(Display*); 14067 void XrmInitialize(); 14068 XrmDatabase XrmGetStringDatabase(char* data); 14069 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 14070 14071 Cursor XCreateFontCursor(Display*, uint shape); 14072 int XDefineCursor(Display* display, Window w, Cursor cursor); 14073 int XUndefineCursor(Display* display, Window w); 14074 14075 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 14076 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 14077 int XFreeCursor(Display* display, Cursor cursor); 14078 14079 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 14080 14081 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 14082 14083 char *XKeysymToString(KeySym keysym); 14084 KeySym XKeycodeToKeysym( 14085 Display* /* display */, 14086 KeyCode /* keycode */, 14087 int /* index */ 14088 ); 14089 14090 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 14091 14092 int XFree(void*); 14093 int XDeleteProperty(Display *display, Window w, Atom property); 14094 14095 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements); 14096 14097 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 14098 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 14099 *actual_type_return, int *actual_format_return, arch_ulong 14100 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 14101 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 14102 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 14103 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 14104 14105 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 14106 14107 Window XGetSelectionOwner(Display *display, Atom selection); 14108 14109 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 14110 14111 char** XListFonts(Display*, const char*, int, int*); 14112 void XFreeFontNames(char**); 14113 14114 Display* XOpenDisplay(const char*); 14115 int XCloseDisplay(Display*); 14116 14117 int XSynchronize(Display*, bool); 14118 14119 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 14120 14121 Bool XSupportsLocale(); 14122 char* XSetLocaleModifiers(const(char)* modifier_list); 14123 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 14124 Status XCloseOM(XOM om); 14125 14126 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 14127 Status XCloseIM(XIM im); 14128 14129 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 14130 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 14131 Display* XDisplayOfIM(XIM im); 14132 char* XLocaleOfIM(XIM im); 14133 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 14134 void XDestroyIC(XIC ic); 14135 void XSetICFocus(XIC ic); 14136 void XUnsetICFocus(XIC ic); 14137 //wchar_t* XwcResetIC(XIC ic); 14138 char* XmbResetIC(XIC ic); 14139 char* Xutf8ResetIC(XIC ic); 14140 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 14141 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 14142 XIM XIMOfIC(XIC ic); 14143 14144 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 14145 14146 14147 XFontStruct *XLoadQueryFont(Display *display, in char *name); 14148 int XFreeFont(Display *display, XFontStruct *font_struct); 14149 int XSetFont(Display* display, GC gc, Font font); 14150 int XTextWidth(XFontStruct*, in char*, int); 14151 14152 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 14153 int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n); 14154 14155 Window XCreateSimpleWindow( 14156 Display* /* display */, 14157 Window /* parent */, 14158 int /* x */, 14159 int /* y */, 14160 uint /* width */, 14161 uint /* height */, 14162 uint /* border_width */, 14163 uint /* border */, 14164 uint /* background */ 14165 ); 14166 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); 14167 14168 int XReparentWindow(Display*, Window, Window, int, int); 14169 int XClearWindow(Display*, Window); 14170 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 14171 int XMoveWindow(Display*, Window, int, int); 14172 int XResizeWindow(Display *display, Window w, uint width, uint height); 14173 14174 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 14175 14176 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 14177 14178 XImage *XCreateImage( 14179 Display* /* display */, 14180 Visual* /* visual */, 14181 uint /* depth */, 14182 int /* format */, 14183 int /* offset */, 14184 ubyte* /* data */, 14185 uint /* width */, 14186 uint /* height */, 14187 int /* bitmap_pad */, 14188 int /* bytes_per_line */ 14189 ); 14190 14191 Status XInitImage (XImage* image); 14192 14193 Atom XInternAtom( 14194 Display* /* display */, 14195 const char* /* atom_name */, 14196 Bool /* only_if_exists */ 14197 ); 14198 14199 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 14200 char* XGetAtomName(Display*, Atom); 14201 Status XGetAtomNames(Display*, Atom*, int count, char**); 14202 14203 int XPutImage( 14204 Display* /* display */, 14205 Drawable /* d */, 14206 GC /* gc */, 14207 XImage* /* image */, 14208 int /* src_x */, 14209 int /* src_y */, 14210 int /* dest_x */, 14211 int /* dest_y */, 14212 uint /* width */, 14213 uint /* height */ 14214 ); 14215 14216 XImage *XGetImage(Display *display, Drawable d, int x, int y, uint width, uint height, c_ulong plane_mask, int format); 14217 14218 14219 int XDestroyWindow( 14220 Display* /* display */, 14221 Window /* w */ 14222 ); 14223 14224 int XDestroyImage(XImage*); 14225 14226 int XSelectInput( 14227 Display* /* display */, 14228 Window /* w */, 14229 EventMask /* event_mask */ 14230 ); 14231 14232 int XMapWindow( 14233 Display* /* display */, 14234 Window /* w */ 14235 ); 14236 14237 Status XIconifyWindow(Display*, Window, int); 14238 int XMapRaised(Display*, Window); 14239 int XMapSubwindows(Display*, Window); 14240 14241 int XNextEvent( 14242 Display* /* display */, 14243 XEvent* /* event_return */ 14244 ); 14245 14246 int XMaskEvent(Display*, arch_long, XEvent*); 14247 14248 Bool XFilterEvent(XEvent *event, Window window); 14249 int XRefreshKeyboardMapping(XMappingEvent *event_map); 14250 14251 Status XSetWMProtocols( 14252 Display* /* display */, 14253 Window /* w */, 14254 Atom* /* protocols */, 14255 int /* count */ 14256 ); 14257 14258 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 14259 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 14260 14261 14262 Status XInitThreads(); 14263 void XLockDisplay (Display* display); 14264 void XUnlockDisplay (Display* display); 14265 14266 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 14267 14268 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 14269 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 14270 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 14271 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 14272 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 14273 14274 14275 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 14276 int XDrawString(Display*, Drawable, GC, int, int, in char*, int); 14277 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 14278 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 14279 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 14280 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 14281 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 14282 int XDrawPoint(Display*, Drawable, GC, int, int); 14283 int XSetForeground(Display*, GC, uint); 14284 int XSetBackground(Display*, GC, uint); 14285 14286 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 14287 void XFreeFontSet(Display*, XFontSet); 14288 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int); 14289 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 14290 14291 int Xutf8TextExtents(XFontSet font_set, const char *, int num_bytes, XRectangle *overall_ink_return, XRectangle *overall_logical_return); 14292 14293 14294 //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); 14295 14296 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 14297 int XSetFunction(Display*, GC, int); 14298 14299 GC XCreateGC(Display*, Drawable, uint, void*); 14300 int XCopyGC(Display*, GC, uint, GC); 14301 int XFreeGC(Display*, GC); 14302 14303 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 14304 bool XCheckMaskEvent(Display*, int, XEvent*); 14305 14306 int XPending(Display*); 14307 int XEventsQueued(Display* display, int mode); 14308 14309 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 14310 int XFreePixmap(Display*, Pixmap); 14311 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 14312 int XFlush(Display*); 14313 int XBell(Display*, int); 14314 int XSync(Display*, bool); 14315 14316 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 14317 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 14318 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 14319 14320 KeySym XStringToKeysym(const char *string); 14321 14322 Bool XCheckTypedEvent(Display *display, int event_type, XEvent *event_return); 14323 14324 Window XDefaultRootWindow(Display*); 14325 14326 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); 14327 14328 int XUngrabButton(Display *display, uint button, uint modifiers, Window grab_window); 14329 14330 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 14331 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 14332 14333 Status XAllocColor(Display*, Colormap, XColor*); 14334 14335 int XWithdrawWindow(Display*, Window, int); 14336 int XUnmapWindow(Display*, Window); 14337 int XLowerWindow(Display*, Window); 14338 int XRaiseWindow(Display*, Window); 14339 14340 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); 14341 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); 14342 14343 int XGetInputFocus(Display*, Window*, int*); 14344 int XSetInputFocus(Display*, Window, int, Time); 14345 14346 XErrorHandler XSetErrorHandler(XErrorHandler); 14347 14348 int XGetErrorText(Display*, int, char*, int); 14349 14350 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 14351 14352 14353 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); 14354 int XUngrabPointer(Display *display, Time time); 14355 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 14356 14357 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 14358 14359 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 14360 int XSetClipMask(Display*, GC, Pixmap); 14361 int XSetClipOrigin(Display*, GC, int, int); 14362 14363 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 14364 14365 void XSetWMName(Display*, Window, XTextProperty*); 14366 Status XGetWMName(Display*, Window, XTextProperty*); 14367 int XStoreName(Display* display, Window w, const(char)* window_name); 14368 14369 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 14370 14371 } 14372 } 14373 14374 interface Xext { 14375 extern(C) nothrow @nogc { 14376 Status XShmAttach(Display*, XShmSegmentInfo*); 14377 Status XShmDetach(Display*, XShmSegmentInfo*); 14378 Status XShmPutImage( 14379 Display* /* dpy */, 14380 Drawable /* d */, 14381 GC /* gc */, 14382 XImage* /* image */, 14383 int /* src_x */, 14384 int /* src_y */, 14385 int /* dst_x */, 14386 int /* dst_y */, 14387 uint /* src_width */, 14388 uint /* src_height */, 14389 Bool /* send_event */ 14390 ); 14391 14392 Status XShmQueryExtension(Display*); 14393 14394 XImage *XShmCreateImage( 14395 Display* /* dpy */, 14396 Visual* /* visual */, 14397 uint /* depth */, 14398 int /* format */, 14399 char* /* data */, 14400 XShmSegmentInfo* /* shminfo */, 14401 uint /* width */, 14402 uint /* height */ 14403 ); 14404 14405 Pixmap XShmCreatePixmap( 14406 Display* /* dpy */, 14407 Drawable /* d */, 14408 char* /* data */, 14409 XShmSegmentInfo* /* shminfo */, 14410 uint /* width */, 14411 uint /* height */, 14412 uint /* depth */ 14413 ); 14414 14415 } 14416 } 14417 14418 // this requires -lXpm 14419 //int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 14420 14421 14422 mixin DynamicLoad!(XLib, "X11", 6) xlib; 14423 mixin DynamicLoad!(Xext, "Xext", 6) xext; 14424 shared static this() { 14425 xlib.loadDynamicLibrary(); 14426 xext.loadDynamicLibrary(); 14427 } 14428 14429 14430 extern(C) nothrow @nogc { 14431 14432 alias XrmDatabase = void*; 14433 struct XrmValue { 14434 uint size; 14435 void* addr; 14436 } 14437 14438 struct XVisualInfo { 14439 Visual* visual; 14440 VisualID visualid; 14441 int screen; 14442 uint depth; 14443 int c_class; 14444 c_ulong red_mask; 14445 c_ulong green_mask; 14446 c_ulong blue_mask; 14447 int colormap_size; 14448 int bits_per_rgb; 14449 } 14450 14451 enum VisualNoMask= 0x0; 14452 enum VisualIDMask= 0x1; 14453 enum VisualScreenMask=0x2; 14454 enum VisualDepthMask= 0x4; 14455 enum VisualClassMask= 0x8; 14456 enum VisualRedMaskMask=0x10; 14457 enum VisualGreenMaskMask=0x20; 14458 enum VisualBlueMaskMask=0x40; 14459 enum VisualColormapSizeMask=0x80; 14460 enum VisualBitsPerRGBMask=0x100; 14461 enum VisualAllMask= 0x1FF; 14462 14463 14464 // XIM and other crap 14465 struct _XOM {} 14466 struct _XIM {} 14467 struct _XIC {} 14468 alias XOM = _XOM*; 14469 alias XIM = _XIM*; 14470 alias XIC = _XIC*; 14471 14472 alias XIMStyle = arch_ulong; 14473 enum : arch_ulong { 14474 XIMPreeditArea = 0x0001, 14475 XIMPreeditCallbacks = 0x0002, 14476 XIMPreeditPosition = 0x0004, 14477 XIMPreeditNothing = 0x0008, 14478 XIMPreeditNone = 0x0010, 14479 XIMStatusArea = 0x0100, 14480 XIMStatusCallbacks = 0x0200, 14481 XIMStatusNothing = 0x0400, 14482 XIMStatusNone = 0x0800, 14483 } 14484 14485 14486 /* X Shared Memory Extension functions */ 14487 //pragma(lib, "Xshm"); 14488 alias arch_ulong ShmSeg; 14489 struct XShmSegmentInfo { 14490 ShmSeg shmseg; 14491 int shmid; 14492 ubyte* shmaddr; 14493 Bool readOnly; 14494 } 14495 14496 // and the necessary OS functions 14497 int shmget(int, size_t, int); 14498 void* shmat(int, in void*, int); 14499 int shmdt(in void*); 14500 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 14501 14502 enum IPC_PRIVATE = 0; 14503 enum IPC_CREAT = 512; 14504 enum IPC_RMID = 0; 14505 14506 /* MIT-SHM end */ 14507 14508 14509 enum MappingType:int { 14510 MappingModifier =0, 14511 MappingKeyboard =1, 14512 MappingPointer =2 14513 } 14514 14515 /* ImageFormat -- PutImage, GetImage */ 14516 enum ImageFormat:int { 14517 XYBitmap =0, /* depth 1, XYFormat */ 14518 XYPixmap =1, /* depth == drawable depth */ 14519 ZPixmap =2 /* depth == drawable depth */ 14520 } 14521 14522 enum ModifierName:int { 14523 ShiftMapIndex =0, 14524 LockMapIndex =1, 14525 ControlMapIndex =2, 14526 Mod1MapIndex =3, 14527 Mod2MapIndex =4, 14528 Mod3MapIndex =5, 14529 Mod4MapIndex =6, 14530 Mod5MapIndex =7 14531 } 14532 14533 enum ButtonMask:int { 14534 Button1Mask =1<<8, 14535 Button2Mask =1<<9, 14536 Button3Mask =1<<10, 14537 Button4Mask =1<<11, 14538 Button5Mask =1<<12, 14539 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 14540 } 14541 14542 enum KeyOrButtonMask:uint { 14543 ShiftMask =1<<0, 14544 LockMask =1<<1, 14545 ControlMask =1<<2, 14546 Mod1Mask =1<<3, 14547 Mod2Mask =1<<4, 14548 Mod3Mask =1<<5, 14549 Mod4Mask =1<<6, 14550 Mod5Mask =1<<7, 14551 Button1Mask =1<<8, 14552 Button2Mask =1<<9, 14553 Button3Mask =1<<10, 14554 Button4Mask =1<<11, 14555 Button5Mask =1<<12, 14556 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 14557 } 14558 14559 enum ButtonName:int { 14560 Button1 =1, 14561 Button2 =2, 14562 Button3 =3, 14563 Button4 =4, 14564 Button5 =5 14565 } 14566 14567 /* Notify modes */ 14568 enum NotifyModes:int 14569 { 14570 NotifyNormal =0, 14571 NotifyGrab =1, 14572 NotifyUngrab =2, 14573 NotifyWhileGrabbed =3 14574 } 14575 enum NotifyHint = 1; /* for MotionNotify events */ 14576 14577 /* Notify detail */ 14578 enum NotifyDetail:int 14579 { 14580 NotifyAncestor =0, 14581 NotifyVirtual =1, 14582 NotifyInferior =2, 14583 NotifyNonlinear =3, 14584 NotifyNonlinearVirtual =4, 14585 NotifyPointer =5, 14586 NotifyPointerRoot =6, 14587 NotifyDetailNone =7 14588 } 14589 14590 /* Visibility notify */ 14591 14592 enum VisibilityNotify:int 14593 { 14594 VisibilityUnobscured =0, 14595 VisibilityPartiallyObscured =1, 14596 VisibilityFullyObscured =2 14597 } 14598 14599 14600 enum WindowStackingMethod:int 14601 { 14602 Above =0, 14603 Below =1, 14604 TopIf =2, 14605 BottomIf =3, 14606 Opposite =4 14607 } 14608 14609 /* Circulation request */ 14610 enum CirculationRequest:int 14611 { 14612 PlaceOnTop =0, 14613 PlaceOnBottom =1 14614 } 14615 14616 enum PropertyNotification:int 14617 { 14618 PropertyNewValue =0, 14619 PropertyDelete =1 14620 } 14621 14622 enum ColorMapNotification:int 14623 { 14624 ColormapUninstalled =0, 14625 ColormapInstalled =1 14626 } 14627 14628 14629 struct _XPrivate {} 14630 struct _XrmHashBucketRec {} 14631 14632 alias void* XPointer; 14633 alias void* XExtData; 14634 14635 version( X86_64 ) { 14636 alias ulong XID; 14637 alias ulong arch_ulong; 14638 alias long arch_long; 14639 } else { 14640 alias uint XID; 14641 alias uint arch_ulong; 14642 alias int arch_long; 14643 } 14644 14645 alias XID Window; 14646 alias XID Drawable; 14647 alias XID Pixmap; 14648 14649 alias arch_ulong Atom; 14650 alias int Bool; 14651 alias Display XDisplay; 14652 14653 alias int ByteOrder; 14654 alias arch_ulong Time; 14655 alias void ScreenFormat; 14656 14657 struct XImage { 14658 int width, height; /* size of image */ 14659 int xoffset; /* number of pixels offset in X direction */ 14660 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 14661 void *data; /* pointer to image data */ 14662 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 14663 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 14664 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 14665 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 14666 int depth; /* depth of image */ 14667 int bytes_per_line; /* accelarator to next line */ 14668 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 14669 arch_ulong red_mask; /* bits in z arrangment */ 14670 arch_ulong green_mask; 14671 arch_ulong blue_mask; 14672 XPointer obdata; /* hook for the object routines to hang on */ 14673 static struct F { /* image manipulation routines */ 14674 XImage* function( 14675 XDisplay* /* display */, 14676 Visual* /* visual */, 14677 uint /* depth */, 14678 int /* format */, 14679 int /* offset */, 14680 ubyte* /* data */, 14681 uint /* width */, 14682 uint /* height */, 14683 int /* bitmap_pad */, 14684 int /* bytes_per_line */) create_image; 14685 int function(XImage *) destroy_image; 14686 arch_ulong function(XImage *, int, int) get_pixel; 14687 int function(XImage *, int, int, arch_ulong) put_pixel; 14688 XImage* function(XImage *, int, int, uint, uint) sub_image; 14689 int function(XImage *, arch_long) add_pixel; 14690 } 14691 F f; 14692 } 14693 version(X86_64) static assert(XImage.sizeof == 136); 14694 else version(X86) static assert(XImage.sizeof == 88); 14695 14696 struct XCharStruct { 14697 short lbearing; /* origin to left edge of raster */ 14698 short rbearing; /* origin to right edge of raster */ 14699 short width; /* advance to next char's origin */ 14700 short ascent; /* baseline to top edge of raster */ 14701 short descent; /* baseline to bottom edge of raster */ 14702 ushort attributes; /* per char flags (not predefined) */ 14703 } 14704 14705 /* 14706 * To allow arbitrary information with fonts, there are additional properties 14707 * returned. 14708 */ 14709 struct XFontProp { 14710 Atom name; 14711 arch_ulong card32; 14712 } 14713 14714 alias Atom Font; 14715 14716 struct XFontStruct { 14717 XExtData *ext_data; /* Hook for extension to hang data */ 14718 Font fid; /* Font ID for this font */ 14719 uint direction; /* Direction the font is painted */ 14720 uint min_char_or_byte2; /* First character */ 14721 uint max_char_or_byte2; /* Last character */ 14722 uint min_byte1; /* First row that exists (for two-byte fonts) */ 14723 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 14724 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 14725 uint default_char; /* Char to print for undefined character */ 14726 int n_properties; /* How many properties there are */ 14727 XFontProp *properties; /* Pointer to array of additional properties*/ 14728 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 14729 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 14730 XCharStruct *per_char; /* first_char to last_char information */ 14731 int ascent; /* Max extent above baseline for spacing */ 14732 int descent; /* Max descent below baseline for spacing */ 14733 } 14734 14735 14736 /* 14737 * Definitions of specific events. 14738 */ 14739 struct XKeyEvent 14740 { 14741 int type; /* of event */ 14742 arch_ulong serial; /* # of last request processed by server */ 14743 Bool send_event; /* true if this came from a SendEvent request */ 14744 Display *display; /* Display the event was read from */ 14745 Window window; /* "event" window it is reported relative to */ 14746 Window root; /* root window that the event occurred on */ 14747 Window subwindow; /* child window */ 14748 Time time; /* milliseconds */ 14749 int x, y; /* pointer x, y coordinates in event window */ 14750 int x_root, y_root; /* coordinates relative to root */ 14751 KeyOrButtonMask state; /* key or button mask */ 14752 uint keycode; /* detail */ 14753 Bool same_screen; /* same screen flag */ 14754 } 14755 version(X86_64) static assert(XKeyEvent.sizeof == 96); 14756 alias XKeyEvent XKeyPressedEvent; 14757 alias XKeyEvent XKeyReleasedEvent; 14758 14759 struct XButtonEvent 14760 { 14761 int type; /* of event */ 14762 arch_ulong serial; /* # of last request processed by server */ 14763 Bool send_event; /* true if this came from a SendEvent request */ 14764 Display *display; /* Display the event was read from */ 14765 Window window; /* "event" window it is reported relative to */ 14766 Window root; /* root window that the event occurred on */ 14767 Window subwindow; /* child window */ 14768 Time time; /* milliseconds */ 14769 int x, y; /* pointer x, y coordinates in event window */ 14770 int x_root, y_root; /* coordinates relative to root */ 14771 KeyOrButtonMask state; /* key or button mask */ 14772 uint button; /* detail */ 14773 Bool same_screen; /* same screen flag */ 14774 } 14775 alias XButtonEvent XButtonPressedEvent; 14776 alias XButtonEvent XButtonReleasedEvent; 14777 14778 struct XMotionEvent{ 14779 int type; /* of event */ 14780 arch_ulong serial; /* # of last request processed by server */ 14781 Bool send_event; /* true if this came from a SendEvent request */ 14782 Display *display; /* Display the event was read from */ 14783 Window window; /* "event" window reported relative to */ 14784 Window root; /* root window that the event occurred on */ 14785 Window subwindow; /* child window */ 14786 Time time; /* milliseconds */ 14787 int x, y; /* pointer x, y coordinates in event window */ 14788 int x_root, y_root; /* coordinates relative to root */ 14789 KeyOrButtonMask state; /* key or button mask */ 14790 byte is_hint; /* detail */ 14791 Bool same_screen; /* same screen flag */ 14792 } 14793 alias XMotionEvent XPointerMovedEvent; 14794 14795 struct XCrossingEvent{ 14796 int type; /* of event */ 14797 arch_ulong serial; /* # of last request processed by server */ 14798 Bool send_event; /* true if this came from a SendEvent request */ 14799 Display *display; /* Display the event was read from */ 14800 Window window; /* "event" window reported relative to */ 14801 Window root; /* root window that the event occurred on */ 14802 Window subwindow; /* child window */ 14803 Time time; /* milliseconds */ 14804 int x, y; /* pointer x, y coordinates in event window */ 14805 int x_root, y_root; /* coordinates relative to root */ 14806 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 14807 NotifyDetail detail; 14808 /* 14809 * NotifyAncestor, NotifyVirtual, NotifyInferior, 14810 * NotifyNonlinear,NotifyNonlinearVirtual 14811 */ 14812 Bool same_screen; /* same screen flag */ 14813 Bool focus; /* Boolean focus */ 14814 KeyOrButtonMask state; /* key or button mask */ 14815 } 14816 alias XCrossingEvent XEnterWindowEvent; 14817 alias XCrossingEvent XLeaveWindowEvent; 14818 14819 struct XFocusChangeEvent{ 14820 int type; /* FocusIn or FocusOut */ 14821 arch_ulong serial; /* # of last request processed by server */ 14822 Bool send_event; /* true if this came from a SendEvent request */ 14823 Display *display; /* Display the event was read from */ 14824 Window window; /* window of event */ 14825 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 14826 NotifyGrab, NotifyUngrab */ 14827 NotifyDetail detail; 14828 /* 14829 * NotifyAncestor, NotifyVirtual, NotifyInferior, 14830 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 14831 * NotifyPointerRoot, NotifyDetailNone 14832 */ 14833 } 14834 alias XFocusChangeEvent XFocusInEvent; 14835 alias XFocusChangeEvent XFocusOutEvent; 14836 14837 enum CWBackPixmap = (1L<<0); 14838 enum CWBackPixel = (1L<<1); 14839 enum CWBorderPixmap = (1L<<2); 14840 enum CWBorderPixel = (1L<<3); 14841 enum CWBitGravity = (1L<<4); 14842 enum CWWinGravity = (1L<<5); 14843 enum CWBackingStore = (1L<<6); 14844 enum CWBackingPlanes = (1L<<7); 14845 enum CWBackingPixel = (1L<<8); 14846 enum CWOverrideRedirect = (1L<<9); 14847 enum CWSaveUnder = (1L<<10); 14848 enum CWEventMask = (1L<<11); 14849 enum CWDontPropagate = (1L<<12); 14850 enum CWColormap = (1L<<13); 14851 enum CWCursor = (1L<<14); 14852 14853 struct XWindowAttributes { 14854 int x, y; /* location of window */ 14855 int width, height; /* width and height of window */ 14856 int border_width; /* border width of window */ 14857 int depth; /* depth of window */ 14858 Visual *visual; /* the associated visual structure */ 14859 Window root; /* root of screen containing window */ 14860 int class_; /* InputOutput, InputOnly*/ 14861 int bit_gravity; /* one of the bit gravity values */ 14862 int win_gravity; /* one of the window gravity values */ 14863 int backing_store; /* NotUseful, WhenMapped, Always */ 14864 arch_ulong backing_planes; /* planes to be preserved if possible */ 14865 arch_ulong backing_pixel; /* value to be used when restoring planes */ 14866 Bool save_under; /* boolean, should bits under be saved? */ 14867 Colormap colormap; /* color map to be associated with window */ 14868 Bool map_installed; /* boolean, is color map currently installed*/ 14869 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 14870 arch_long all_event_masks; /* set of events all people have interest in*/ 14871 arch_long your_event_mask; /* my event mask */ 14872 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 14873 Bool override_redirect; /* boolean value for override-redirect */ 14874 Screen *screen; /* back pointer to correct screen */ 14875 } 14876 14877 enum IsUnmapped = 0; 14878 enum IsUnviewable = 1; 14879 enum IsViewable = 2; 14880 14881 struct XSetWindowAttributes { 14882 Pixmap background_pixmap;/* background, None, or ParentRelative */ 14883 arch_ulong background_pixel;/* background pixel */ 14884 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 14885 arch_ulong border_pixel;/* border pixel value */ 14886 int bit_gravity; /* one of bit gravity values */ 14887 int win_gravity; /* one of the window gravity values */ 14888 int backing_store; /* NotUseful, WhenMapped, Always */ 14889 arch_ulong backing_planes;/* planes to be preserved if possible */ 14890 arch_ulong backing_pixel;/* value to use in restoring planes */ 14891 Bool save_under; /* should bits under be saved? (popups) */ 14892 arch_long event_mask; /* set of events that should be saved */ 14893 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 14894 Bool override_redirect; /* boolean value for override_redirect */ 14895 Colormap colormap; /* color map to be associated with window */ 14896 Cursor cursor; /* cursor to be displayed (or None) */ 14897 } 14898 14899 14900 alias int Status; 14901 14902 14903 enum EventMask:int 14904 { 14905 NoEventMask =0, 14906 KeyPressMask =1<<0, 14907 KeyReleaseMask =1<<1, 14908 ButtonPressMask =1<<2, 14909 ButtonReleaseMask =1<<3, 14910 EnterWindowMask =1<<4, 14911 LeaveWindowMask =1<<5, 14912 PointerMotionMask =1<<6, 14913 PointerMotionHintMask =1<<7, 14914 Button1MotionMask =1<<8, 14915 Button2MotionMask =1<<9, 14916 Button3MotionMask =1<<10, 14917 Button4MotionMask =1<<11, 14918 Button5MotionMask =1<<12, 14919 ButtonMotionMask =1<<13, 14920 KeymapStateMask =1<<14, 14921 ExposureMask =1<<15, 14922 VisibilityChangeMask =1<<16, 14923 StructureNotifyMask =1<<17, 14924 ResizeRedirectMask =1<<18, 14925 SubstructureNotifyMask =1<<19, 14926 SubstructureRedirectMask=1<<20, 14927 FocusChangeMask =1<<21, 14928 PropertyChangeMask =1<<22, 14929 ColormapChangeMask =1<<23, 14930 OwnerGrabButtonMask =1<<24 14931 } 14932 14933 struct MwmHints { 14934 int flags; 14935 int functions; 14936 int decorations; 14937 int input_mode; 14938 int status; 14939 } 14940 14941 enum { 14942 MWM_HINTS_FUNCTIONS = (1L << 0), 14943 MWM_HINTS_DECORATIONS = (1L << 1), 14944 14945 MWM_FUNC_ALL = (1L << 0), 14946 MWM_FUNC_RESIZE = (1L << 1), 14947 MWM_FUNC_MOVE = (1L << 2), 14948 MWM_FUNC_MINIMIZE = (1L << 3), 14949 MWM_FUNC_MAXIMIZE = (1L << 4), 14950 MWM_FUNC_CLOSE = (1L << 5) 14951 } 14952 14953 import core.stdc.config : c_long, c_ulong; 14954 14955 /* Size hints mask bits */ 14956 14957 enum USPosition = (1L << 0) /* user specified x, y */; 14958 enum USSize = (1L << 1) /* user specified width, height */; 14959 enum PPosition = (1L << 2) /* program specified position */; 14960 enum PSize = (1L << 3) /* program specified size */; 14961 enum PMinSize = (1L << 4) /* program specified minimum size */; 14962 enum PMaxSize = (1L << 5) /* program specified maximum size */; 14963 enum PResizeInc = (1L << 6) /* program specified resize increments */; 14964 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 14965 enum PBaseSize = (1L << 8); 14966 enum PWinGravity = (1L << 9); 14967 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 14968 struct XSizeHints { 14969 arch_long flags; /* marks which fields in this structure are defined */ 14970 int x, y; /* Obsolete */ 14971 int width, height; /* Obsolete */ 14972 int min_width, min_height; 14973 int max_width, max_height; 14974 int width_inc, height_inc; 14975 struct Aspect { 14976 int x; /* numerator */ 14977 int y; /* denominator */ 14978 } 14979 14980 Aspect min_aspect; 14981 Aspect max_aspect; 14982 int base_width, base_height; 14983 int win_gravity; 14984 /* this structure may be extended in the future */ 14985 } 14986 14987 14988 14989 enum EventType:int 14990 { 14991 KeyPress =2, 14992 KeyRelease =3, 14993 ButtonPress =4, 14994 ButtonRelease =5, 14995 MotionNotify =6, 14996 EnterNotify =7, 14997 LeaveNotify =8, 14998 FocusIn =9, 14999 FocusOut =10, 15000 KeymapNotify =11, 15001 Expose =12, 15002 GraphicsExpose =13, 15003 NoExpose =14, 15004 VisibilityNotify =15, 15005 CreateNotify =16, 15006 DestroyNotify =17, 15007 UnmapNotify =18, 15008 MapNotify =19, 15009 MapRequest =20, 15010 ReparentNotify =21, 15011 ConfigureNotify =22, 15012 ConfigureRequest =23, 15013 GravityNotify =24, 15014 ResizeRequest =25, 15015 CirculateNotify =26, 15016 CirculateRequest =27, 15017 PropertyNotify =28, 15018 SelectionClear =29, 15019 SelectionRequest =30, 15020 SelectionNotify =31, 15021 ColormapNotify =32, 15022 ClientMessage =33, 15023 MappingNotify =34, 15024 LASTEvent =35 /* must be bigger than any event # */ 15025 } 15026 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 15027 struct XKeymapEvent 15028 { 15029 int type; 15030 arch_ulong serial; /* # of last request processed by server */ 15031 Bool send_event; /* true if this came from a SendEvent request */ 15032 Display *display; /* Display the event was read from */ 15033 Window window; 15034 byte[32] key_vector; 15035 } 15036 15037 struct XExposeEvent 15038 { 15039 int type; 15040 arch_ulong serial; /* # of last request processed by server */ 15041 Bool send_event; /* true if this came from a SendEvent request */ 15042 Display *display; /* Display the event was read from */ 15043 Window window; 15044 int x, y; 15045 int width, height; 15046 int count; /* if non-zero, at least this many more */ 15047 } 15048 15049 struct XGraphicsExposeEvent{ 15050 int type; 15051 arch_ulong serial; /* # of last request processed by server */ 15052 Bool send_event; /* true if this came from a SendEvent request */ 15053 Display *display; /* Display the event was read from */ 15054 Drawable drawable; 15055 int x, y; 15056 int width, height; 15057 int count; /* if non-zero, at least this many more */ 15058 int major_code; /* core is CopyArea or CopyPlane */ 15059 int minor_code; /* not defined in the core */ 15060 } 15061 15062 struct XNoExposeEvent{ 15063 int type; 15064 arch_ulong serial; /* # of last request processed by server */ 15065 Bool send_event; /* true if this came from a SendEvent request */ 15066 Display *display; /* Display the event was read from */ 15067 Drawable drawable; 15068 int major_code; /* core is CopyArea or CopyPlane */ 15069 int minor_code; /* not defined in the core */ 15070 } 15071 15072 struct XVisibilityEvent{ 15073 int type; 15074 arch_ulong serial; /* # of last request processed by server */ 15075 Bool send_event; /* true if this came from a SendEvent request */ 15076 Display *display; /* Display the event was read from */ 15077 Window window; 15078 VisibilityNotify state; /* Visibility state */ 15079 } 15080 15081 struct XCreateWindowEvent{ 15082 int type; 15083 arch_ulong serial; /* # of last request processed by server */ 15084 Bool send_event; /* true if this came from a SendEvent request */ 15085 Display *display; /* Display the event was read from */ 15086 Window parent; /* parent of the window */ 15087 Window window; /* window id of window created */ 15088 int x, y; /* window location */ 15089 int width, height; /* size of window */ 15090 int border_width; /* border width */ 15091 Bool override_redirect; /* creation should be overridden */ 15092 } 15093 15094 struct XDestroyWindowEvent 15095 { 15096 int type; 15097 arch_ulong serial; /* # of last request processed by server */ 15098 Bool send_event; /* true if this came from a SendEvent request */ 15099 Display *display; /* Display the event was read from */ 15100 Window event; 15101 Window window; 15102 } 15103 15104 struct XUnmapEvent 15105 { 15106 int type; 15107 arch_ulong serial; /* # of last request processed by server */ 15108 Bool send_event; /* true if this came from a SendEvent request */ 15109 Display *display; /* Display the event was read from */ 15110 Window event; 15111 Window window; 15112 Bool from_configure; 15113 } 15114 15115 struct XMapEvent 15116 { 15117 int type; 15118 arch_ulong serial; /* # of last request processed by server */ 15119 Bool send_event; /* true if this came from a SendEvent request */ 15120 Display *display; /* Display the event was read from */ 15121 Window event; 15122 Window window; 15123 Bool override_redirect; /* Boolean, is override set... */ 15124 } 15125 15126 struct XMapRequestEvent 15127 { 15128 int type; 15129 arch_ulong serial; /* # of last request processed by server */ 15130 Bool send_event; /* true if this came from a SendEvent request */ 15131 Display *display; /* Display the event was read from */ 15132 Window parent; 15133 Window window; 15134 } 15135 15136 struct XReparentEvent 15137 { 15138 int type; 15139 arch_ulong serial; /* # of last request processed by server */ 15140 Bool send_event; /* true if this came from a SendEvent request */ 15141 Display *display; /* Display the event was read from */ 15142 Window event; 15143 Window window; 15144 Window parent; 15145 int x, y; 15146 Bool override_redirect; 15147 } 15148 15149 struct XConfigureEvent 15150 { 15151 int type; 15152 arch_ulong serial; /* # of last request processed by server */ 15153 Bool send_event; /* true if this came from a SendEvent request */ 15154 Display *display; /* Display the event was read from */ 15155 Window event; 15156 Window window; 15157 int x, y; 15158 int width, height; 15159 int border_width; 15160 Window above; 15161 Bool override_redirect; 15162 } 15163 15164 struct XGravityEvent 15165 { 15166 int type; 15167 arch_ulong serial; /* # of last request processed by server */ 15168 Bool send_event; /* true if this came from a SendEvent request */ 15169 Display *display; /* Display the event was read from */ 15170 Window event; 15171 Window window; 15172 int x, y; 15173 } 15174 15175 struct XResizeRequestEvent 15176 { 15177 int type; 15178 arch_ulong serial; /* # of last request processed by server */ 15179 Bool send_event; /* true if this came from a SendEvent request */ 15180 Display *display; /* Display the event was read from */ 15181 Window window; 15182 int width, height; 15183 } 15184 15185 struct XConfigureRequestEvent 15186 { 15187 int type; 15188 arch_ulong serial; /* # of last request processed by server */ 15189 Bool send_event; /* true if this came from a SendEvent request */ 15190 Display *display; /* Display the event was read from */ 15191 Window parent; 15192 Window window; 15193 int x, y; 15194 int width, height; 15195 int border_width; 15196 Window above; 15197 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 15198 arch_ulong value_mask; 15199 } 15200 15201 struct XCirculateEvent 15202 { 15203 int type; 15204 arch_ulong serial; /* # of last request processed by server */ 15205 Bool send_event; /* true if this came from a SendEvent request */ 15206 Display *display; /* Display the event was read from */ 15207 Window event; 15208 Window window; 15209 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 15210 } 15211 15212 struct XCirculateRequestEvent 15213 { 15214 int type; 15215 arch_ulong serial; /* # of last request processed by server */ 15216 Bool send_event; /* true if this came from a SendEvent request */ 15217 Display *display; /* Display the event was read from */ 15218 Window parent; 15219 Window window; 15220 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 15221 } 15222 15223 struct XPropertyEvent 15224 { 15225 int type; 15226 arch_ulong serial; /* # of last request processed by server */ 15227 Bool send_event; /* true if this came from a SendEvent request */ 15228 Display *display; /* Display the event was read from */ 15229 Window window; 15230 Atom atom; 15231 Time time; 15232 PropertyNotification state; /* NewValue, Deleted */ 15233 } 15234 15235 struct XSelectionClearEvent 15236 { 15237 int type; 15238 arch_ulong serial; /* # of last request processed by server */ 15239 Bool send_event; /* true if this came from a SendEvent request */ 15240 Display *display; /* Display the event was read from */ 15241 Window window; 15242 Atom selection; 15243 Time time; 15244 } 15245 15246 struct XSelectionRequestEvent 15247 { 15248 int type; 15249 arch_ulong serial; /* # of last request processed by server */ 15250 Bool send_event; /* true if this came from a SendEvent request */ 15251 Display *display; /* Display the event was read from */ 15252 Window owner; 15253 Window requestor; 15254 Atom selection; 15255 Atom target; 15256 Atom property; 15257 Time time; 15258 } 15259 15260 struct XSelectionEvent 15261 { 15262 int type; 15263 arch_ulong serial; /* # of last request processed by server */ 15264 Bool send_event; /* true if this came from a SendEvent request */ 15265 Display *display; /* Display the event was read from */ 15266 Window requestor; 15267 Atom selection; 15268 Atom target; 15269 Atom property; /* ATOM or None */ 15270 Time time; 15271 } 15272 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 15273 15274 struct XColormapEvent 15275 { 15276 int type; 15277 arch_ulong serial; /* # of last request processed by server */ 15278 Bool send_event; /* true if this came from a SendEvent request */ 15279 Display *display; /* Display the event was read from */ 15280 Window window; 15281 Colormap colormap; /* COLORMAP or None */ 15282 Bool new_; /* C++ */ 15283 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 15284 } 15285 version(X86_64) static assert(XColormapEvent.sizeof == 56); 15286 15287 struct XClientMessageEvent 15288 { 15289 int type; 15290 arch_ulong serial; /* # of last request processed by server */ 15291 Bool send_event; /* true if this came from a SendEvent request */ 15292 Display *display; /* Display the event was read from */ 15293 Window window; 15294 Atom message_type; 15295 int format; 15296 union Data{ 15297 byte[20] b; 15298 short[10] s; 15299 arch_ulong[5] l; 15300 } 15301 Data data; 15302 15303 } 15304 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 15305 15306 struct XMappingEvent 15307 { 15308 int type; 15309 arch_ulong serial; /* # of last request processed by server */ 15310 Bool send_event; /* true if this came from a SendEvent request */ 15311 Display *display; /* Display the event was read from */ 15312 Window window; /* unused */ 15313 MappingType request; /* one of MappingModifier, MappingKeyboard, 15314 MappingPointer */ 15315 int first_keycode; /* first keycode */ 15316 int count; /* defines range of change w. first_keycode*/ 15317 } 15318 15319 struct XErrorEvent 15320 { 15321 int type; 15322 Display *display; /* Display the event was read from */ 15323 XID resourceid; /* resource id */ 15324 arch_ulong serial; /* serial number of failed request */ 15325 ubyte error_code; /* error code of failed request */ 15326 ubyte request_code; /* Major op-code of failed request */ 15327 ubyte minor_code; /* Minor op-code of failed request */ 15328 } 15329 15330 struct XAnyEvent 15331 { 15332 int type; 15333 arch_ulong serial; /* # of last request processed by server */ 15334 Bool send_event; /* true if this came from a SendEvent request */ 15335 Display *display;/* Display the event was read from */ 15336 Window window; /* window on which event was requested in event mask */ 15337 } 15338 15339 union XEvent{ 15340 int type; /* must not be changed; first element */ 15341 XAnyEvent xany; 15342 XKeyEvent xkey; 15343 XButtonEvent xbutton; 15344 XMotionEvent xmotion; 15345 XCrossingEvent xcrossing; 15346 XFocusChangeEvent xfocus; 15347 XExposeEvent xexpose; 15348 XGraphicsExposeEvent xgraphicsexpose; 15349 XNoExposeEvent xnoexpose; 15350 XVisibilityEvent xvisibility; 15351 XCreateWindowEvent xcreatewindow; 15352 XDestroyWindowEvent xdestroywindow; 15353 XUnmapEvent xunmap; 15354 XMapEvent xmap; 15355 XMapRequestEvent xmaprequest; 15356 XReparentEvent xreparent; 15357 XConfigureEvent xconfigure; 15358 XGravityEvent xgravity; 15359 XResizeRequestEvent xresizerequest; 15360 XConfigureRequestEvent xconfigurerequest; 15361 XCirculateEvent xcirculate; 15362 XCirculateRequestEvent xcirculaterequest; 15363 XPropertyEvent xproperty; 15364 XSelectionClearEvent xselectionclear; 15365 XSelectionRequestEvent xselectionrequest; 15366 XSelectionEvent xselection; 15367 XColormapEvent xcolormap; 15368 XClientMessageEvent xclient; 15369 XMappingEvent xmapping; 15370 XErrorEvent xerror; 15371 XKeymapEvent xkeymap; 15372 arch_ulong[24] pad; 15373 } 15374 15375 15376 struct Display { 15377 XExtData *ext_data; /* hook for extension to hang data */ 15378 _XPrivate *private1; 15379 int fd; /* Network socket. */ 15380 int private2; 15381 int proto_major_version;/* major version of server's X protocol */ 15382 int proto_minor_version;/* minor version of servers X protocol */ 15383 char *vendor; /* vendor of the server hardware */ 15384 XID private3; 15385 XID private4; 15386 XID private5; 15387 int private6; 15388 XID function(Display*)resource_alloc;/* allocator function */ 15389 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 15390 int bitmap_unit; /* padding and data requirements */ 15391 int bitmap_pad; /* padding requirements on bitmaps */ 15392 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 15393 int nformats; /* number of pixmap formats in list */ 15394 ScreenFormat *pixmap_format; /* pixmap format list */ 15395 int private8; 15396 int release; /* release of the server */ 15397 _XPrivate *private9; 15398 _XPrivate *private10; 15399 int qlen; /* Length of input event queue */ 15400 arch_ulong last_request_read; /* seq number of last event read */ 15401 arch_ulong request; /* sequence number of last request. */ 15402 XPointer private11; 15403 XPointer private12; 15404 XPointer private13; 15405 XPointer private14; 15406 uint max_request_size; /* maximum number 32 bit words in request*/ 15407 _XrmHashBucketRec *db; 15408 int function (Display*)private15; 15409 char *display_name; /* "host:display" string used on this connect*/ 15410 int default_screen; /* default screen for operations */ 15411 int nscreens; /* number of screens on this server*/ 15412 Screen *screens; /* pointer to list of screens */ 15413 arch_ulong motion_buffer; /* size of motion buffer */ 15414 arch_ulong private16; 15415 int min_keycode; /* minimum defined keycode */ 15416 int max_keycode; /* maximum defined keycode */ 15417 XPointer private17; 15418 XPointer private18; 15419 int private19; 15420 byte *xdefaults; /* contents of defaults from server */ 15421 /* there is more to this structure, but it is private to Xlib */ 15422 } 15423 15424 // I got these numbers from a C program as a sanity test 15425 version(X86_64) { 15426 static assert(Display.sizeof == 296); 15427 static assert(XPointer.sizeof == 8); 15428 static assert(XErrorEvent.sizeof == 40); 15429 static assert(XAnyEvent.sizeof == 40); 15430 static assert(XMappingEvent.sizeof == 56); 15431 static assert(XEvent.sizeof == 192); 15432 } else { 15433 static assert(Display.sizeof == 176); 15434 static assert(XPointer.sizeof == 4); 15435 static assert(XEvent.sizeof == 96); 15436 } 15437 15438 struct Depth 15439 { 15440 int depth; /* this depth (Z) of the depth */ 15441 int nvisuals; /* number of Visual types at this depth */ 15442 Visual *visuals; /* list of visuals possible at this depth */ 15443 } 15444 15445 alias void* GC; 15446 alias c_ulong VisualID; 15447 alias XID Colormap; 15448 alias XID Cursor; 15449 alias XID KeySym; 15450 alias uint KeyCode; 15451 enum None = 0; 15452 } 15453 15454 version(without_opengl) {} 15455 else { 15456 extern(C) nothrow @nogc { 15457 15458 15459 static if(!SdpyIsUsingIVGLBinds) { 15460 enum GLX_USE_GL= 1; /* support GLX rendering */ 15461 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 15462 enum GLX_LEVEL= 3; /* level in plane stacking */ 15463 enum GLX_RGBA= 4; /* true if RGBA mode */ 15464 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 15465 enum GLX_STEREO= 6; /* stereo buffering supported */ 15466 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 15467 enum GLX_RED_SIZE= 8; /* number of red component bits */ 15468 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 15469 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 15470 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 15471 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 15472 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 15473 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 15474 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 15475 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 15476 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 15477 15478 15479 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 15480 15481 15482 15483 enum GL_TRUE = 1; 15484 enum GL_FALSE = 0; 15485 alias int GLint; 15486 } 15487 15488 alias XID GLXContextID; 15489 alias XID GLXPixmap; 15490 alias XID GLXDrawable; 15491 alias XID GLXPbuffer; 15492 alias XID GLXWindow; 15493 alias XID GLXFBConfigID; 15494 alias void* GLXContext; 15495 15496 } 15497 } 15498 15499 enum AllocNone = 0; 15500 15501 extern(C) { 15502 /* WARNING, this type not in Xlib spec */ 15503 extern(C) alias XIOErrorHandler = int function (Display* display); 15504 } 15505 15506 extern(C) nothrow @nogc { 15507 struct Screen{ 15508 XExtData *ext_data; /* hook for extension to hang data */ 15509 Display *display; /* back pointer to display structure */ 15510 Window root; /* Root window id. */ 15511 int width, height; /* width and height of screen */ 15512 int mwidth, mheight; /* width and height of in millimeters */ 15513 int ndepths; /* number of depths possible */ 15514 Depth *depths; /* list of allowable depths on the screen */ 15515 int root_depth; /* bits per pixel */ 15516 Visual *root_visual; /* root visual */ 15517 GC default_gc; /* GC for the root root visual */ 15518 Colormap cmap; /* default color map */ 15519 uint white_pixel; 15520 uint black_pixel; /* White and Black pixel values */ 15521 int max_maps, min_maps; /* max and min color maps */ 15522 int backing_store; /* Never, WhenMapped, Always */ 15523 bool save_unders; 15524 int root_input_mask; /* initial root input mask */ 15525 } 15526 15527 struct Visual 15528 { 15529 XExtData *ext_data; /* hook for extension to hang data */ 15530 VisualID visualid; /* visual id of this visual */ 15531 int class_; /* class of screen (monochrome, etc.) */ 15532 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 15533 int bits_per_rgb; /* log base 2 of distinct color values */ 15534 int map_entries; /* color map entries */ 15535 } 15536 15537 alias Display* _XPrivDisplay; 15538 15539 Screen* ScreenOfDisplay(Display* dpy, int scr) { 15540 assert(dpy !is null); 15541 return &dpy.screens[scr]; 15542 } 15543 15544 Window RootWindow(Display *dpy,int scr) { 15545 return ScreenOfDisplay(dpy,scr).root; 15546 } 15547 15548 struct XWMHints { 15549 arch_long flags; 15550 Bool input; 15551 int initial_state; 15552 Pixmap icon_pixmap; 15553 Window icon_window; 15554 int icon_x, icon_y; 15555 Pixmap icon_mask; 15556 XID window_group; 15557 } 15558 15559 struct XClassHint { 15560 char* res_name; 15561 char* res_class; 15562 } 15563 15564 int DefaultScreen(Display *dpy) { 15565 return dpy.default_screen; 15566 } 15567 15568 int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 15569 int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 15570 int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 15571 int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 15572 int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 15573 auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 15574 15575 int ConnectionNumber(Display* dpy) { return dpy.fd; } 15576 15577 enum int AnyPropertyType = 0; 15578 enum int Success = 0; 15579 15580 enum int RevertToNone = None; 15581 enum int PointerRoot = 1; 15582 enum Time CurrentTime = 0; 15583 enum int RevertToPointerRoot = PointerRoot; 15584 enum int RevertToParent = 2; 15585 15586 int DefaultDepthOfDisplay(Display* dpy) { 15587 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 15588 } 15589 15590 Visual* DefaultVisual(Display *dpy,int scr) { 15591 return ScreenOfDisplay(dpy,scr).root_visual; 15592 } 15593 15594 GC DefaultGC(Display *dpy,int scr) { 15595 return ScreenOfDisplay(dpy,scr).default_gc; 15596 } 15597 15598 uint BlackPixel(Display *dpy,int scr) { 15599 return ScreenOfDisplay(dpy,scr).black_pixel; 15600 } 15601 15602 uint WhitePixel(Display *dpy,int scr) { 15603 return ScreenOfDisplay(dpy,scr).white_pixel; 15604 } 15605 15606 alias void* XFontSet; // i think 15607 struct XmbTextItem { 15608 char* chars; 15609 int nchars; 15610 int delta; 15611 XFontSet font_set; 15612 } 15613 15614 struct XTextItem { 15615 char* chars; 15616 int nchars; 15617 int delta; 15618 Font font; 15619 } 15620 15621 enum { 15622 GXclear = 0x0, /* 0 */ 15623 GXand = 0x1, /* src AND dst */ 15624 GXandReverse = 0x2, /* src AND NOT dst */ 15625 GXcopy = 0x3, /* src */ 15626 GXandInverted = 0x4, /* NOT src AND dst */ 15627 GXnoop = 0x5, /* dst */ 15628 GXxor = 0x6, /* src XOR dst */ 15629 GXor = 0x7, /* src OR dst */ 15630 GXnor = 0x8, /* NOT src AND NOT dst */ 15631 GXequiv = 0x9, /* NOT src XOR dst */ 15632 GXinvert = 0xa, /* NOT dst */ 15633 GXorReverse = 0xb, /* src OR NOT dst */ 15634 GXcopyInverted = 0xc, /* NOT src */ 15635 GXorInverted = 0xd, /* NOT src OR dst */ 15636 GXnand = 0xe, /* NOT src OR NOT dst */ 15637 GXset = 0xf, /* 1 */ 15638 } 15639 enum QueueMode : int { 15640 QueuedAlready, 15641 QueuedAfterReading, 15642 QueuedAfterFlush 15643 } 15644 15645 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 15646 15647 struct XPoint { 15648 short x; 15649 short y; 15650 } 15651 15652 enum CoordMode:int { 15653 CoordModeOrigin = 0, 15654 CoordModePrevious = 1 15655 } 15656 15657 enum PolygonShape:int { 15658 Complex = 0, 15659 Nonconvex = 1, 15660 Convex = 2 15661 } 15662 15663 struct XTextProperty { 15664 const(char)* value; /* same as Property routines */ 15665 Atom encoding; /* prop type */ 15666 int format; /* prop data format: 8, 16, or 32 */ 15667 arch_ulong nitems; /* number of data items in value */ 15668 } 15669 15670 version( X86_64 ) { 15671 static assert(XTextProperty.sizeof == 32); 15672 } 15673 15674 15675 struct XGCValues { 15676 int function_; /* logical operation */ 15677 arch_ulong plane_mask;/* plane mask */ 15678 arch_ulong foreground;/* foreground pixel */ 15679 arch_ulong background;/* background pixel */ 15680 int line_width; /* line width */ 15681 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 15682 int cap_style; /* CapNotLast, CapButt, 15683 CapRound, CapProjecting */ 15684 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 15685 int fill_style; /* FillSolid, FillTiled, 15686 FillStippled, FillOpaeueStippled */ 15687 int fill_rule; /* EvenOddRule, WindingRule */ 15688 int arc_mode; /* ArcChord, ArcPieSlice */ 15689 Pixmap tile; /* tile pixmap for tiling operations */ 15690 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 15691 int ts_x_origin; /* offset for tile or stipple operations */ 15692 int ts_y_origin; 15693 Font font; /* default text font for text operations */ 15694 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 15695 Bool graphics_exposures;/* boolean, should exposures be generated */ 15696 int clip_x_origin; /* origin for clipping */ 15697 int clip_y_origin; 15698 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 15699 int dash_offset; /* patterned/dashed line information */ 15700 char dashes; 15701 } 15702 15703 struct XColor { 15704 arch_ulong pixel; 15705 ushort red, green, blue; 15706 byte flags; 15707 byte pad; 15708 } 15709 15710 alias XErrorHandler = int function(Display*, XErrorEvent*); 15711 15712 struct XRectangle { 15713 short x; 15714 short y; 15715 ushort width; 15716 ushort height; 15717 } 15718 15719 enum ClipByChildren = 0; 15720 enum IncludeInferiors = 1; 15721 15722 enum Atom XA_PRIMARY = 1; 15723 enum Atom XA_SECONDARY = 2; 15724 enum Atom XA_STRING = 31; 15725 enum Atom XA_CARDINAL = 6; 15726 enum Atom XA_WM_NAME = 39; 15727 enum Atom XA_ATOM = 4; 15728 enum Atom XA_WINDOW = 33; 15729 enum Atom XA_WM_HINTS = 35; 15730 enum int PropModeAppend = 2; 15731 enum int PropModeReplace = 0; 15732 enum int PropModePrepend = 1; 15733 15734 enum int CopyFromParent = 0; 15735 enum int InputOutput = 1; 15736 15737 // XWMHints 15738 enum InputHint = 1 << 0; 15739 enum StateHint = 1 << 1; 15740 enum IconPixmapHint = (1L << 2); 15741 enum IconWindowHint = (1L << 3); 15742 enum IconPositionHint = (1L << 4); 15743 enum IconMaskHint = (1L << 5); 15744 enum WindowGroupHint = (1L << 6); 15745 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 15746 enum XUrgencyHint = (1L << 8); 15747 15748 // GC Components 15749 enum GCFunction = (1L<<0); 15750 enum GCPlaneMask = (1L<<1); 15751 enum GCForeground = (1L<<2); 15752 enum GCBackground = (1L<<3); 15753 enum GCLineWidth = (1L<<4); 15754 enum GCLineStyle = (1L<<5); 15755 enum GCCapStyle = (1L<<6); 15756 enum GCJoinStyle = (1L<<7); 15757 enum GCFillStyle = (1L<<8); 15758 enum GCFillRule = (1L<<9); 15759 enum GCTile = (1L<<10); 15760 enum GCStipple = (1L<<11); 15761 enum GCTileStipXOrigin = (1L<<12); 15762 enum GCTileStipYOrigin = (1L<<13); 15763 enum GCFont = (1L<<14); 15764 enum GCSubwindowMode = (1L<<15); 15765 enum GCGraphicsExposures= (1L<<16); 15766 enum GCClipXOrigin = (1L<<17); 15767 enum GCClipYOrigin = (1L<<18); 15768 enum GCClipMask = (1L<<19); 15769 enum GCDashOffset = (1L<<20); 15770 enum GCDashList = (1L<<21); 15771 enum GCArcMode = (1L<<22); 15772 enum GCLastBit = 22; 15773 15774 15775 enum int WithdrawnState = 0; 15776 enum int NormalState = 1; 15777 enum int IconicState = 3; 15778 15779 } 15780 } else version (OSXCocoa) { 15781 private: 15782 alias void* id; 15783 alias void* Class; 15784 alias void* SEL; 15785 alias void* IMP; 15786 alias void* Ivar; 15787 alias byte BOOL; 15788 alias const(void)* CFStringRef; 15789 alias const(void)* CFAllocatorRef; 15790 alias const(void)* CFTypeRef; 15791 alias const(void)* CGContextRef; 15792 alias const(void)* CGColorSpaceRef; 15793 alias const(void)* CGImageRef; 15794 alias ulong CGBitmapInfo; 15795 15796 struct objc_super { 15797 id self; 15798 Class superclass; 15799 } 15800 15801 struct CFRange { 15802 long location, length; 15803 } 15804 15805 struct NSPoint { 15806 double x, y; 15807 15808 static fromTuple(T)(T tupl) { 15809 return NSPoint(tupl.tupleof); 15810 } 15811 } 15812 struct NSSize { 15813 double width, height; 15814 } 15815 struct NSRect { 15816 NSPoint origin; 15817 NSSize size; 15818 } 15819 alias NSPoint CGPoint; 15820 alias NSSize CGSize; 15821 alias NSRect CGRect; 15822 15823 struct CGAffineTransform { 15824 double a, b, c, d, tx, ty; 15825 } 15826 15827 enum NSApplicationActivationPolicyRegular = 0; 15828 enum NSBackingStoreBuffered = 2; 15829 enum kCFStringEncodingUTF8 = 0x08000100; 15830 15831 enum : size_t { 15832 NSBorderlessWindowMask = 0, 15833 NSTitledWindowMask = 1 << 0, 15834 NSClosableWindowMask = 1 << 1, 15835 NSMiniaturizableWindowMask = 1 << 2, 15836 NSResizableWindowMask = 1 << 3, 15837 NSTexturedBackgroundWindowMask = 1 << 8 15838 } 15839 15840 enum : ulong { 15841 kCGImageAlphaNone, 15842 kCGImageAlphaPremultipliedLast, 15843 kCGImageAlphaPremultipliedFirst, 15844 kCGImageAlphaLast, 15845 kCGImageAlphaFirst, 15846 kCGImageAlphaNoneSkipLast, 15847 kCGImageAlphaNoneSkipFirst 15848 } 15849 enum : ulong { 15850 kCGBitmapAlphaInfoMask = 0x1F, 15851 kCGBitmapFloatComponents = (1 << 8), 15852 kCGBitmapByteOrderMask = 0x7000, 15853 kCGBitmapByteOrderDefault = (0 << 12), 15854 kCGBitmapByteOrder16Little = (1 << 12), 15855 kCGBitmapByteOrder32Little = (2 << 12), 15856 kCGBitmapByteOrder16Big = (3 << 12), 15857 kCGBitmapByteOrder32Big = (4 << 12) 15858 } 15859 enum CGPathDrawingMode { 15860 kCGPathFill, 15861 kCGPathEOFill, 15862 kCGPathStroke, 15863 kCGPathFillStroke, 15864 kCGPathEOFillStroke 15865 } 15866 enum objc_AssociationPolicy : size_t { 15867 OBJC_ASSOCIATION_ASSIGN = 0, 15868 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 15869 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 15870 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 15871 OBJC_ASSOCIATION_COPY = 0x303 //01403 15872 } 15873 15874 extern(C) { 15875 id objc_msgSend(id receiver, SEL selector, ...); 15876 id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...); 15877 id objc_getClass(const(char)* name); 15878 SEL sel_registerName(const(char)* str); 15879 Class objc_allocateClassPair(Class superclass, const(char)* name, 15880 size_t extra_bytes); 15881 void objc_registerClassPair(Class cls); 15882 BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types); 15883 id objc_getAssociatedObject(id object, void* key); 15884 void objc_setAssociatedObject(id object, void* key, id value, 15885 objc_AssociationPolicy policy); 15886 Ivar class_getInstanceVariable(Class cls, const(char)* name); 15887 id object_getIvar(id object, Ivar ivar); 15888 void object_setIvar(id object, Ivar ivar, id value); 15889 BOOL class_addIvar(Class cls, const(char)* name, 15890 size_t size, ubyte alignment, const(char)* types); 15891 15892 extern __gshared id NSApp; 15893 15894 void CFRelease(CFTypeRef obj); 15895 15896 CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator, 15897 const(char)* bytes, long numBytes, 15898 long encoding, 15899 BOOL isExternalRepresentation); 15900 long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding, 15901 char lossByte, bool isExternalRepresentation, 15902 char* buffer, long maxBufLen, long* usedBufLen); 15903 long CFStringGetLength(CFStringRef theString); 15904 15905 CGContextRef CGBitmapContextCreate(void* data, 15906 size_t width, size_t height, 15907 size_t bitsPerComponent, 15908 size_t bytesPerRow, 15909 CGColorSpaceRef colorspace, 15910 CGBitmapInfo bitmapInfo); 15911 void CGContextRelease(CGContextRef c); 15912 ubyte* CGBitmapContextGetData(CGContextRef c); 15913 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 15914 size_t CGBitmapContextGetWidth(CGContextRef c); 15915 size_t CGBitmapContextGetHeight(CGContextRef c); 15916 15917 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 15918 void CGColorSpaceRelease(CGColorSpaceRef cs); 15919 15920 void CGContextSetRGBStrokeColor(CGContextRef c, 15921 double red, double green, double blue, 15922 double alpha); 15923 void CGContextSetRGBFillColor(CGContextRef c, 15924 double red, double green, double blue, 15925 double alpha); 15926 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 15927 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, 15928 const(char)* str, size_t length); 15929 void CGContextStrokeLineSegments(CGContextRef c, 15930 const(CGPoint)* points, size_t count); 15931 15932 void CGContextBeginPath(CGContextRef c); 15933 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 15934 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 15935 void CGContextAddArc(CGContextRef c, double x, double y, double radius, 15936 double startAngle, double endAngle, long clockwise); 15937 void CGContextAddRect(CGContextRef c, CGRect rect); 15938 void CGContextAddLines(CGContextRef c, 15939 const(CGPoint)* points, size_t count); 15940 void CGContextSaveGState(CGContextRef c); 15941 void CGContextRestoreGState(CGContextRef c); 15942 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, 15943 ulong textEncoding); 15944 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 15945 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 15946 15947 void CGImageRelease(CGImageRef image); 15948 } 15949 15950 private: 15951 // A convenient method to create a CFString (=NSString) from a D string. 15952 CFStringRef createCFString(string str) { 15953 return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length, 15954 kCFStringEncodingUTF8, false); 15955 } 15956 15957 // Objective-C calls. 15958 RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) { 15959 auto _cmd = sel_registerName(selector.ptr); 15960 alias extern(C) RetType function(id, SEL, T) ExpectedType; 15961 return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args); 15962 } 15963 RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) { 15964 auto _cmd = sel_registerName(selector.ptr); 15965 auto cls = objc_getClass(className); 15966 alias extern(C) RetType function(id, SEL, T) ExpectedType; 15967 return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args); 15968 } 15969 RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) { 15970 return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args); 15971 } 15972 15973 alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay; 15974 alias objc_msgSend_classMethod!("alloc", id) alloc; 15975 alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:", 15976 id, NSRect, size_t, size_t, BOOL) initWithContentRect; 15977 alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle; 15978 alias objc_msgSend_specialized!("center", void) center; 15979 alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame; 15980 alias objc_msgSend_specialized!("setContentView:", void, id) setContentView; 15981 alias objc_msgSend_specialized!("release", void) release; 15982 alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor; 15983 alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor; 15984 alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront; 15985 alias objc_msgSend_specialized!("invalidate", void) invalidate; 15986 alias objc_msgSend_specialized!("close", void) close; 15987 alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:", 15988 id, double, id, SEL, id, BOOL) scheduledTimer; 15989 alias objc_msgSend_specialized!("run", void) run; 15990 alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext", 15991 id) currentNSGraphicsContext; 15992 alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort; 15993 alias objc_msgSend_specialized!("characters", CFStringRef) characters; 15994 alias objc_msgSend_specialized!("superclass", Class) superclass; 15995 alias objc_msgSend_specialized!("init", id) init; 15996 alias objc_msgSend_specialized!("addItem:", void, id) addItem; 15997 alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu; 15998 alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:", 15999 id, CFStringRef, SEL, CFStringRef) initWithTitle; 16000 alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu; 16001 alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate; 16002 alias objc_msgSend_specialized!("activateIgnoringOtherApps:", 16003 void, BOOL) activateIgnoringOtherApps; 16004 alias objc_msgSend_classMethod!("NSApplication", "sharedApplication", 16005 id) sharedNSApplication; 16006 alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy; 16007 } else static assert(0, "Unsupported operating system"); 16008 16009 16010 version(OSXCocoa) { 16011 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 16012 // 16013 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 16014 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 16015 // 16016 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 16017 // Probably won't even fully compile right now 16018 16019 import std.math : PI; 16020 import std.algorithm : map; 16021 import std.array : array; 16022 16023 alias SimpleWindow NativeWindowHandle; 16024 alias void delegate(id) NativeEventHandler; 16025 16026 __gshared Ivar simpleWindowIvar; 16027 16028 enum KEY_ESCAPE = 27; 16029 16030 mixin template NativeImageImplementation() { 16031 CGContextRef context; 16032 ubyte* rawData; 16033 final: 16034 16035 void convertToRgbaBytes(ubyte[] where) { 16036 assert(where.length == this.width * this.height * 4); 16037 16038 // if rawData had a length.... 16039 //assert(rawData.length == where.length); 16040 for(long idx = 0; idx < where.length; idx += 4) { 16041 auto alpha = rawData[idx + 3]; 16042 if(alpha == 255) { 16043 where[idx + 0] = rawData[idx + 0]; // r 16044 where[idx + 1] = rawData[idx + 1]; // g 16045 where[idx + 2] = rawData[idx + 2]; // b 16046 where[idx + 3] = rawData[idx + 3]; // a 16047 } else { 16048 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 16049 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 16050 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 16051 where[idx + 3] = rawData[idx + 3]; // a 16052 16053 } 16054 } 16055 } 16056 16057 void setFromRgbaBytes(in ubyte[] where) { 16058 // FIXME: this is probably wrong 16059 assert(where.length == this.width * this.height * 4); 16060 16061 // if rawData had a length.... 16062 //assert(rawData.length == where.length); 16063 for(long idx = 0; idx < where.length; idx += 4) { 16064 auto alpha = rawData[idx + 3]; 16065 if(alpha == 255) { 16066 rawData[idx + 0] = where[idx + 0]; // r 16067 rawData[idx + 1] = where[idx + 1]; // g 16068 rawData[idx + 2] = where[idx + 2]; // b 16069 rawData[idx + 3] = where[idx + 3]; // a 16070 } else { 16071 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 16072 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 16073 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 16074 rawData[idx + 3] = where[idx + 3]; // a 16075 16076 } 16077 } 16078 } 16079 16080 16081 void createImage(int width, int height, bool forcexshm=false) { 16082 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 16083 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 16084 colorSpace, 16085 kCGImageAlphaPremultipliedLast 16086 |kCGBitmapByteOrder32Big); 16087 CGColorSpaceRelease(colorSpace); 16088 rawData = CGBitmapContextGetData(context); 16089 } 16090 void dispose() { 16091 CGContextRelease(context); 16092 } 16093 16094 void setPixel(int x, int y, Color c) { 16095 auto offset = (y * width + x) * 4; 16096 if (c.a == 255) { 16097 rawData[offset + 0] = c.r; 16098 rawData[offset + 1] = c.g; 16099 rawData[offset + 2] = c.b; 16100 rawData[offset + 3] = c.a; 16101 } else { 16102 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 16103 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 16104 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 16105 rawData[offset + 3] = c.a; 16106 } 16107 } 16108 } 16109 16110 mixin template NativeScreenPainterImplementation() { 16111 CGContextRef context; 16112 ubyte[4] _outlineComponents; 16113 id view; 16114 16115 void create(NativeWindowHandle window) { 16116 context = window.drawingContext; 16117 view = window.view; 16118 } 16119 16120 void dispose() { 16121 setNeedsDisplay(view, true); 16122 } 16123 16124 // NotYetImplementedException 16125 Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); } 16126 void rasterOp(RasterOp op) {} 16127 Pen _activePen; 16128 Color _fillColor; 16129 Rectangle _clipRectangle; 16130 void setClipRectangle(int, int, int, int) {} 16131 void setFont(OperatingSystemFont) {} 16132 int fontHeight() { return 14; } 16133 16134 // end 16135 16136 void pen(Pen pen) { 16137 _activePen = pen; 16138 auto color = pen.color; // FIXME 16139 double alphaComponent = color.a/255.0f; 16140 CGContextSetRGBStrokeColor(context, 16141 color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 16142 16143 if (color.a != 255) { 16144 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 16145 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 16146 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 16147 _outlineComponents[3] = color.a; 16148 } else { 16149 _outlineComponents[0] = color.r; 16150 _outlineComponents[1] = color.g; 16151 _outlineComponents[2] = color.b; 16152 _outlineComponents[3] = color.a; 16153 } 16154 } 16155 16156 @property void fillColor(Color color) { 16157 CGContextSetRGBFillColor(context, 16158 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 16159 } 16160 16161 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 16162 // NotYetImplementedException for upper left/width/height 16163 auto cgImage = CGBitmapContextCreateImage(image.context); 16164 auto size = CGSize(CGBitmapContextGetWidth(image.context), 16165 CGBitmapContextGetHeight(image.context)); 16166 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 16167 CGImageRelease(cgImage); 16168 } 16169 16170 version(OSXCocoa) {} else // NotYetImplementedException 16171 void drawPixmap(Sprite image, int x, int y) { 16172 // FIXME: is this efficient? 16173 auto cgImage = CGBitmapContextCreateImage(image.context); 16174 auto size = CGSize(CGBitmapContextGetWidth(image.context), 16175 CGBitmapContextGetHeight(image.context)); 16176 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 16177 CGImageRelease(cgImage); 16178 } 16179 16180 16181 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 16182 // FIXME: alignment 16183 if (_outlineComponents[3] != 0) { 16184 CGContextSaveGState(context); 16185 auto invAlpha = 1.0f/_outlineComponents[3]; 16186 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 16187 _outlineComponents[1]*invAlpha, 16188 _outlineComponents[2]*invAlpha, 16189 _outlineComponents[3]/255.0f); 16190 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 16191 // auto cfstr = cast(id)createCFString(text); 16192 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 16193 // NSPoint(x, y), null); 16194 // CFRelease(cfstr); 16195 CGContextRestoreGState(context); 16196 } 16197 } 16198 16199 void drawPixel(int x, int y) { 16200 auto rawData = CGBitmapContextGetData(context); 16201 auto width = CGBitmapContextGetWidth(context); 16202 auto height = CGBitmapContextGetHeight(context); 16203 auto offset = ((height - y - 1) * width + x) * 4; 16204 rawData[offset .. offset+4] = _outlineComponents; 16205 } 16206 16207 void drawLine(int x1, int y1, int x2, int y2) { 16208 CGPoint[2] linePoints; 16209 linePoints[0] = CGPoint(x1, y1); 16210 linePoints[1] = CGPoint(x2, y2); 16211 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 16212 } 16213 16214 void drawRectangle(int x, int y, int width, int height) { 16215 CGContextBeginPath(context); 16216 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 16217 CGContextAddRect(context, rect); 16218 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 16219 } 16220 16221 void drawEllipse(int x1, int y1, int x2, int y2) { 16222 CGContextBeginPath(context); 16223 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 16224 CGContextAddEllipseInRect(context, rect); 16225 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 16226 } 16227 16228 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 16229 // @@@BUG@@@ Does not support elliptic arc (width != height). 16230 CGContextBeginPath(context); 16231 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 16232 start*PI/(180*64), finish*PI/(180*64), 0); 16233 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 16234 } 16235 16236 void drawPolygon(Point[] intPoints) { 16237 CGContextBeginPath(context); 16238 auto points = array(map!(CGPoint.fromTuple)(intPoints)); 16239 CGContextAddLines(context, points.ptr, points.length); 16240 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 16241 } 16242 } 16243 16244 mixin template NativeSimpleWindowImplementation() { 16245 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 16246 synchronized { 16247 if (NSApp == null) initializeApp(); 16248 } 16249 16250 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 16251 16252 // create the window. 16253 window = initWithContentRect(alloc("NSWindow"), 16254 contentRect, 16255 NSTitledWindowMask 16256 |NSClosableWindowMask 16257 |NSMiniaturizableWindowMask 16258 |NSResizableWindowMask, 16259 NSBackingStoreBuffered, 16260 true); 16261 16262 // set the title & move the window to center. 16263 auto windowTitle = createCFString(title); 16264 setTitle(window, windowTitle); 16265 CFRelease(windowTitle); 16266 center(window); 16267 16268 // create area to draw on. 16269 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 16270 drawingContext = CGBitmapContextCreate(null, width, height, 16271 8, 4*width, colorSpace, 16272 kCGImageAlphaPremultipliedLast 16273 |kCGBitmapByteOrder32Big); 16274 CGColorSpaceRelease(colorSpace); 16275 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 16276 auto matrix = CGContextGetTextMatrix(drawingContext); 16277 matrix.c = -matrix.c; 16278 matrix.d = -matrix.d; 16279 CGContextSetTextMatrix(drawingContext, matrix); 16280 16281 // create the subview that things will be drawn on. 16282 view = initWithFrame(alloc("SDGraphicsView"), contentRect); 16283 setContentView(window, view); 16284 object_setIvar(view, simpleWindowIvar, cast(id)this); 16285 release(view); 16286 16287 setBackgroundColor(window, whiteNSColor); 16288 makeKeyAndOrderFront(window, null); 16289 } 16290 void dispose() { 16291 closeWindow(); 16292 release(window); 16293 } 16294 void closeWindow() { 16295 invalidate(timer); 16296 .close(window); 16297 } 16298 16299 ScreenPainter getPainter() { 16300 return ScreenPainter(this, this); 16301 } 16302 16303 id window; 16304 id timer; 16305 id view; 16306 CGContextRef drawingContext; 16307 } 16308 16309 extern(C) { 16310 private: 16311 BOOL returnTrue3(id self, SEL _cmd, id app) { 16312 return true; 16313 } 16314 BOOL returnTrue2(id self, SEL _cmd) { 16315 return true; 16316 } 16317 16318 void pulse(id self, SEL _cmd) { 16319 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 16320 simpleWindow.handlePulse(); 16321 setNeedsDisplay(self, true); 16322 } 16323 void drawRect(id self, SEL _cmd, NSRect rect) { 16324 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 16325 auto curCtx = graphicsPort(currentNSGraphicsContext); 16326 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 16327 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), 16328 CGBitmapContextGetHeight(simpleWindow.drawingContext)); 16329 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 16330 CGImageRelease(cgImage); 16331 } 16332 void keyDown(id self, SEL _cmd, id event) { 16333 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 16334 16335 // the event may have multiple characters, and we send them all at 16336 // once. 16337 if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) { 16338 auto chars = characters(event); 16339 auto range = CFRange(0, CFStringGetLength(chars)); 16340 auto buffer = new char[range.length*3]; 16341 long actualLength; 16342 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false, 16343 buffer.ptr, cast(int) buffer.length, &actualLength); 16344 foreach (dchar dc; buffer[0..actualLength]) { 16345 if (simpleWindow.handleCharEvent) 16346 simpleWindow.handleCharEvent(dc); 16347 // NotYetImplementedException 16348 //if (simpleWindow.handleKeyEvent) 16349 //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp? 16350 } 16351 } 16352 16353 // the event's 'keyCode' is hardware-dependent. I don't think people 16354 // will like it. Let's leave it to the native handler. 16355 16356 // perform the default action. 16357 16358 // so the default action is to make a bomp sound and i dont want that 16359 // sooooooooo yeah not gonna do that. 16360 16361 //auto superData = objc_super(self, superclass(self)); 16362 //alias extern(C) void function(objc_super*, SEL, id) T; 16363 //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event); 16364 } 16365 } 16366 16367 // initialize the app so that it can be interacted with the user. 16368 // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html 16369 private void initializeApp() { 16370 // push an autorelease pool to avoid leaking. 16371 init(alloc("NSAutoreleasePool")); 16372 16373 // create a new NSApp instance 16374 sharedNSApplication; 16375 setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular); 16376 16377 // create the "Quit" menu. 16378 auto menuBar = init(alloc("NSMenu")); 16379 auto appMenuItem = init(alloc("NSMenuItem")); 16380 addItem(menuBar, appMenuItem); 16381 setMainMenu(NSApp, menuBar); 16382 release(appMenuItem); 16383 release(menuBar); 16384 16385 auto appMenu = init(alloc("NSMenu")); 16386 auto quitTitle = createCFString("Quit"); 16387 auto q = createCFString("q"); 16388 auto quitItem = initWithTitle(alloc("NSMenuItem"), 16389 quitTitle, sel_registerName("terminate:"), q); 16390 addItem(appMenu, quitItem); 16391 setSubmenu(appMenuItem, appMenu); 16392 release(quitItem); 16393 release(appMenu); 16394 CFRelease(q); 16395 CFRelease(quitTitle); 16396 16397 // assign a delegate for the application, allow it to quit when the last 16398 // window is closed. 16399 auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), 16400 "SDWindowCloseDelegate", 0); 16401 class_addMethod(delegateClass, 16402 sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"), 16403 &returnTrue3, "c@:@"); 16404 objc_registerClassPair(delegateClass); 16405 16406 auto appDelegate = init(alloc("SDWindowCloseDelegate")); 16407 setDelegate(NSApp, appDelegate); 16408 activateIgnoringOtherApps(NSApp, true); 16409 16410 // create a new view that draws the graphics and respond to keyDown 16411 // events. 16412 auto viewClass = objc_allocateClassPair(objc_getClass("NSView"), 16413 "SDGraphicsView", (void*).sizeof); 16414 class_addIvar(viewClass, "simpledisplay_simpleWindow", 16415 (void*).sizeof, (void*).alignof, "^v"); 16416 class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"), 16417 &pulse, "v@:"); 16418 class_addMethod(viewClass, sel_registerName("drawRect:"), 16419 &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); 16420 class_addMethod(viewClass, sel_registerName("isFlipped"), 16421 &returnTrue2, "c@:"); 16422 class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"), 16423 &returnTrue2, "c@:"); 16424 class_addMethod(viewClass, sel_registerName("keyDown:"), 16425 &keyDown, "v@:@"); 16426 objc_registerClassPair(viewClass); 16427 simpleWindowIvar = class_getInstanceVariable(viewClass, 16428 "simpledisplay_simpleWindow"); 16429 } 16430 } 16431 16432 version(without_opengl) {} else 16433 extern(System) nothrow @nogc { 16434 //enum uint GL_VERSION = 0x1F02; 16435 //const(char)* glGetString (/*GLenum*/uint); 16436 version(X11) { 16437 static if (!SdpyIsUsingIVGLBinds) { 16438 16439 enum GLX_X_RENDERABLE = 0x8012; 16440 enum GLX_DRAWABLE_TYPE = 0x8010; 16441 enum GLX_RENDER_TYPE = 0x8011; 16442 enum GLX_X_VISUAL_TYPE = 0x22; 16443 enum GLX_TRUE_COLOR = 0x8002; 16444 enum GLX_WINDOW_BIT = 0x00000001; 16445 enum GLX_RGBA_BIT = 0x00000001; 16446 enum GLX_COLOR_INDEX_BIT = 0x00000002; 16447 enum GLX_SAMPLE_BUFFERS = 0x186a0; 16448 enum GLX_SAMPLES = 0x186a1; 16449 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 16450 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 16451 } 16452 16453 // GLX_EXT_swap_control 16454 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 16455 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 16456 16457 //k8: ugly code to prevent warnings when sdpy is compiled into .a 16458 extern(System) { 16459 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 16460 } 16461 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 16462 16463 // this made public so we don't have to get it again and again 16464 public bool glXCreateContextAttribsARB_present () { 16465 if (glXCreateContextAttribsARBFn is cast(void*)1) { 16466 // get it 16467 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 16468 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 16469 } 16470 return (glXCreateContextAttribsARBFn !is null); 16471 } 16472 16473 // this made public so we don't have to get it again and again 16474 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 16475 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 16476 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 16477 } 16478 16479 // extern(C) private __gshared int function(int) glXSwapIntervalSGI; // seems totally redundant to the tohers 16480 extern(C) private __gshared int function(int) glXSwapIntervalMESA; 16481 16482 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 16483 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 16484 if (_glx_swapInterval_fn is null) { 16485 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 16486 if (_glx_swapInterval_fn is null) { 16487 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 16488 return; 16489 } 16490 version(sdddd) { import std.stdio; debug writeln("glXSwapIntervalEXT found!"); } 16491 } 16492 16493 if(glXSwapIntervalMESA is null) { 16494 // it seems to require both to actually take effect on many computers 16495 // idk why 16496 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) glXGetProcAddress("glXSwapIntervalMESA"); 16497 if(glXSwapIntervalMESA is null) 16498 glXSwapIntervalMESA = cast(typeof(glXSwapIntervalMESA)) 1; 16499 } 16500 16501 if(cast(void*) glXSwapIntervalMESA > cast(void*) 1) 16502 glXSwapIntervalMESA(wait ? 1 : 0); 16503 16504 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 16505 } 16506 } else version(Windows) { 16507 static if (!SdpyIsUsingIVGLBinds) { 16508 enum GL_TRUE = 1; 16509 enum GL_FALSE = 0; 16510 alias int GLint; 16511 16512 public void* glbindGetProcAddress (const(char)* name) { 16513 void* res = wglGetProcAddress(name); 16514 if (res is null) { 16515 /+ 16516 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 16517 import core.sys.windows.windef, core.sys.windows.winbase; 16518 __gshared HINSTANCE dll = null; 16519 if (dll is null) { 16520 dll = LoadLibraryA("opengl32.dll"); 16521 if (dll is null) return null; // <32, but idc 16522 } 16523 res = GetProcAddress(dll, name); 16524 +/ 16525 res = GetProcAddress(gl.libHandle, name); 16526 } 16527 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 16528 return res; 16529 } 16530 } 16531 16532 16533 private __gshared extern(System) BOOL function(int) wglSwapIntervalEXT; 16534 void wglSetVSync(bool wait) { 16535 if(wglSwapIntervalEXT is null) { 16536 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) wglGetProcAddress("wglSwapIntervalEXT"); 16537 if(wglSwapIntervalEXT is null) 16538 wglSwapIntervalEXT = cast(typeof(wglSwapIntervalEXT)) 1; 16539 } 16540 if(cast(void*) wglSwapIntervalEXT is cast(void*) 1) 16541 return; 16542 16543 wglSwapIntervalEXT(wait ? 1 : 0); 16544 } 16545 16546 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 16547 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 16548 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 16549 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 16550 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 16551 16552 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 16553 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 16554 16555 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 16556 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 16557 16558 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 16559 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 16560 16561 void wglInitOtherFunctions () { 16562 if (wglCreateContextAttribsARB is null) { 16563 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 16564 } 16565 } 16566 } 16567 16568 static if (!SdpyIsUsingIVGLBinds) { 16569 16570 interface GL { 16571 extern(System) @nogc nothrow: 16572 16573 void glGetIntegerv(int, void*); 16574 void glMatrixMode(int); 16575 void glPushMatrix(); 16576 void glLoadIdentity(); 16577 void glOrtho(double, double, double, double, double, double); 16578 void glFrustum(double, double, double, double, double, double); 16579 16580 void glPopMatrix(); 16581 void glEnable(int); 16582 void glDisable(int); 16583 void glClear(int); 16584 void glBegin(int); 16585 void glVertex2f(float, float); 16586 void glVertex3f(float, float, float); 16587 void glEnd(); 16588 void glColor3b(byte, byte, byte); 16589 void glColor3ub(ubyte, ubyte, ubyte); 16590 void glColor4b(byte, byte, byte, byte); 16591 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 16592 void glColor3i(int, int, int); 16593 void glColor3ui(uint, uint, uint); 16594 void glColor4i(int, int, int, int); 16595 void glColor4ui(uint, uint, uint, uint); 16596 void glColor3f(float, float, float); 16597 void glColor4f(float, float, float, float); 16598 void glTranslatef(float, float, float); 16599 void glScalef(float, float, float); 16600 version(X11) { 16601 void glSecondaryColor3b(byte, byte, byte); 16602 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 16603 void glSecondaryColor3i(int, int, int); 16604 void glSecondaryColor3ui(uint, uint, uint); 16605 void glSecondaryColor3f(float, float, float); 16606 } 16607 16608 void glDrawElements(int, int, int, void*); 16609 16610 void glRotatef(float, float, float, float); 16611 16612 uint glGetError(); 16613 16614 void glDeleteTextures(int, uint*); 16615 16616 16617 void glRasterPos2i(int, int); 16618 void glDrawPixels(int, int, uint, uint, void*); 16619 void glClearColor(float, float, float, float); 16620 16621 16622 void glPixelStorei(uint, int); 16623 16624 void glGenTextures(uint, uint*); 16625 void glBindTexture(int, int); 16626 void glTexParameteri(uint, uint, int); 16627 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 16628 void glTexImage2D(int, int, int, int, int, int, int, int, in void*); 16629 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 16630 /*GLsizei*/int width, /*GLsizei*/int height, 16631 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 16632 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 16633 16634 void glLineWidth(int); 16635 16636 16637 void glTexCoord2f(float, float); 16638 void glVertex2i(int, int); 16639 void glBlendFunc (int, int); 16640 void glDepthFunc (int); 16641 void glViewport(int, int, int, int); 16642 16643 void glClearDepth(double); 16644 16645 void glReadBuffer(uint); 16646 void glReadPixels(int, int, int, int, int, int, void*); 16647 16648 void glFlush(); 16649 void glFinish(); 16650 16651 version(Windows) { 16652 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 16653 HGLRC wglCreateContext(HDC); 16654 HGLRC wglCreateLayerContext(HDC, int); 16655 BOOL wglDeleteContext(HGLRC); 16656 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 16657 HGLRC wglGetCurrentContext(); 16658 HDC wglGetCurrentDC(); 16659 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 16660 PROC wglGetProcAddress(LPCSTR); 16661 BOOL wglMakeCurrent(HDC, HGLRC); 16662 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 16663 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 16664 BOOL wglShareLists(HGLRC, HGLRC); 16665 BOOL wglSwapLayerBuffers(HDC, UINT); 16666 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 16667 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 16668 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 16669 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 16670 } 16671 16672 } 16673 16674 interface GL3 { 16675 extern(System) @nogc nothrow: 16676 16677 void glGenVertexArrays(GLsizei, GLuint*); 16678 void glBindVertexArray(GLuint); 16679 void glDeleteVertexArrays(GLsizei, const(GLuint)*); 16680 void glGenerateMipmap(GLenum); 16681 void glBufferSubData(GLenum, GLintptr, GLsizeiptr, const(GLvoid)*); 16682 void glStencilMask(GLuint); 16683 void glStencilFunc(GLenum, GLint, GLuint); 16684 void glGetShaderInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 16685 void glGetProgramInfoLog(GLuint, GLsizei, GLsizei*, GLchar*); 16686 GLuint glCreateProgram(); 16687 GLuint glCreateShader(GLenum); 16688 void glShaderSource(GLuint, GLsizei, const(GLchar*)*, const(GLint)*); 16689 void glCompileShader(GLuint); 16690 void glGetShaderiv(GLuint, GLenum, GLint*); 16691 void glAttachShader(GLuint, GLuint); 16692 void glBindAttribLocation(GLuint, GLuint, const(GLchar)*); 16693 void glLinkProgram(GLuint); 16694 void glGetProgramiv(GLuint, GLenum, GLint*); 16695 void glDeleteProgram(GLuint); 16696 void glDeleteShader(GLuint); 16697 GLint glGetUniformLocation(GLuint, const(GLchar)*); 16698 void glGenBuffers(GLsizei, GLuint*); 16699 void glUniform4fv(GLint, GLsizei, const(GLfloat)*); 16700 void glUniform4f(GLint, float, float, float, float); 16701 void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); 16702 void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); 16703 void glDrawArrays(GLenum, GLint, GLsizei); 16704 void glStencilOp(GLenum, GLenum, GLenum); 16705 void glUseProgram(GLuint); 16706 void glCullFace(GLenum); 16707 void glFrontFace(GLenum); 16708 void glActiveTexture(GLenum); 16709 void glBindBuffer(GLenum, GLuint); 16710 void glBufferData(GLenum, GLsizeiptr, const(void)*, GLenum); 16711 void glEnableVertexAttribArray(GLuint); 16712 void glVertexAttribPointer(GLuint, GLint, GLenum, GLboolean, GLsizei, const(void)*); 16713 void glUniform1i(GLint, GLint); 16714 void glUniform2fv(GLint, GLsizei, const(GLfloat)*); 16715 void glDisableVertexAttribArray(GLuint); 16716 void glDeleteBuffers(GLsizei, const(GLuint)*); 16717 void glBlendFuncSeparate(GLenum, GLenum, GLenum, GLenum); 16718 void glLogicOp (GLenum opcode); 16719 void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); 16720 void glDeleteFramebuffers (GLsizei n, const(GLuint)* framebuffers); 16721 void glGenFramebuffers (GLsizei n, GLuint* framebuffers); 16722 GLenum glCheckFramebufferStatus (GLenum target); 16723 void glBindFramebuffer (GLenum target, GLuint framebuffer); 16724 } 16725 16726 interface GL4 { 16727 extern(System) @nogc nothrow: 16728 16729 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 16730 /*GLsizei*/int width, /*GLsizei*/int height, 16731 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 16732 } 16733 16734 interface GLU { 16735 extern(System) @nogc nothrow: 16736 16737 void gluLookAt(double, double, double, double, double, double, double, double, double); 16738 void gluPerspective(double, double, double, double); 16739 16740 char* gluErrorString(uint); 16741 } 16742 16743 16744 enum GL_RED = 0x1903; 16745 enum GL_ALPHA = 0x1906; 16746 16747 enum uint GL_FRONT = 0x0404; 16748 16749 enum uint GL_BLEND = 0x0be2; 16750 enum uint GL_LEQUAL = 0x0203; 16751 16752 16753 enum uint GL_RGB = 0x1907; 16754 enum uint GL_BGRA = 0x80e1; 16755 enum uint GL_RGBA = 0x1908; 16756 enum uint GL_TEXTURE_2D = 0x0DE1; 16757 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 16758 enum uint GL_NEAREST = 0x2600; 16759 enum uint GL_LINEAR = 0x2601; 16760 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 16761 enum uint GL_TEXTURE_WRAP_S = 0x2802; 16762 enum uint GL_TEXTURE_WRAP_T = 0x2803; 16763 enum uint GL_REPEAT = 0x2901; 16764 enum uint GL_CLAMP = 0x2900; 16765 enum uint GL_CLAMP_TO_EDGE = 0x812F; 16766 enum uint GL_CLAMP_TO_BORDER = 0x812D; 16767 enum uint GL_DECAL = 0x2101; 16768 enum uint GL_MODULATE = 0x2100; 16769 enum uint GL_TEXTURE_ENV = 0x2300; 16770 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 16771 enum uint GL_REPLACE = 0x1E01; 16772 enum uint GL_LIGHTING = 0x0B50; 16773 enum uint GL_DITHER = 0x0BD0; 16774 16775 enum uint GL_NO_ERROR = 0; 16776 16777 16778 16779 enum int GL_VIEWPORT = 0x0BA2; 16780 enum int GL_MODELVIEW = 0x1700; 16781 enum int GL_TEXTURE = 0x1702; 16782 enum int GL_PROJECTION = 0x1701; 16783 enum int GL_DEPTH_TEST = 0x0B71; 16784 16785 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 16786 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 16787 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 16788 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 16789 16790 enum int GL_POINTS = 0x0000; 16791 enum int GL_LINES = 0x0001; 16792 enum int GL_LINE_LOOP = 0x0002; 16793 enum int GL_LINE_STRIP = 0x0003; 16794 enum int GL_TRIANGLES = 0x0004; 16795 enum int GL_TRIANGLE_STRIP = 5; 16796 enum int GL_TRIANGLE_FAN = 6; 16797 enum int GL_QUADS = 7; 16798 enum int GL_QUAD_STRIP = 8; 16799 enum int GL_POLYGON = 9; 16800 16801 alias GLvoid = void; 16802 alias GLboolean = ubyte; 16803 alias GLuint = uint; 16804 alias GLenum = uint; 16805 alias GLchar = char; 16806 alias GLsizei = int; 16807 alias GLfloat = float; 16808 alias GLintptr = size_t; 16809 alias GLsizeiptr = ptrdiff_t; 16810 16811 16812 enum uint GL_INVALID_ENUM = 0x0500; 16813 16814 enum uint GL_ZERO = 0; 16815 enum uint GL_ONE = 1; 16816 16817 enum uint GL_BYTE = 0x1400; 16818 enum uint GL_UNSIGNED_BYTE = 0x1401; 16819 enum uint GL_SHORT = 0x1402; 16820 enum uint GL_UNSIGNED_SHORT = 0x1403; 16821 enum uint GL_INT = 0x1404; 16822 enum uint GL_UNSIGNED_INT = 0x1405; 16823 enum uint GL_FLOAT = 0x1406; 16824 enum uint GL_2_BYTES = 0x1407; 16825 enum uint GL_3_BYTES = 0x1408; 16826 enum uint GL_4_BYTES = 0x1409; 16827 enum uint GL_DOUBLE = 0x140A; 16828 16829 enum uint GL_STREAM_DRAW = 0x88E0; 16830 16831 enum uint GL_CCW = 0x0901; 16832 16833 enum uint GL_STENCIL_TEST = 0x0B90; 16834 enum uint GL_SCISSOR_TEST = 0x0C11; 16835 16836 enum uint GL_EQUAL = 0x0202; 16837 enum uint GL_NOTEQUAL = 0x0205; 16838 16839 enum uint GL_ALWAYS = 0x0207; 16840 enum uint GL_KEEP = 0x1E00; 16841 16842 enum uint GL_INCR = 0x1E02; 16843 16844 enum uint GL_INCR_WRAP = 0x8507; 16845 enum uint GL_DECR_WRAP = 0x8508; 16846 16847 enum uint GL_CULL_FACE = 0x0B44; 16848 enum uint GL_BACK = 0x0405; 16849 16850 enum uint GL_FRAGMENT_SHADER = 0x8B30; 16851 enum uint GL_VERTEX_SHADER = 0x8B31; 16852 16853 enum uint GL_COMPILE_STATUS = 0x8B81; 16854 enum uint GL_LINK_STATUS = 0x8B82; 16855 16856 enum uint GL_ELEMENT_ARRAY_BUFFER = 0x8893; 16857 16858 enum uint GL_STATIC_DRAW = 0x88E4; 16859 16860 enum uint GL_UNPACK_ALIGNMENT = 0x0CF5; 16861 enum uint GL_UNPACK_ROW_LENGTH = 0x0CF2; 16862 enum uint GL_UNPACK_SKIP_PIXELS = 0x0CF4; 16863 enum uint GL_UNPACK_SKIP_ROWS = 0x0CF3; 16864 16865 enum uint GL_GENERATE_MIPMAP = 0x8191; 16866 enum uint GL_LINEAR_MIPMAP_LINEAR = 0x2703; 16867 16868 enum uint GL_TEXTURE0 = 0x84C0U; 16869 enum uint GL_TEXTURE1 = 0x84C1U; 16870 16871 enum uint GL_ARRAY_BUFFER = 0x8892; 16872 16873 enum uint GL_SRC_COLOR = 0x0300; 16874 enum uint GL_ONE_MINUS_SRC_COLOR = 0x0301; 16875 enum uint GL_SRC_ALPHA = 0x0302; 16876 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 16877 enum uint GL_DST_ALPHA = 0x0304; 16878 enum uint GL_ONE_MINUS_DST_ALPHA = 0x0305; 16879 enum uint GL_DST_COLOR = 0x0306; 16880 enum uint GL_ONE_MINUS_DST_COLOR = 0x0307; 16881 enum uint GL_SRC_ALPHA_SATURATE = 0x0308; 16882 16883 enum uint GL_INVERT = 0x150AU; 16884 16885 enum uint GL_DEPTH_STENCIL = 0x84F9U; 16886 enum uint GL_UNSIGNED_INT_24_8 = 0x84FAU; 16887 16888 enum uint GL_FRAMEBUFFER = 0x8D40U; 16889 enum uint GL_COLOR_ATTACHMENT0 = 0x8CE0U; 16890 enum uint GL_DEPTH_STENCIL_ATTACHMENT = 0x821AU; 16891 16892 enum uint GL_FRAMEBUFFER_COMPLETE = 0x8CD5U; 16893 enum uint GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6U; 16894 enum uint GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7U; 16895 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 16896 enum uint GL_FRAMEBUFFER_UNSUPPORTED = 0x8CDDU; 16897 16898 enum uint GL_COLOR_LOGIC_OP = 0x0BF2U; 16899 enum uint GL_CLEAR = 0x1500U; 16900 enum uint GL_COPY = 0x1503U; 16901 enum uint GL_XOR = 0x1506U; 16902 16903 enum uint GL_FRAMEBUFFER_BINDING = 0x8CA6U; 16904 16905 enum uint GL_TEXTURE_LOD_BIAS = 0x8501; 16906 16907 } 16908 } 16909 16910 version(without_opengl) {} else { 16911 static if(!SdpyIsUsingIVGLBinds) { 16912 version(Windows) { 16913 mixin DynamicLoad!(GL, "opengl32", 1, true) gl; 16914 mixin DynamicLoad!(GLU, "glu32", 1, true) glu; 16915 } else { 16916 mixin DynamicLoad!(GL, "GL", 1, true) gl; 16917 mixin DynamicLoad!(GLU, "GLU", 3, true) glu; 16918 } 16919 mixin DynamicLoadSupplementalOpenGL!(GL3) gl3; 16920 16921 16922 shared static this() { 16923 gl.loadDynamicLibrary(); 16924 glu.loadDynamicLibrary(); 16925 } 16926 } 16927 } 16928 16929 /++ 16930 Convenience method for converting D arrays to opengl buffer data 16931 16932 I would LOVE to overload it with the original glBufferData, but D won't 16933 let me since glBufferData is a function pointer :( 16934 16935 Added: August 25, 2020 (version 8.5) 16936 +/ 16937 version(without_opengl) {} else 16938 void glBufferDataSlice(GLenum target, const(void[]) data, GLenum usage) { 16939 glBufferData(target, data.length, data.ptr, usage); 16940 } 16941 16942 /+ 16943 /++ 16944 A matrix for simple uses that easily integrates with [OpenGlShader]. 16945 16946 Might not be useful to you since it only as some simple functions and 16947 probably isn't that fast. 16948 16949 Note it uses an inline static array for its storage, so copying it 16950 may be expensive. 16951 +/ 16952 struct BasicMatrix(int columns, int rows, T = float) { 16953 import core.stdc.math; 16954 16955 T[columns * rows] data = 0.0; 16956 16957 /++ 16958 Basic operations that operate *in place*. 16959 +/ 16960 void translate() { 16961 16962 } 16963 16964 /// ditto 16965 void scale() { 16966 16967 } 16968 16969 /// ditto 16970 void rotate() { 16971 16972 } 16973 16974 /++ 16975 16976 +/ 16977 static if(columns == rows) 16978 static BasicMatrix identity() { 16979 BasicMatrix m; 16980 foreach(i; 0 .. columns) 16981 data[0 + i + i * columns] = 1.0; 16982 return m; 16983 } 16984 16985 static BasicMatrix ortho() { 16986 return BasicMatrix.init; 16987 } 16988 } 16989 +/ 16990 16991 /++ 16992 Convenience class for using opengl shaders. 16993 16994 Ensure that you've loaded opengl 3+ and set your active 16995 context before trying to use this. 16996 16997 Added: August 25, 2020 (version 8.5) 16998 +/ 16999 version(without_opengl) {} else 17000 final class OpenGlShader { 17001 private int shaderProgram_; 17002 private @property void shaderProgram(int a) { 17003 shaderProgram_ = a; 17004 } 17005 /// Get the program ID for use in OpenGL functions. 17006 public @property int shaderProgram() { 17007 return shaderProgram_; 17008 } 17009 17010 /++ 17011 17012 +/ 17013 static struct Source { 17014 uint type; /// GL_FRAGMENT_SHADER, GL_VERTEX_SHADER, etc. 17015 string code; /// 17016 } 17017 17018 /++ 17019 Helper method to just compile some shader code and check for errors 17020 while you do glCreateShader, etc. on the outside yourself. 17021 17022 This just does `glShaderSource` and `glCompileShader` for the given code. 17023 17024 If you the OpenGlShader class constructor, you never need to call this yourself. 17025 +/ 17026 static void compile(int sid, Source code) { 17027 const(char)*[1] buffer; 17028 int[1] lengthBuffer; 17029 17030 buffer[0] = code.code.ptr; 17031 lengthBuffer[0] = cast(int) code.code.length; 17032 17033 glShaderSource(sid, 1, buffer.ptr, lengthBuffer.ptr); 17034 glCompileShader(sid); 17035 17036 int success; 17037 glGetShaderiv(sid, GL_COMPILE_STATUS, &success); 17038 if(!success) { 17039 char[512] info; 17040 int len; 17041 glGetShaderInfoLog(sid, info.length, &len, info.ptr); 17042 17043 throw new Exception("Shader compile failure: " ~ cast(immutable) info[0 .. len]); 17044 } 17045 } 17046 17047 /++ 17048 Calls `glLinkProgram` and throws if error a occurs. 17049 17050 If you the OpenGlShader class constructor, you never need to call this yourself. 17051 +/ 17052 static void link(int shaderProgram) { 17053 glLinkProgram(shaderProgram); 17054 int success; 17055 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 17056 if(!success) { 17057 char[512] info; 17058 int len; 17059 glGetProgramInfoLog(shaderProgram, info.length, &len, info.ptr); 17060 17061 throw new Exception("Shader link failure: " ~ cast(immutable) info[0 .. len]); 17062 } 17063 } 17064 17065 /++ 17066 Constructs the shader object by calling `glCreateProgram`, then 17067 compiling each given [Source], and finally, linking them together. 17068 17069 Throws: on compile or link failure. 17070 +/ 17071 this(Source[] codes...) { 17072 shaderProgram = glCreateProgram(); 17073 17074 int[16] shadersBufferStack; 17075 17076 int[] shadersBuffer = codes.length <= shadersBufferStack.length ? 17077 shadersBufferStack[0 .. codes.length] : 17078 new int[](codes.length); 17079 17080 foreach(idx, code; codes) { 17081 shadersBuffer[idx] = glCreateShader(code.type); 17082 17083 compile(shadersBuffer[idx], code); 17084 17085 glAttachShader(shaderProgram, shadersBuffer[idx]); 17086 } 17087 17088 link(shaderProgram); 17089 17090 foreach(s; shadersBuffer) 17091 glDeleteShader(s); 17092 } 17093 17094 /// Calls `glUseProgram(this.shaderProgram)` 17095 void use() { 17096 glUseProgram(this.shaderProgram); 17097 } 17098 17099 /// Deletes the program. 17100 void delete_() { 17101 glDeleteProgram(shaderProgram); 17102 shaderProgram = 0; 17103 } 17104 17105 /++ 17106 [OpenGlShader.uniforms].name gives you one of these. 17107 17108 You can get the id out of it or just assign 17109 +/ 17110 static struct Uniform { 17111 /// the id passed to glUniform* 17112 int id; 17113 17114 /// Assigns the 4 floats. You will probably have to call this via the .opAssign name 17115 void opAssign(float x, float y, float z, float w) { 17116 glUniform4f(id, x, y, z, w); 17117 } 17118 } 17119 17120 static struct UniformsHelper { 17121 OpenGlShader _shader; 17122 17123 @property Uniform opDispatch(string name)() { 17124 auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); 17125 if(i == -1) 17126 throw new Exception("Could not find uniform " ~ name); 17127 return Uniform(i); 17128 } 17129 } 17130 17131 /++ 17132 Gives access to the uniforms through dot access. 17133 `OpenGlShader.Uniform = shader.uniforms.foo; // calls glGetUniformLocation(this, "foo"); 17134 +/ 17135 @property UniformsHelper uniforms() { return UniformsHelper(this); } 17136 } 17137 17138 version(linux) { 17139 version(with_eventloop) {} else { 17140 private int epollFd = -1; 17141 void prepareEventLoop() { 17142 if(epollFd != -1) 17143 return; // already initialized, no need to do it again 17144 import ep = core.sys.linux.epoll; 17145 17146 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 17147 if(epollFd == -1) 17148 throw new Exception("epoll create failure"); 17149 } 17150 } 17151 } else version(Posix) { 17152 void prepareEventLoop() {} 17153 } 17154 17155 version(X11) { 17156 import core.stdc.locale : LC_ALL; // rdmd fix 17157 __gshared bool sdx_isUTF8Locale; 17158 17159 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 17160 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 17161 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 17162 // anal magic is here. I (Ketmar) hope you like it. 17163 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 17164 // always return correct unicode symbols. The detection is here 'cause user can change locale 17165 // later. 17166 17167 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 17168 shared static this () { 17169 if(!librariesSuccessfullyLoaded) 17170 return; 17171 17172 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 17173 17174 // this doesn't hurt; it may add some locking, but the speed is still 17175 // allows doing 60 FPS videogames; also, ignore the result, as most 17176 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 17177 // never seen this failing). 17178 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 17179 17180 setlocale(LC_ALL, ""); 17181 // check if out locale is UTF-8 17182 auto lct = setlocale(LC_CTYPE, null); 17183 if (lct is null) { 17184 sdx_isUTF8Locale = false; 17185 } else { 17186 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 17187 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 17188 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 17189 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 17190 { 17191 sdx_isUTF8Locale = true; 17192 break; 17193 } 17194 } 17195 } 17196 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 17197 } 17198 } 17199 17200 class ExperimentalTextComponent2 { 17201 /+ 17202 Stage 1: get it working monospace 17203 Stage 2: use proportional font 17204 Stage 3: allow changes in inline style 17205 Stage 4: allow new fonts and sizes in the middle 17206 Stage 5: optimize gap buffer 17207 Stage 6: optimize layout 17208 Stage 7: word wrap 17209 Stage 8: justification 17210 Stage 9: editing, selection, etc. 17211 17212 Operations: 17213 insert text 17214 overstrike text 17215 select 17216 cut 17217 modify 17218 +/ 17219 17220 /++ 17221 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. 17222 +/ 17223 this(SimpleWindow window) { 17224 this.window = window; 17225 } 17226 17227 private SimpleWindow window; 17228 17229 17230 /++ 17231 When you render a [ComponentInFlow], it returns an arbitrary number of these interfaces 17232 representing the internal parts. The first pass is focused on the x parameter, then the 17233 renderer is responsible for going back to the parts in the current line and calling 17234 adjustDownForAscent to change the y params. 17235 +/ 17236 static interface ComponentRenderHelper { 17237 17238 /+ 17239 When you do an edit, possibly stuff on the same line previously need to move (to adjust 17240 the baseline), stuff subsequent needs to move (adjust x) and possibly stuff below needs 17241 to move (adjust y to make room for new line) until you get back to the same position, 17242 then you can stop - if one thing is unchanged, nothing after it is changed too. 17243 17244 Word wrap might change this as if can rewrap tons of stuff, but the same idea applies, 17245 once you reach something that is unchanged, you can stop. 17246 +/ 17247 17248 void adjustDownForAscent(int amount); // at the end of the line it needs to do these 17249 17250 int ascent() const; 17251 int descent() const; 17252 17253 int advance() const; 17254 17255 bool endsWithExplititLineBreak() const; 17256 } 17257 17258 static interface RenderResult { 17259 /++ 17260 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. 17261 +/ 17262 void popFront(); 17263 @property bool empty() const; 17264 @property ComponentRenderHelper front() const; 17265 17266 void repositionForNextLine(Point baseline, int availableWidth); 17267 } 17268 17269 static interface ComponentInFlow { 17270 void draw(ScreenPainter painter); 17271 //RenderResult render(Point baseline, int availableWidth); // FIXME: it needs to be able to say "my cache is good, nothing different" 17272 17273 bool startsWithExplicitLineBreak() const; 17274 } 17275 17276 static class TextFlowComponent : ComponentInFlow { 17277 bool startsWithExplicitLineBreak() const { return false; } // FIXME: if it is block this can return true 17278 17279 Color foreground; 17280 Color background; 17281 17282 OperatingSystemFont font; // should NEVER be null 17283 17284 ubyte attributes; // underline, strike through, display on new block 17285 17286 version(Windows) 17287 const(wchar)[] content; 17288 else 17289 const(char)[] content; // this should NEVER have a newline, except at the end 17290 17291 RenderedComponent[] rendered; // entirely controlled by [rerender] 17292 17293 // could prolly put some spacing around it too like margin / padding 17294 17295 this(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) 17296 in { assert(font !is null); 17297 assert(!font.isNull); } 17298 do 17299 { 17300 this.foreground = f; 17301 this.background = b; 17302 this.font = font; 17303 17304 this.attributes = attr; 17305 version(Windows) { 17306 auto conversionFlags = 0;//WindowsStringConversionFlags.convertNewLines; 17307 auto sz = sizeOfConvertedWstring(c, conversionFlags); 17308 auto buffer = new wchar[](sz); 17309 this.content = makeWindowsString(c, buffer, conversionFlags); 17310 } else { 17311 this.content = c.dup; 17312 } 17313 } 17314 17315 void draw(ScreenPainter painter) { 17316 painter.setFont(this.font); 17317 painter.outlineColor = this.foreground; 17318 painter.fillColor = Color.transparent; 17319 foreach(rendered; this.rendered) { 17320 // the component works in term of baseline, 17321 // but the painter works in term of upper left bounding box 17322 // so need to translate that 17323 17324 if(this.background.a) { 17325 painter.fillColor = this.background; 17326 painter.outlineColor = this.background; 17327 17328 painter.drawRectangle(Point(rendered.startX, rendered.startY - this.font.ascent), Size(rendered.width, this.font.height)); 17329 17330 painter.outlineColor = this.foreground; 17331 painter.fillColor = Color.transparent; 17332 } 17333 17334 painter.drawText(Point(rendered.startX, rendered.startY - this.font.ascent), rendered.slice); 17335 17336 // FIXME: strike through, underline, highlight selection, etc. 17337 } 17338 } 17339 } 17340 17341 // I could split the parts into words on render 17342 // for easier word-wrap, each one being an unbreakable "inline-block" 17343 private TextFlowComponent[] parts; 17344 private int needsRerenderFrom; 17345 17346 void addPart(Color f, Color b, OperatingSystemFont font, ubyte attr, const(char)[] c) { 17347 // FIXME: needsRerenderFrom. Basically if the bounding box and baseline is the same as the previous thing, it can prolly just stop. 17348 parts ~= new TextFlowComponent(f, b, font, attr, c); 17349 } 17350 17351 static struct RenderedComponent { 17352 int startX; 17353 int startY; 17354 short width; 17355 // 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! 17356 // for individual chars in here you've gotta process on demand 17357 version(Windows) 17358 const(wchar)[] slice; 17359 else 17360 const(char)[] slice; 17361 } 17362 17363 17364 void rerender(Rectangle boundingBox) { 17365 Point baseline = boundingBox.upperLeft; 17366 17367 this.boundingBox.left = boundingBox.left; 17368 this.boundingBox.top = boundingBox.top; 17369 17370 auto remainingParts = parts; 17371 17372 int largestX; 17373 17374 17375 foreach(part; parts) 17376 part.font.prepareContext(window); 17377 scope(exit) 17378 foreach(part; parts) 17379 part.font.releaseContext(); 17380 17381 calculateNextLine: 17382 17383 int nextLineHeight = 0; 17384 int nextBiggestDescent = 0; 17385 17386 foreach(part; remainingParts) { 17387 auto height = part.font.ascent; 17388 if(height > nextLineHeight) 17389 nextLineHeight = height; 17390 if(part.font.descent > nextBiggestDescent) 17391 nextBiggestDescent = part.font.descent; 17392 if(part.content.length && part.content[$-1] == '\n') 17393 break; 17394 } 17395 17396 baseline.y += nextLineHeight; 17397 auto lineStart = baseline; 17398 17399 while(remainingParts.length) { 17400 remainingParts[0].rendered = null; 17401 17402 bool eol; 17403 if(remainingParts[0].content.length && remainingParts[0].content[$-1] == '\n') 17404 eol = true; 17405 17406 // FIXME: word wrap 17407 auto font = remainingParts[0].font; 17408 auto slice = remainingParts[0].content[0 .. $ - (eol ? 1 : 0)]; 17409 auto width = font.stringWidth(slice, window); 17410 remainingParts[0].rendered ~= RenderedComponent(baseline.x, baseline.y, cast(short) width, slice); 17411 17412 remainingParts = remainingParts[1 .. $]; 17413 baseline.x += width; 17414 17415 if(eol) { 17416 baseline.y += nextBiggestDescent; 17417 if(baseline.x > largestX) 17418 largestX = baseline.x; 17419 baseline.x = lineStart.x; 17420 goto calculateNextLine; 17421 } 17422 } 17423 17424 if(baseline.x > largestX) 17425 largestX = baseline.x; 17426 17427 this.boundingBox.right = largestX; 17428 this.boundingBox.bottom = baseline.y; 17429 } 17430 17431 // you must call rerender first! 17432 void draw(ScreenPainter painter) { 17433 foreach(part; parts) { 17434 part.draw(painter); 17435 } 17436 } 17437 17438 struct IdentifyResult { 17439 TextFlowComponent part; 17440 int charIndexInPart; 17441 int totalCharIndex = -1; // if this is -1, it just means the end 17442 17443 Rectangle boundingBox; 17444 } 17445 17446 IdentifyResult identify(Point pt, bool exact = false) { 17447 if(parts.length == 0) 17448 return IdentifyResult(null, 0); 17449 17450 if(pt.y < boundingBox.top) { 17451 if(exact) 17452 return IdentifyResult(null, 1); 17453 return IdentifyResult(parts[0], 0); 17454 } 17455 if(pt.y > boundingBox.bottom) { 17456 if(exact) 17457 return IdentifyResult(null, 2); 17458 return IdentifyResult(parts[$-1], cast(int) parts[$-1].content.length); 17459 } 17460 17461 int tci = 0; 17462 17463 // I should probably like binary search this or something... 17464 foreach(ref part; parts) { 17465 foreach(rendered; part.rendered) { 17466 auto rect = Rectangle(rendered.startX, rendered.startY - part.font.ascent, rendered.startX + rendered.width, rendered.startY + part.font.descent); 17467 if(rect.contains(pt)) { 17468 auto x = pt.x - rendered.startX; 17469 auto estimatedIdx = x / part.font.averageWidth; 17470 17471 if(estimatedIdx < 0) 17472 estimatedIdx = 0; 17473 17474 if(estimatedIdx > rendered.slice.length) 17475 estimatedIdx = cast(int) rendered.slice.length; 17476 17477 int idx; 17478 int x1, x2; 17479 if(part.font.isMonospace) { 17480 auto w = part.font.averageWidth; 17481 if(!exact && x > (estimatedIdx + 1) * w) 17482 return IdentifyResult(null, 4); 17483 idx = estimatedIdx; 17484 x1 = idx * w; 17485 x2 = (idx + 1) * w; 17486 } else { 17487 idx = estimatedIdx; 17488 17489 part.font.prepareContext(window); 17490 scope(exit) part.font.releaseContext(); 17491 17492 // int iterations; 17493 17494 while(true) { 17495 // iterations++; 17496 x1 = idx ? part.font.stringWidth(rendered.slice[0 .. idx - 1]) : 0; 17497 x2 = part.font.stringWidth(rendered.slice[0 .. idx]); // should be the maximum since `averageWidth` kinda lies. 17498 17499 x1 += rendered.startX; 17500 x2 += rendered.startX; 17501 17502 if(pt.x < x1) { 17503 if(idx == 0) { 17504 if(exact) 17505 return IdentifyResult(null, 6); 17506 else 17507 break; 17508 } 17509 idx--; 17510 } else if(pt.x > x2) { 17511 idx++; 17512 if(idx > rendered.slice.length) { 17513 if(exact) 17514 return IdentifyResult(null, 5); 17515 else 17516 break; 17517 } 17518 } else if(pt.x >= x1 && pt.x <= x2) { 17519 if(idx) 17520 idx--; // point it at the original index 17521 break; // we fit 17522 } 17523 } 17524 17525 // import std.stdio; writeln(iterations) 17526 } 17527 17528 17529 return IdentifyResult(part, idx, tci + idx, Rectangle(x1, rect.top, x2, rect.bottom)); // FIXME: utf-8? 17530 } 17531 } 17532 tci += cast(int) part.content.length; // FIXME: utf-8? 17533 } 17534 return IdentifyResult(null, 3); 17535 } 17536 17537 Rectangle boundingBox; // only set after [rerender] 17538 17539 // text will be positioned around the exclusion zone 17540 static struct ExclusionZone { 17541 17542 } 17543 17544 ExclusionZone[] exclusionZones; 17545 } 17546 17547 17548 // Don't use this yet. When I'm happy with it, I will move it to the 17549 // regular module namespace. 17550 mixin template ExperimentalTextComponent() { 17551 17552 static: 17553 17554 alias Rectangle = arsd.color.Rectangle; 17555 17556 struct ForegroundColor { 17557 Color color; 17558 alias color this; 17559 17560 this(Color c) { 17561 color = c; 17562 } 17563 17564 this(int r, int g, int b, int a = 255) { 17565 color = Color(r, g, b, a); 17566 } 17567 17568 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 17569 return ForegroundColor(mixin("Color." ~ s)); 17570 } 17571 } 17572 17573 struct BackgroundColor { 17574 Color color; 17575 alias color this; 17576 17577 this(Color c) { 17578 color = c; 17579 } 17580 17581 this(int r, int g, int b, int a = 255) { 17582 color = Color(r, g, b, a); 17583 } 17584 17585 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 17586 return BackgroundColor(mixin("Color." ~ s)); 17587 } 17588 } 17589 17590 static class InlineElement { 17591 string text; 17592 17593 BlockElement containingBlock; 17594 17595 Color color = Color.black; 17596 Color backgroundColor = Color.transparent; 17597 ushort styles; 17598 17599 string font; 17600 int fontSize; 17601 17602 int lineHeight; 17603 17604 void* identifier; 17605 17606 Rectangle boundingBox; 17607 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 17608 17609 bool isMergeCompatible(InlineElement other) { 17610 return 17611 containingBlock is other.containingBlock && 17612 color == other.color && 17613 backgroundColor == other.backgroundColor && 17614 styles == other.styles && 17615 font == other.font && 17616 fontSize == other.fontSize && 17617 lineHeight == other.lineHeight && 17618 true; 17619 } 17620 17621 int xOfIndex(size_t index) { 17622 if(index < letterXs.length) 17623 return letterXs[index]; 17624 else 17625 return boundingBox.right; 17626 } 17627 17628 InlineElement clone() { 17629 auto ie = new InlineElement(); 17630 ie.tupleof = this.tupleof; 17631 return ie; 17632 } 17633 17634 InlineElement getPreviousInlineElement() { 17635 InlineElement prev = null; 17636 foreach(ie; this.containingBlock.parts) { 17637 if(ie is this) 17638 break; 17639 prev = ie; 17640 } 17641 if(prev is null) { 17642 BlockElement pb; 17643 BlockElement cb = this.containingBlock; 17644 moar: 17645 foreach(ie; this.containingBlock.containingLayout.blocks) { 17646 if(ie is cb) 17647 break; 17648 pb = ie; 17649 } 17650 if(pb is null) 17651 return null; 17652 if(pb.parts.length == 0) { 17653 cb = pb; 17654 goto moar; 17655 } 17656 17657 prev = pb.parts[$-1]; 17658 17659 } 17660 return prev; 17661 } 17662 17663 InlineElement getNextInlineElement() { 17664 InlineElement next = null; 17665 foreach(idx, ie; this.containingBlock.parts) { 17666 if(ie is this) { 17667 if(idx + 1 < this.containingBlock.parts.length) 17668 next = this.containingBlock.parts[idx + 1]; 17669 break; 17670 } 17671 } 17672 if(next is null) { 17673 BlockElement n; 17674 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 17675 if(ie is this.containingBlock) { 17676 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 17677 n = this.containingBlock.containingLayout.blocks[idx + 1]; 17678 break; 17679 } 17680 } 17681 if(n is null) 17682 return null; 17683 17684 if(n.parts.length) 17685 next = n.parts[0]; 17686 else {} // FIXME 17687 17688 } 17689 return next; 17690 } 17691 17692 } 17693 17694 // Block elements are used entirely for positioning inline elements, 17695 // which are the things that are actually drawn. 17696 class BlockElement { 17697 InlineElement[] parts; 17698 uint alignment; 17699 17700 int whiteSpace; // pre, pre-wrap, wrap 17701 17702 TextLayout containingLayout; 17703 17704 // inputs 17705 Point where; 17706 Size minimumSize; 17707 Size maximumSize; 17708 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 17709 void* identifier; 17710 17711 Rectangle margin; 17712 Rectangle padding; 17713 17714 // outputs 17715 Rectangle[] boundingBoxes; 17716 } 17717 17718 struct TextIdentifyResult { 17719 InlineElement element; 17720 int offset; 17721 17722 private TextIdentifyResult fixupNewline() { 17723 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 17724 offset--; 17725 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 17726 offset--; 17727 } 17728 return this; 17729 } 17730 } 17731 17732 class TextLayout { 17733 BlockElement[] blocks; 17734 Rectangle boundingBox_; 17735 Rectangle boundingBox() { return boundingBox_; } 17736 void boundingBox(Rectangle r) { 17737 if(r != boundingBox_) { 17738 boundingBox_ = r; 17739 layoutInvalidated = true; 17740 } 17741 } 17742 17743 Rectangle contentBoundingBox() { 17744 Rectangle r; 17745 foreach(block; blocks) 17746 foreach(ie; block.parts) { 17747 if(ie.boundingBox.right > r.right) 17748 r.right = ie.boundingBox.right; 17749 if(ie.boundingBox.bottom > r.bottom) 17750 r.bottom = ie.boundingBox.bottom; 17751 } 17752 return r; 17753 } 17754 17755 BlockElement[] getBlocks() { 17756 return blocks; 17757 } 17758 17759 InlineElement[] getTexts() { 17760 InlineElement[] elements; 17761 foreach(block; blocks) 17762 elements ~= block.parts; 17763 return elements; 17764 } 17765 17766 string getPlainText() { 17767 string text; 17768 foreach(block; blocks) 17769 foreach(part; block.parts) 17770 text ~= part.text; 17771 return text; 17772 } 17773 17774 string getHtml() { 17775 return null; // FIXME 17776 } 17777 17778 this(Rectangle boundingBox) { 17779 this.boundingBox = boundingBox; 17780 } 17781 17782 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 17783 auto be = new BlockElement(); 17784 be.containingLayout = this; 17785 if(after is null) 17786 blocks ~= be; 17787 else { 17788 foreach(idx, b; blocks) { 17789 if(b is after.containingBlock) { 17790 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 17791 break; 17792 } 17793 } 17794 } 17795 return be; 17796 } 17797 17798 void clear() { 17799 blocks = null; 17800 selectionStart = selectionEnd = caret = Caret.init; 17801 } 17802 17803 void addText(Args...)(Args args) { 17804 if(blocks.length == 0) 17805 addBlock(); 17806 17807 InlineElement ie = new InlineElement(); 17808 foreach(idx, arg; args) { 17809 static if(is(typeof(arg) == ForegroundColor)) 17810 ie.color = arg; 17811 else static if(is(typeof(arg) == TextFormat)) { 17812 if(arg & 0x8000) // ~TextFormat.something turns it off 17813 ie.styles &= arg; 17814 else 17815 ie.styles |= arg; 17816 } else static if(is(typeof(arg) == string)) { 17817 static if(idx == 0 && args.length > 1) 17818 static assert(0, "Put styles before the string."); 17819 size_t lastLineIndex; 17820 foreach(cidx, char a; arg) { 17821 if(a == '\n') { 17822 ie.text = arg[lastLineIndex .. cidx + 1]; 17823 lastLineIndex = cidx + 1; 17824 ie.containingBlock = blocks[$-1]; 17825 blocks[$-1].parts ~= ie.clone; 17826 ie.text = null; 17827 } else { 17828 17829 } 17830 } 17831 17832 ie.text = arg[lastLineIndex .. $]; 17833 ie.containingBlock = blocks[$-1]; 17834 blocks[$-1].parts ~= ie.clone; 17835 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 17836 } 17837 } 17838 17839 invalidateLayout(); 17840 } 17841 17842 void tryMerge(InlineElement into, InlineElement what) { 17843 if(!into.isMergeCompatible(what)) { 17844 return; // cannot merge, different configs 17845 } 17846 17847 // cool, can merge, bring text together... 17848 into.text ~= what.text; 17849 17850 // and remove what 17851 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 17852 if(what.containingBlock.parts[a] is what) { 17853 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 17854 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 17855 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 17856 17857 } 17858 } 17859 17860 // FIXME: ensure no other carets have a reference to it 17861 } 17862 17863 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 17864 TextIdentifyResult identify(int x, int y, bool exact = false) { 17865 TextIdentifyResult inexactMatch; 17866 foreach(block; blocks) { 17867 foreach(part; block.parts) { 17868 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 17869 17870 // FIXME binary search 17871 int tidx; 17872 int lastX; 17873 foreach_reverse(idxo, lx; part.letterXs) { 17874 int idx = cast(int) idxo; 17875 if(lx <= x) { 17876 if(lastX && lastX - x < x - lx) 17877 tidx = idx + 1; 17878 else 17879 tidx = idx; 17880 break; 17881 } 17882 lastX = lx; 17883 } 17884 17885 return TextIdentifyResult(part, tidx).fixupNewline; 17886 } else if(!exact) { 17887 // we're not in the box, but are we on the same line? 17888 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 17889 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 17890 } 17891 } 17892 } 17893 17894 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 17895 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 17896 17897 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 17898 } 17899 17900 void moveCaretToPixelCoordinates(int x, int y) { 17901 auto result = identify(x, y); 17902 caret.inlineElement = result.element; 17903 caret.offset = result.offset; 17904 } 17905 17906 void selectToPixelCoordinates(int x, int y) { 17907 auto result = identify(x, y); 17908 17909 if(y < caretLastDrawnY1) { 17910 // on a previous line, carat is selectionEnd 17911 selectionEnd = caret; 17912 17913 selectionStart = Caret(this, result.element, result.offset); 17914 } else if(y > caretLastDrawnY2) { 17915 // on a later line 17916 selectionStart = caret; 17917 17918 selectionEnd = Caret(this, result.element, result.offset); 17919 } else { 17920 // on the same line... 17921 if(x <= caretLastDrawnX) { 17922 selectionEnd = caret; 17923 selectionStart = Caret(this, result.element, result.offset); 17924 } else { 17925 selectionStart = caret; 17926 selectionEnd = Caret(this, result.element, result.offset); 17927 } 17928 17929 } 17930 } 17931 17932 17933 /// Call this if the inputs change. It will reflow everything 17934 void redoLayout(ScreenPainter painter) { 17935 //painter.setClipRectangle(boundingBox); 17936 auto pos = Point(boundingBox.left, boundingBox.top); 17937 17938 int lastHeight; 17939 void nl() { 17940 pos.x = boundingBox.left; 17941 pos.y += lastHeight; 17942 } 17943 foreach(block; blocks) { 17944 nl(); 17945 foreach(part; block.parts) { 17946 part.letterXs = null; 17947 17948 auto size = painter.textSize(part.text); 17949 version(Windows) 17950 if(part.text.length && part.text[$-1] == '\n') 17951 size.height /= 2; // windows counts the new line at the end, but we don't want that 17952 17953 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 17954 17955 foreach(idx, char c; part.text) { 17956 // FIXME: unicode 17957 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 17958 } 17959 17960 pos.x += size.width; 17961 if(pos.x >= boundingBox.right) { 17962 pos.y += size.height; 17963 pos.x = boundingBox.left; 17964 lastHeight = 0; 17965 } else { 17966 lastHeight = size.height; 17967 } 17968 17969 if(part.text.length && part.text[$-1] == '\n') 17970 nl(); 17971 } 17972 } 17973 17974 layoutInvalidated = false; 17975 } 17976 17977 bool layoutInvalidated = true; 17978 void invalidateLayout() { 17979 layoutInvalidated = true; 17980 } 17981 17982 // FIXME: caret can remain sometimes when inserting 17983 // FIXME: inserting at the beginning once you already have something can eff it up. 17984 void drawInto(ScreenPainter painter, bool focused = false) { 17985 if(layoutInvalidated) 17986 redoLayout(painter); 17987 foreach(block; blocks) { 17988 foreach(part; block.parts) { 17989 painter.outlineColor = part.color; 17990 painter.fillColor = part.backgroundColor; 17991 17992 auto pos = part.boundingBox.upperLeft; 17993 auto size = part.boundingBox.size; 17994 17995 painter.drawText(pos, part.text); 17996 if(part.styles & TextFormat.underline) 17997 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 17998 if(part.styles & TextFormat.strikethrough) 17999 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 18000 } 18001 } 18002 18003 // on every redraw, I will force the caret to be 18004 // redrawn too, in order to eliminate perceived lag 18005 // when moving around with the mouse. 18006 eraseCaret(painter); 18007 18008 if(focused) { 18009 highlightSelection(painter); 18010 drawCaret(painter); 18011 } 18012 } 18013 18014 Color selectionXorColor = Color(255, 255, 127); 18015 18016 void highlightSelection(ScreenPainter painter) { 18017 if(selectionStart is selectionEnd) 18018 return; // no selection 18019 18020 if(selectionStart.inlineElement is null) return; 18021 if(selectionEnd.inlineElement is null) return; 18022 18023 assert(selectionStart.inlineElement !is null); 18024 assert(selectionEnd.inlineElement !is null); 18025 18026 painter.rasterOp = RasterOp.xor; 18027 painter.outlineColor = Color.transparent; 18028 painter.fillColor = selectionXorColor; 18029 18030 auto at = selectionStart.inlineElement; 18031 auto atOffset = selectionStart.offset; 18032 bool done; 18033 while(at) { 18034 auto box = at.boundingBox; 18035 if(atOffset < at.letterXs.length) 18036 box.left = at.letterXs[atOffset]; 18037 18038 if(at is selectionEnd.inlineElement) { 18039 if(selectionEnd.offset < at.letterXs.length) 18040 box.right = at.letterXs[selectionEnd.offset]; 18041 done = true; 18042 } 18043 18044 painter.drawRectangle(box.upperLeft, box.width, box.height); 18045 18046 if(done) 18047 break; 18048 18049 at = at.getNextInlineElement(); 18050 atOffset = 0; 18051 } 18052 } 18053 18054 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 18055 bool caretShowingOnScreen = false; 18056 void drawCaret(ScreenPainter painter) { 18057 //painter.setClipRectangle(boundingBox); 18058 int x, y1, y2; 18059 if(caret.inlineElement is null) { 18060 x = boundingBox.left; 18061 y1 = boundingBox.top + 2; 18062 y2 = boundingBox.top + painter.fontHeight; 18063 } else { 18064 x = caret.inlineElement.xOfIndex(caret.offset); 18065 y1 = caret.inlineElement.boundingBox.top + 2; 18066 y2 = caret.inlineElement.boundingBox.bottom - 2; 18067 } 18068 18069 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 18070 eraseCaret(painter); 18071 18072 painter.pen = Pen(Color.white, 1); 18073 painter.rasterOp = RasterOp.xor; 18074 painter.drawLine( 18075 Point(x, y1), 18076 Point(x, y2) 18077 ); 18078 painter.rasterOp = RasterOp.normal; 18079 caretShowingOnScreen = !caretShowingOnScreen; 18080 18081 if(caretShowingOnScreen) { 18082 caretLastDrawnX = x; 18083 caretLastDrawnY1 = y1; 18084 caretLastDrawnY2 = y2; 18085 } 18086 } 18087 18088 Rectangle caretBoundingBox() { 18089 int x, y1, y2; 18090 if(caret.inlineElement is null) { 18091 x = boundingBox.left; 18092 y1 = boundingBox.top + 2; 18093 y2 = boundingBox.top + 16; 18094 } else { 18095 x = caret.inlineElement.xOfIndex(caret.offset); 18096 y1 = caret.inlineElement.boundingBox.top + 2; 18097 y2 = caret.inlineElement.boundingBox.bottom - 2; 18098 } 18099 18100 return Rectangle(x, y1, x + 1, y2); 18101 } 18102 18103 void eraseCaret(ScreenPainter painter) { 18104 //painter.setClipRectangle(boundingBox); 18105 if(!caretShowingOnScreen) return; 18106 painter.pen = Pen(Color.white, 1); 18107 painter.rasterOp = RasterOp.xor; 18108 painter.drawLine( 18109 Point(caretLastDrawnX, caretLastDrawnY1), 18110 Point(caretLastDrawnX, caretLastDrawnY2) 18111 ); 18112 18113 caretShowingOnScreen = false; 18114 painter.rasterOp = RasterOp.normal; 18115 } 18116 18117 /// Caret movement api 18118 /// These should give the user a logical result based on what they see on screen... 18119 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 18120 void moveUp() { 18121 if(caret.inlineElement is null) return; 18122 auto x = caret.inlineElement.xOfIndex(caret.offset); 18123 auto y = caret.inlineElement.boundingBox.top + 2; 18124 18125 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 18126 if(y < 0) 18127 return; 18128 18129 auto i = identify(x, y); 18130 18131 if(i.element) { 18132 caret.inlineElement = i.element; 18133 caret.offset = i.offset; 18134 } 18135 } 18136 void moveDown() { 18137 if(caret.inlineElement is null) return; 18138 auto x = caret.inlineElement.xOfIndex(caret.offset); 18139 auto y = caret.inlineElement.boundingBox.bottom - 2; 18140 18141 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 18142 18143 auto i = identify(x, y); 18144 if(i.element) { 18145 caret.inlineElement = i.element; 18146 caret.offset = i.offset; 18147 } 18148 } 18149 void moveLeft() { 18150 if(caret.inlineElement is null) return; 18151 if(caret.offset) 18152 caret.offset--; 18153 else { 18154 auto p = caret.inlineElement.getPreviousInlineElement(); 18155 if(p) { 18156 caret.inlineElement = p; 18157 if(p.text.length && p.text[$-1] == '\n') 18158 caret.offset = cast(int) p.text.length - 1; 18159 else 18160 caret.offset = cast(int) p.text.length; 18161 } 18162 } 18163 } 18164 void moveRight() { 18165 if(caret.inlineElement is null) return; 18166 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 18167 caret.offset++; 18168 } else { 18169 auto p = caret.inlineElement.getNextInlineElement(); 18170 if(p) { 18171 caret.inlineElement = p; 18172 caret.offset = 0; 18173 } 18174 } 18175 } 18176 void moveHome() { 18177 if(caret.inlineElement is null) return; 18178 auto x = 0; 18179 auto y = caret.inlineElement.boundingBox.top + 2; 18180 18181 auto i = identify(x, y); 18182 18183 if(i.element) { 18184 caret.inlineElement = i.element; 18185 caret.offset = i.offset; 18186 } 18187 } 18188 void moveEnd() { 18189 if(caret.inlineElement is null) return; 18190 auto x = int.max; 18191 auto y = caret.inlineElement.boundingBox.top + 2; 18192 18193 auto i = identify(x, y); 18194 18195 if(i.element) { 18196 caret.inlineElement = i.element; 18197 caret.offset = i.offset; 18198 } 18199 18200 } 18201 void movePageUp(ref Caret caret) {} 18202 void movePageDown(ref Caret caret) {} 18203 18204 void moveDocumentStart(ref Caret caret) { 18205 if(blocks.length && blocks[0].parts.length) 18206 caret = Caret(this, blocks[0].parts[0], 0); 18207 else 18208 caret = Caret.init; 18209 } 18210 18211 void moveDocumentEnd(ref Caret caret) { 18212 if(blocks.length) { 18213 auto parts = blocks[$-1].parts; 18214 if(parts.length) { 18215 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 18216 } else { 18217 caret = Caret.init; 18218 } 18219 } else 18220 caret = Caret.init; 18221 } 18222 18223 void deleteSelection() { 18224 if(selectionStart is selectionEnd) 18225 return; 18226 18227 if(selectionStart.inlineElement is null) return; 18228 if(selectionEnd.inlineElement is null) return; 18229 18230 assert(selectionStart.inlineElement !is null); 18231 assert(selectionEnd.inlineElement !is null); 18232 18233 auto at = selectionStart.inlineElement; 18234 18235 if(selectionEnd.inlineElement is at) { 18236 // same element, need to chop out 18237 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 18238 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 18239 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 18240 } else { 18241 // different elements, we can do it with slicing 18242 at.text = at.text[0 .. selectionStart.offset]; 18243 if(selectionStart.offset < at.letterXs.length) 18244 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 18245 18246 at = at.getNextInlineElement(); 18247 18248 while(at) { 18249 if(at is selectionEnd.inlineElement) { 18250 at.text = at.text[selectionEnd.offset .. $]; 18251 if(selectionEnd.offset < at.letterXs.length) 18252 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 18253 selectionEnd.offset = 0; 18254 break; 18255 } else { 18256 auto cfd = at; 18257 cfd.text = null; // delete the whole thing 18258 18259 at = at.getNextInlineElement(); 18260 18261 if(cfd.text.length == 0) { 18262 // and remove cfd 18263 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 18264 if(cfd.containingBlock.parts[a] is cfd) { 18265 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 18266 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 18267 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 18268 18269 } 18270 } 18271 } 18272 } 18273 } 18274 } 18275 18276 caret = selectionEnd; 18277 selectNone(); 18278 18279 invalidateLayout(); 18280 18281 } 18282 18283 /// Plain text editing api. These work at the current caret inside the selected inline element. 18284 void insert(in char[] text) { 18285 foreach(dchar ch; text) 18286 insert(ch); 18287 } 18288 /// ditto 18289 void insert(dchar ch) { 18290 18291 bool selectionDeleted = false; 18292 if(selectionStart !is selectionEnd) { 18293 deleteSelection(); 18294 selectionDeleted = true; 18295 } 18296 18297 if(ch == 127) { 18298 delete_(); 18299 return; 18300 } 18301 if(ch == 8) { 18302 if(!selectionDeleted) 18303 backspace(); 18304 return; 18305 } 18306 18307 invalidateLayout(); 18308 18309 if(ch == 13) ch = 10; 18310 auto e = caret.inlineElement; 18311 if(e is null) { 18312 addText("" ~ cast(char) ch) ; // FIXME 18313 return; 18314 } 18315 18316 if(caret.offset == e.text.length) { 18317 e.text ~= cast(char) ch; // FIXME 18318 caret.offset++; 18319 if(ch == 10) { 18320 auto c = caret.inlineElement.clone; 18321 c.text = null; 18322 c.letterXs = null; 18323 insertPartAfter(c,e); 18324 caret = Caret(this, c, 0); 18325 } 18326 } else { 18327 // FIXME cast char sucks 18328 if(ch == 10) { 18329 auto c = caret.inlineElement.clone; 18330 c.text = e.text[caret.offset .. $]; 18331 if(caret.offset < c.letterXs.length) 18332 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 18333 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 18334 if(caret.offset <= e.letterXs.length) { 18335 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 18336 } 18337 insertPartAfter(c,e); 18338 caret = Caret(this, c, 0); 18339 } else { 18340 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 18341 caret.offset++; 18342 } 18343 } 18344 } 18345 18346 void insertPartAfter(InlineElement what, InlineElement where) { 18347 foreach(idx, p; where.containingBlock.parts) { 18348 if(p is where) { 18349 if(idx + 1 == where.containingBlock.parts.length) 18350 where.containingBlock.parts ~= what; 18351 else 18352 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 18353 return; 18354 } 18355 } 18356 } 18357 18358 void cleanupStructures() { 18359 for(size_t i = 0; i < blocks.length; i++) { 18360 auto block = blocks[i]; 18361 for(size_t a = 0; a < block.parts.length; a++) { 18362 auto part = block.parts[a]; 18363 if(part.text.length == 0) { 18364 for(size_t b = a; b < block.parts.length - 1; b++) 18365 block.parts[b] = block.parts[b+1]; 18366 block.parts = block.parts[0 .. $-1]; 18367 } 18368 } 18369 if(block.parts.length == 0) { 18370 for(size_t a = i; a < blocks.length - 1; a++) 18371 blocks[a] = blocks[a+1]; 18372 blocks = blocks[0 .. $-1]; 18373 } 18374 } 18375 } 18376 18377 void backspace() { 18378 try_again: 18379 auto e = caret.inlineElement; 18380 if(e is null) 18381 return; 18382 if(caret.offset == 0) { 18383 auto prev = e.getPreviousInlineElement(); 18384 if(prev is null) 18385 return; 18386 auto newOffset = cast(int) prev.text.length; 18387 tryMerge(prev, e); 18388 caret.inlineElement = prev; 18389 caret.offset = prev is null ? 0 : newOffset; 18390 18391 goto try_again; 18392 } else if(caret.offset == e.text.length) { 18393 e.text = e.text[0 .. $-1]; 18394 caret.offset--; 18395 } else { 18396 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 18397 caret.offset--; 18398 } 18399 //cleanupStructures(); 18400 18401 invalidateLayout(); 18402 } 18403 void delete_() { 18404 if(selectionStart !is selectionEnd) 18405 deleteSelection(); 18406 else { 18407 auto before = caret; 18408 moveRight(); 18409 if(caret != before) { 18410 backspace(); 18411 } 18412 } 18413 18414 invalidateLayout(); 18415 } 18416 void overstrike() {} 18417 18418 /// Selection API. See also: caret movement. 18419 void selectAll() { 18420 moveDocumentStart(selectionStart); 18421 moveDocumentEnd(selectionEnd); 18422 } 18423 bool selectNone() { 18424 if(selectionStart != selectionEnd) { 18425 selectionStart = selectionEnd = Caret.init; 18426 return true; 18427 } 18428 return false; 18429 } 18430 18431 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 18432 /// They will modify the current selection if there is one and will splice one in if needed. 18433 void changeAttributes() {} 18434 18435 18436 /// Text search api. They manipulate the selection and/or caret. 18437 void findText(string text) {} 18438 void findIndex(size_t textIndex) {} 18439 18440 // sample event handlers 18441 18442 void handleEvent(KeyEvent event) { 18443 //if(event.type == KeyEvent.Type.KeyPressed) { 18444 18445 //} 18446 } 18447 18448 void handleEvent(dchar ch) { 18449 18450 } 18451 18452 void handleEvent(MouseEvent event) { 18453 18454 } 18455 18456 bool contentEditable; // can it be edited? 18457 bool contentCaretable; // is there a caret/cursor that moves around in there? 18458 bool contentSelectable; // selectable? 18459 18460 Caret caret; 18461 Caret selectionStart; 18462 Caret selectionEnd; 18463 18464 bool insertMode; 18465 } 18466 18467 struct Caret { 18468 TextLayout layout; 18469 InlineElement inlineElement; 18470 int offset; 18471 } 18472 18473 enum TextFormat : ushort { 18474 // decorations 18475 underline = 1, 18476 strikethrough = 2, 18477 18478 // font selectors 18479 18480 bold = 0x4000 | 1, // weight 700 18481 light = 0x4000 | 2, // weight 300 18482 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 18483 // bold | light is really invalid but should give weight 500 18484 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 18485 18486 italic = 0x4000 | 8, 18487 smallcaps = 0x4000 | 16, 18488 } 18489 18490 void* findFont(string family, int weight, TextFormat formats) { 18491 return null; 18492 } 18493 18494 } 18495 18496 /++ 18497 $(PITFALL this is not yet stable and may break in future versions without notice.) 18498 18499 History: 18500 Added February 19, 2021 18501 +/ 18502 /// Group: drag_and_drop 18503 interface DropHandler { 18504 /++ 18505 Called when the drag enters the handler's area. 18506 +/ 18507 DragAndDropAction dragEnter(DropPackage*); 18508 /++ 18509 Called when the drag leaves the handler's area or is 18510 cancelled. You should free your resources when this is called. 18511 +/ 18512 void dragLeave(); 18513 /++ 18514 Called continually as the drag moves over the handler's area. 18515 18516 Returns: feedback to the dragger 18517 +/ 18518 DropParameters dragOver(Point pt); 18519 /++ 18520 The user dropped the data and you should process it now. You can 18521 access the data through the given [DropPackage]. 18522 +/ 18523 void drop(scope DropPackage*); 18524 /++ 18525 Called when the drop is complete. You should free whatever temporary 18526 resources you were using. It is often reasonable to simply forward 18527 this call to [dragLeave]. 18528 +/ 18529 void finish(); 18530 18531 /++ 18532 Parameters returned by [DropHandler.drop]. 18533 +/ 18534 static struct DropParameters { 18535 /++ 18536 Acceptable action over this area. 18537 +/ 18538 DragAndDropAction action; 18539 /++ 18540 Rectangle, in client coordinates, where the dragger can expect the same result during this drag session and thus need not ask again. 18541 18542 If you leave this as Rectangle.init, the dragger will continue to ask and this can waste resources. 18543 +/ 18544 Rectangle consistentWithin; 18545 } 18546 } 18547 18548 /++ 18549 History: 18550 Added February 19, 2021 18551 +/ 18552 /// Group: drag_and_drop 18553 enum DragAndDropAction { 18554 none = 0, 18555 copy, 18556 move, 18557 link, 18558 ask, 18559 custom 18560 } 18561 18562 /++ 18563 An opaque structure representing dropped data. It contains 18564 private, platform-specific data that your `drop` function 18565 should simply forward. 18566 18567 $(PITFALL this is not yet stable and may break in future versions without notice.) 18568 18569 History: 18570 Added February 19, 2021 18571 +/ 18572 /// Group: drag_and_drop 18573 struct DropPackage { 18574 /++ 18575 Lists the available formats as magic numbers. You should compare these 18576 against looked-up formats (see [DraggableData.getFormatId]) you know you support and can 18577 understand the passed data. 18578 +/ 18579 DraggableData.FormatId[] availableFormats() { 18580 version(X11) { 18581 return xFormats; 18582 } else version(Windows) { 18583 if(pDataObj is null) 18584 return null; 18585 18586 typeof(return) ret; 18587 18588 IEnumFORMATETC ef; 18589 if(pDataObj.EnumFormatEtc(DATADIR.DATADIR_GET, &ef) == S_OK) { 18590 FORMATETC fmt; 18591 ULONG fetched; 18592 while(ef.Next(1, &fmt, &fetched) == S_OK) { 18593 if(fetched == 0) 18594 break; 18595 18596 if(fmt.lindex != -1) 18597 continue; 18598 if(fmt.dwAspect != DVASPECT.DVASPECT_CONTENT) 18599 continue; 18600 if(!(fmt.tymed & TYMED.TYMED_HGLOBAL)) 18601 continue; 18602 18603 ret ~= fmt.cfFormat; 18604 } 18605 } 18606 18607 return ret; 18608 } 18609 } 18610 18611 /++ 18612 Gets data from the drop and optionally accepts it. 18613 18614 Returns: 18615 void because the data is fed asynchronously through the `dg` parameter. 18616 18617 Params: 18618 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. 18619 18620 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. 18621 18622 Calling `getData` again after accepting a drop is not permitted. 18623 18624 format = the format you want, from [availableFormats]. Use [DraggableData.getFormatId] to convert from a MIME string or well-known standard format. 18625 18626 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. 18627 18628 Throws: 18629 if `format` was not compatible with the [availableFormats] or if the drop has already been accepted. 18630 18631 History: 18632 Included in first release of [DropPackage]. 18633 +/ 18634 void getData(DragAndDropAction acceptedAction, DraggableData.FormatId format, void delegate(scope ubyte[] data) dg) { 18635 version(X11) { 18636 18637 auto display = XDisplayConnection.get(); 18638 auto selectionAtom = GetAtom!"XdndSelection"(display); 18639 auto best = format; 18640 18641 static class X11GetSelectionHandler_Drop : X11GetSelectionHandler { 18642 18643 XDisplay* display; 18644 Atom selectionAtom; 18645 DraggableData.FormatId best; 18646 DraggableData.FormatId format; 18647 void delegate(scope ubyte[] data) dg; 18648 DragAndDropAction acceptedAction; 18649 Window sourceWindow; 18650 SimpleWindow win; 18651 this(XDisplay* display, SimpleWindow win, Window sourceWindow, DraggableData.FormatId format, Atom selectionAtom, DraggableData.FormatId best, void delegate(scope ubyte[] data) dg, DragAndDropAction acceptedAction) { 18652 this.display = display; 18653 this.win = win; 18654 this.sourceWindow = sourceWindow; 18655 this.format = format; 18656 this.selectionAtom = selectionAtom; 18657 this.best = best; 18658 this.dg = dg; 18659 this.acceptedAction = acceptedAction; 18660 } 18661 18662 18663 mixin X11GetSelectionHandler_Basics; 18664 18665 void handleData(Atom target, in ubyte[] data) { 18666 //if(target == GetAtom!"UTF8_STRING"(XDisplayConnection.get) || target == XA_STRING || target == GetAtom!"text/plain"(XDisplayConnection.get)) 18667 18668 dg(cast(ubyte[]) data); 18669 18670 if(acceptedAction != DragAndDropAction.none) { 18671 auto display = XDisplayConnection.get; 18672 18673 XClientMessageEvent xclient; 18674 18675 xclient.type = EventType.ClientMessage; 18676 xclient.window = sourceWindow; 18677 xclient.message_type = GetAtom!"XdndFinished"(display); 18678 xclient.format = 32; 18679 xclient.data.l[0] = win.impl.window; 18680 xclient.data.l[1] = 1; // drop successful 18681 xclient.data.l[2] = dndActionAtom(display, acceptedAction); 18682 18683 XSendEvent( 18684 display, 18685 sourceWindow, 18686 false, 18687 EventMask.NoEventMask, 18688 cast(XEvent*) &xclient 18689 ); 18690 18691 XFlush(display); 18692 } 18693 } 18694 18695 Atom findBestFormat(Atom[] answer) { 18696 Atom best = None; 18697 foreach(option; answer) { 18698 if(option == format) { 18699 best = option; 18700 break; 18701 } 18702 /* 18703 if(option == GetAtom!"UTF8_STRING"(XDisplayConnection.get)) { 18704 best = option; 18705 break; 18706 } else if(option == XA_STRING) { 18707 best = option; 18708 } else if(option == GetAtom!"text/plain"(XDisplayConnection.get)) { 18709 best = option; 18710 } 18711 */ 18712 } 18713 return best; 18714 } 18715 } 18716 18717 win.impl.getSelectionHandlers[selectionAtom] = new X11GetSelectionHandler_Drop(display, win, sourceWindow, format, selectionAtom, best, dg, acceptedAction); 18718 18719 XConvertSelection(display, selectionAtom, best, GetAtom!("SDD_DATA", true)(display), win.impl.window, dataTimestamp); 18720 18721 } else version(Windows) { 18722 18723 // clean up like DragLeave 18724 // pass effect back up 18725 18726 FORMATETC t; 18727 assert(format >= 0 && format <= ushort.max); 18728 t.cfFormat = cast(ushort) format; 18729 t.lindex = -1; 18730 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 18731 t.tymed = TYMED.TYMED_HGLOBAL; 18732 18733 STGMEDIUM m; 18734 18735 if(pDataObj.GetData(&t, &m) != S_OK) { 18736 // fail 18737 } else { 18738 // succeed, take the data and clean up 18739 18740 // FIXME: ensure it is legit HGLOBAL 18741 auto handle = m.hGlobal; 18742 18743 if(handle) { 18744 auto sz = GlobalSize(handle); 18745 if(auto ptr = cast(ubyte*) GlobalLock(handle)) { 18746 scope(exit) GlobalUnlock(handle); 18747 scope(exit) GlobalFree(handle); 18748 18749 auto data = ptr[0 .. sz]; 18750 18751 dg(data); 18752 } 18753 } 18754 } 18755 } 18756 } 18757 18758 private: 18759 18760 version(X11) { 18761 SimpleWindow win; 18762 Window sourceWindow; 18763 Time dataTimestamp; 18764 18765 Atom[] xFormats; 18766 } 18767 version(Windows) { 18768 IDataObject pDataObj; 18769 } 18770 } 18771 18772 /++ 18773 A generic helper base class for making a drop handler with a preference list of custom types. 18774 This is the base for [TextDropHandler] and [FilesDropHandler] and you can use it for your own 18775 droppers too. 18776 18777 It assumes the whole window it used, but you can subclass to change that. 18778 18779 $(PITFALL this is not yet stable and may break in future versions without notice.) 18780 18781 History: 18782 Added February 19, 2021 18783 +/ 18784 /// Group: drag_and_drop 18785 class GenericDropHandlerBase : DropHandler { 18786 // no fancy state here so no need to do anything here 18787 void finish() { } 18788 void dragLeave() { } 18789 18790 private DragAndDropAction acceptedAction; 18791 private DraggableData.FormatId acceptedFormat; 18792 private void delegate(scope ubyte[]) acceptedHandler; 18793 18794 struct FormatHandler { 18795 DraggableData.FormatId format; 18796 void delegate(scope ubyte[]) handler; 18797 } 18798 18799 protected abstract FormatHandler[] formatHandlers(); 18800 18801 DragAndDropAction dragEnter(DropPackage* pkg) { 18802 debug(sdpy_dnd) { import std.stdio; foreach(fmt; pkg.availableFormats()) writeln(fmt, " ", DraggableData.getFormatName(fmt)); } 18803 foreach(fmt; formatHandlers()) 18804 foreach(f; pkg.availableFormats()) 18805 if(f == fmt.format) { 18806 acceptedFormat = f; 18807 acceptedHandler = fmt.handler; 18808 return acceptedAction = DragAndDropAction.copy; 18809 } 18810 return acceptedAction = DragAndDropAction.none; 18811 } 18812 DropParameters dragOver(Point pt) { 18813 return DropParameters(acceptedAction); 18814 } 18815 18816 void drop(scope DropPackage* dropPackage) { 18817 if(!acceptedFormat || acceptedHandler is null) { 18818 debug(sdpy_dnd) { import std.stdio; writeln("drop called w/ handler ", acceptedHandler, " and format ", acceptedFormat); } 18819 return; // prolly shouldn't happen anyway... 18820 } 18821 18822 dropPackage.getData(acceptedAction, acceptedFormat, acceptedHandler); 18823 } 18824 } 18825 18826 /++ 18827 A simple handler for making your window accept drops of plain text. 18828 18829 $(PITFALL this is not yet stable and may break in future versions without notice.) 18830 18831 History: 18832 Added February 22, 2021 18833 +/ 18834 /// Group: drag_and_drop 18835 class TextDropHandler : GenericDropHandlerBase { 18836 private void delegate(in char[] text) dg; 18837 18838 /++ 18839 18840 +/ 18841 this(void delegate(in char[] text) dg) { 18842 this.dg = dg; 18843 } 18844 18845 protected override FormatHandler[] formatHandlers() { 18846 version(X11) 18847 return [ 18848 FormatHandler(GetAtom!"UTF8_STRING"(XDisplayConnection.get), &translator), 18849 FormatHandler(GetAtom!"text/plain;charset=utf-8"(XDisplayConnection.get), &translator), 18850 ]; 18851 else version(Windows) 18852 return [ 18853 FormatHandler(CF_UNICODETEXT, &translator), 18854 ]; 18855 } 18856 18857 private void translator(scope ubyte[] data) { 18858 version(X11) 18859 dg(cast(char[]) data); 18860 else version(Windows) 18861 dg(makeUtf8StringFromWindowsString(cast(wchar[]) data)); 18862 } 18863 } 18864 18865 /++ 18866 A simple handler for making your window accept drops of files, issued to you as file names. 18867 18868 $(PITFALL this is not yet stable and may break in future versions without notice.) 18869 18870 History: 18871 Added February 22, 2021 18872 +/ 18873 /// Group: drag_and_drop 18874 18875 class FilesDropHandler : GenericDropHandlerBase { 18876 private void delegate(in char[][]) dg; 18877 18878 /++ 18879 18880 +/ 18881 this(void delegate(in char[][] fileNames) dg) { 18882 this.dg = dg; 18883 } 18884 18885 protected override FormatHandler[] formatHandlers() { 18886 version(X11) 18887 return [ 18888 FormatHandler(GetAtom!"text/uri-list"(XDisplayConnection.get), &translator), 18889 ]; 18890 else version(Windows) 18891 return [ 18892 FormatHandler(CF_HDROP, &translator), 18893 ]; 18894 } 18895 18896 private void translator(scope ubyte[] data) { 18897 version(X11) { 18898 char[] listString = cast(char[]) data; 18899 char[][16] buffer; 18900 int count; 18901 char[][] result = buffer[]; 18902 18903 void commit(char[] s) { 18904 if(count == result.length) 18905 result.length += 16; 18906 if(s.length > 7 && s[0 ..7] == "file://") 18907 s = s[7 .. $]; // FIXME: also may need to trim out the host and do some entity decoding 18908 result[count++] = s; 18909 } 18910 18911 size_t last; 18912 foreach(idx, char c; listString) { 18913 if(c == '\n') { 18914 commit(listString[last .. idx - 1]); // a \r 18915 last = idx + 1; // a \n 18916 } 18917 } 18918 18919 if(last < listString.length) { 18920 commit(listString[last .. $]); 18921 } 18922 18923 // FIXME: they are uris now, should I translate it to local file names? 18924 // of course the host name is supposed to be there cuz of X rokking... 18925 18926 dg(result[0 .. count]); 18927 } else version(Windows) { 18928 18929 static struct DROPFILES { 18930 DWORD pFiles; 18931 POINT pt; 18932 BOOL fNC; 18933 BOOL fWide; 18934 } 18935 18936 18937 const(char)[][16] buffer; 18938 int count; 18939 const(char)[][] result = buffer[]; 18940 size_t last; 18941 18942 void commitA(in char[] stuff) { 18943 if(count == result.length) 18944 result.length += 16; 18945 result[count++] = stuff; 18946 } 18947 18948 void commitW(in wchar[] stuff) { 18949 commitA(makeUtf8StringFromWindowsString(stuff)); 18950 } 18951 18952 void magic(T)(T chars) { 18953 size_t idx; 18954 while(chars[idx]) { 18955 last = idx; 18956 while(chars[idx]) { 18957 idx++; 18958 } 18959 static if(is(T == char*)) 18960 commitA(chars[last .. idx]); 18961 else 18962 commitW(chars[last .. idx]); 18963 idx++; 18964 } 18965 } 18966 18967 auto df = cast(DROPFILES*) data.ptr; 18968 if(df.fWide) { 18969 wchar* chars = cast(wchar*) (data.ptr + df.pFiles); 18970 magic(chars); 18971 } else { 18972 char* chars = cast(char*) (data.ptr + df.pFiles); 18973 magic(chars); 18974 } 18975 dg(result[0 .. count]); 18976 } 18977 } 18978 } 18979 18980 /++ 18981 Interface to describe data being dragged. See also [draggable] helper function. 18982 18983 $(PITFALL this is not yet stable and may break in future versions without notice.) 18984 18985 History: 18986 Added February 19, 2021 18987 +/ 18988 interface DraggableData { 18989 version(X11) 18990 alias FormatId = Atom; 18991 else 18992 alias FormatId = uint; 18993 /++ 18994 Gets the platform-specific FormatId associated with the given named format. 18995 18996 This may be a MIME type, but may also be other various strings defined by the 18997 programs you want to interoperate with. 18998 18999 FIXME: sdpy needs to offer data adapter things that look for compatible formats 19000 and convert it to some particular type for you. 19001 +/ 19002 static FormatId getFormatId(string name)() { 19003 version(X11) 19004 return GetAtom!name(XDisplayConnection.get); 19005 else version(Windows) { 19006 static UINT cache; 19007 if(!cache) 19008 cache = RegisterClipboardFormatA(name); 19009 return cache; 19010 } else 19011 throw new NotYetImplementedException(); 19012 } 19013 19014 /++ 19015 Looks up a string to represent the name for the given format, if there is one. 19016 19017 You should avoid using this function because it is slow. It is provided more for 19018 debugging than for primary use. 19019 +/ 19020 static string getFormatName(FormatId format) { 19021 version(X11) { 19022 if(format == 0) 19023 return "None"; 19024 else 19025 return getAtomName(format, XDisplayConnection.get); 19026 } else version(Windows) { 19027 switch(format) { 19028 case CF_UNICODETEXT: return "CF_UNICODETEXT"; 19029 case CF_DIBV5: return "CF_DIBV5"; 19030 case CF_RIFF: return "CF_RIFF"; 19031 case CF_WAVE: return "CF_WAVE"; 19032 case CF_HDROP: return "CF_HDROP"; 19033 default: 19034 char[1024] name; 19035 auto count = GetClipboardFormatNameA(format, name.ptr, name.length); 19036 return name[0 .. count].idup; 19037 } 19038 } 19039 } 19040 19041 FormatId[] availableFormats(); 19042 // Return the slice of data you filled, empty slice if done. 19043 // this is to support the incremental thing 19044 ubyte[] getData(FormatId format, return scope ubyte[] data); 19045 19046 size_t dataLength(FormatId format); 19047 } 19048 19049 /++ 19050 $(PITFALL this is not yet stable and may break in future versions without notice.) 19051 19052 History: 19053 Added February 19, 2021 19054 +/ 19055 DraggableData draggable(string s) { 19056 version(X11) 19057 return new class X11SetSelectionHandler_Text, DraggableData { 19058 this() { 19059 super(s); 19060 } 19061 19062 override FormatId[] availableFormats() { 19063 return X11SetSelectionHandler_Text.availableFormats(); 19064 } 19065 19066 override ubyte[] getData(FormatId format, return scope ubyte[] data) { 19067 return X11SetSelectionHandler_Text.getData(format, data); 19068 } 19069 19070 size_t dataLength(FormatId format) { 19071 return s.length; 19072 } 19073 }; 19074 version(Windows) 19075 return new class DraggableData { 19076 FormatId[] availableFormats() { 19077 return [CF_UNICODETEXT]; 19078 } 19079 19080 ubyte[] getData(FormatId format, return scope ubyte[] data) { 19081 return cast(ubyte[]) makeWindowsString(s, cast(wchar[]) data, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 19082 } 19083 19084 size_t dataLength(FormatId format) { 19085 return sizeOfConvertedWstring(s, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate) * wchar.sizeof; 19086 } 19087 }; 19088 } 19089 19090 /++ 19091 $(PITFALL this is not yet stable and may break in future versions without notice.) 19092 19093 History: 19094 Added February 19, 2021 19095 +/ 19096 /// Group: drag_and_drop 19097 int doDragDrop(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) 19098 in { 19099 assert(window !is null); 19100 assert(handler !is null); 19101 } 19102 do 19103 { 19104 version(X11) { 19105 auto sh = cast(X11SetSelectionHandler) handler; 19106 if(sh is null) { 19107 // gotta make my own adapter. 19108 sh = new class X11SetSelectionHandler { 19109 mixin X11SetSelectionHandler_Basics; 19110 19111 Atom[] availableFormats() { return handler.availableFormats(); } 19112 ubyte[] getData(Atom format, return scope ubyte[] data) { 19113 return handler.getData(format, data); 19114 } 19115 19116 // since the drop selection is only ever used once it isn't important 19117 // to reset it. 19118 void done() {} 19119 }; 19120 } 19121 return doDragDropX11(window, sh, action); 19122 } else version(Windows) { 19123 return doDragDropWindows(window, handler, action); 19124 } else throw new NotYetImplementedException(); 19125 } 19126 19127 version(Windows) 19128 private int doDragDropWindows(SimpleWindow window, DraggableData handler, DragAndDropAction action = DragAndDropAction.copy) { 19129 IDataObject obj = new class IDataObject { 19130 ULONG refCount; 19131 ULONG AddRef() { 19132 return ++refCount; 19133 } 19134 ULONG Release() { 19135 return --refCount; 19136 } 19137 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 19138 if (IID_IUnknown == *riid) { 19139 *ppv = cast(void*) cast(IUnknown) this; 19140 } 19141 else if (IID_IDataObject == *riid) { 19142 *ppv = cast(void*) cast(IDataObject) this; 19143 } 19144 else { 19145 *ppv = null; 19146 return E_NOINTERFACE; 19147 } 19148 19149 AddRef(); 19150 return NOERROR; 19151 } 19152 19153 HRESULT DAdvise(FORMATETC* pformatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) { 19154 // import std.stdio; writeln("Advise"); 19155 return E_NOTIMPL; 19156 } 19157 HRESULT DUnadvise(DWORD dwConnection) { 19158 return E_NOTIMPL; 19159 } 19160 HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) { 19161 // import std.stdio; writeln("EnumDAdvise"); 19162 return OLE_E_ADVISENOTSUPPORTED; 19163 } 19164 // tell what formats it supports 19165 19166 FORMATETC[] types; 19167 this() { 19168 FORMATETC t; 19169 foreach(ty; handler.availableFormats()) { 19170 assert(ty <= ushort.max && ty >= 0); 19171 t.cfFormat = cast(ushort) ty; 19172 t.lindex = -1; 19173 t.dwAspect = DVASPECT.DVASPECT_CONTENT; 19174 t.tymed = TYMED.TYMED_HGLOBAL; 19175 } 19176 types ~= t; 19177 } 19178 HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatEtc) { 19179 if(dwDirection == DATADIR.DATADIR_GET) { 19180 *ppenumFormatEtc = new class IEnumFORMATETC { 19181 ULONG refCount; 19182 ULONG AddRef() { 19183 return ++refCount; 19184 } 19185 ULONG Release() { 19186 return --refCount; 19187 } 19188 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 19189 if (IID_IUnknown == *riid) { 19190 *ppv = cast(void*) cast(IUnknown) this; 19191 } 19192 else if (IID_IEnumFORMATETC == *riid) { 19193 *ppv = cast(void*) cast(IEnumFORMATETC) this; 19194 } 19195 else { 19196 *ppv = null; 19197 return E_NOINTERFACE; 19198 } 19199 19200 AddRef(); 19201 return NOERROR; 19202 } 19203 19204 19205 int pos; 19206 this() { 19207 pos = 0; 19208 } 19209 19210 HRESULT Clone(IEnumFORMATETC* ppenum) { 19211 // import std.stdio; writeln("clone"); 19212 return E_NOTIMPL; // FIXME 19213 } 19214 19215 // Caller is responsible for freeing memory 19216 HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) { 19217 // fetched may be null if celt is one 19218 if(celt != 1) 19219 return E_NOTIMPL; // FIXME 19220 19221 if(celt + pos > types.length) 19222 return S_FALSE; 19223 19224 *rgelt = types[pos++]; 19225 19226 if(pceltFetched !is null) 19227 *pceltFetched = 1; 19228 19229 // import std.stdio; writeln("ok celt ", celt); 19230 return S_OK; 19231 } 19232 19233 HRESULT Reset() { 19234 pos = 0; 19235 return S_OK; 19236 } 19237 19238 HRESULT Skip(ULONG celt) { 19239 if(celt + pos <= types.length) { 19240 pos += celt; 19241 return S_OK; 19242 } 19243 return S_FALSE; 19244 } 19245 }; 19246 19247 return S_OK; 19248 } else 19249 return E_NOTIMPL; 19250 } 19251 // given a format, return the format you'd prefer to use cuz it is identical 19252 HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) { 19253 // FIXME: prolly could be better but meh 19254 // import std.stdio; writeln("gcf: ", *pformatectIn); 19255 *pformatetcOut = *pformatectIn; 19256 return S_OK; 19257 } 19258 HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 19259 foreach(ty; types) { 19260 if(ty == *pformatetcIn) { 19261 auto format = ty.cfFormat; 19262 // import std.stdio; writeln("A: ", *pformatetcIn, "\nB: ", ty); 19263 STGMEDIUM medium; 19264 medium.tymed = TYMED.TYMED_HGLOBAL; 19265 19266 auto sz = handler.dataLength(format); 19267 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz); 19268 if(handle is null) throw new Exception("GlobalAlloc"); 19269 if(auto data = cast(wchar*) GlobalLock(handle)) { 19270 auto slice = data[0 .. sz]; 19271 scope(exit) 19272 GlobalUnlock(handle); 19273 19274 handler.getData(format, cast(ubyte[]) slice[]); 19275 } 19276 19277 19278 medium.hGlobal = handle; // FIXME 19279 *pmedium = medium; 19280 return S_OK; 19281 } 19282 } 19283 return DV_E_FORMATETC; 19284 } 19285 HRESULT GetDataHere(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) { 19286 // import std.stdio; writeln("GDH: ", *pformatetcIn); 19287 return E_NOTIMPL; // FIXME 19288 } 19289 HRESULT QueryGetData(FORMATETC* pformatetc) { 19290 auto search = *pformatetc; 19291 search.tymed &= TYMED.TYMED_HGLOBAL; 19292 foreach(ty; types) 19293 if(ty == search) { 19294 // import std.stdio; writeln("QueryGetData ", search, " ", types[0]); 19295 return S_OK; 19296 } 19297 if(pformatetc.cfFormat==CF_UNICODETEXT) { 19298 //import std.stdio; writeln("QueryGetData FALSE ", search, " ", types[0]); 19299 } 19300 return S_FALSE; 19301 } 19302 HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) { 19303 // import std.stdio; writeln("SetData: "); 19304 return E_NOTIMPL; 19305 } 19306 }; 19307 19308 19309 IDropSource src = new class IDropSource { 19310 ULONG refCount; 19311 ULONG AddRef() { 19312 return ++refCount; 19313 } 19314 ULONG Release() { 19315 return --refCount; 19316 } 19317 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 19318 if (IID_IUnknown == *riid) { 19319 *ppv = cast(void*) cast(IUnknown) this; 19320 } 19321 else if (IID_IDropSource == *riid) { 19322 *ppv = cast(void*) cast(IDropSource) this; 19323 } 19324 else { 19325 *ppv = null; 19326 return E_NOINTERFACE; 19327 } 19328 19329 AddRef(); 19330 return NOERROR; 19331 } 19332 19333 int QueryContinueDrag(int fEscapePressed, uint grfKeyState) { 19334 if(fEscapePressed) 19335 return DRAGDROP_S_CANCEL; 19336 if(!(grfKeyState & MK_LBUTTON)) 19337 return DRAGDROP_S_DROP; 19338 return S_OK; 19339 } 19340 19341 int GiveFeedback(uint dwEffect) { 19342 return DRAGDROP_S_USEDEFAULTCURSORS; 19343 } 19344 }; 19345 19346 DWORD effect; 19347 19348 if(action == DragAndDropAction.none) assert(0, "Don't drag something with a none effect."); 19349 19350 DROPEFFECT de = win32DragAndDropAction(action); 19351 19352 // I'm not as concerned about the GC here since DoDragDrop blocks so the stack frame still sane the whole time 19353 // but still prolly a FIXME 19354 19355 auto ret = DoDragDrop(obj, src, de, &effect); 19356 /+ 19357 import std.stdio; 19358 if(ret == DRAGDROP_S_DROP) 19359 writeln("drop ", effect); 19360 else if(ret == DRAGDROP_S_CANCEL) 19361 writeln("cancel"); 19362 else if(ret == S_OK) 19363 writeln("ok"); 19364 else writeln(ret); 19365 +/ 19366 19367 return ret; 19368 } 19369 19370 version(Windows) 19371 DROPEFFECT win32DragAndDropAction(DragAndDropAction action) { 19372 DROPEFFECT de; 19373 19374 with(DragAndDropAction) 19375 with(DROPEFFECT) 19376 final switch(action) { 19377 case none: de = DROPEFFECT_NONE; break; 19378 case copy: de = DROPEFFECT_COPY; break; 19379 case move: de = DROPEFFECT_MOVE; break; 19380 case link: de = DROPEFFECT_LINK; break; 19381 case ask: throw new Exception("ask not implemented yet"); 19382 case custom: throw new Exception("custom not implemented yet"); 19383 } 19384 19385 return de; 19386 } 19387 19388 19389 /++ 19390 History: 19391 Added February 19, 2021 19392 +/ 19393 /// Group: drag_and_drop 19394 void enableDragAndDrop(SimpleWindow window, DropHandler handler) { 19395 version(X11) { 19396 auto display = XDisplayConnection.get; 19397 19398 Atom atom = 5; // right??? 19399 19400 XChangeProperty( 19401 display, 19402 window.impl.window, 19403 GetAtom!"XdndAware"(display), 19404 XA_ATOM, 19405 32 /* bits */, 19406 PropModeReplace, 19407 &atom, 19408 1); 19409 19410 window.dropHandler = handler; 19411 } else version(Windows) { 19412 19413 initDnd(); 19414 19415 auto dropTarget = new class (handler) IDropTarget { 19416 DropHandler handler; 19417 this(DropHandler handler) { 19418 this.handler = handler; 19419 } 19420 ULONG refCount; 19421 ULONG AddRef() { 19422 return ++refCount; 19423 } 19424 ULONG Release() { 19425 return --refCount; 19426 } 19427 HRESULT QueryInterface(const (IID)*riid, LPVOID *ppv) { 19428 if (IID_IUnknown == *riid) { 19429 *ppv = cast(void*) cast(IUnknown) this; 19430 } 19431 else if (IID_IDropTarget == *riid) { 19432 *ppv = cast(void*) cast(IDropTarget) this; 19433 } 19434 else { 19435 *ppv = null; 19436 return E_NOINTERFACE; 19437 } 19438 19439 AddRef(); 19440 return NOERROR; 19441 } 19442 19443 19444 // /////////////////// 19445 19446 HRESULT DragEnter(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 19447 DropPackage dropPackage = DropPackage(pDataObj); 19448 *pdwEffect = win32DragAndDropAction(handler.dragEnter(&dropPackage)); 19449 return S_OK; // https://docs.microsoft.com/en-us/windows/win32/api/oleidl/nf-oleidl-idroptarget-dragenter 19450 } 19451 19452 HRESULT DragLeave() { 19453 handler.dragLeave(); 19454 // release the IDataObject if needed 19455 return S_OK; 19456 } 19457 19458 HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 19459 auto res = handler.dragOver(Point(pt.x, pt.y)); // FIXME: translate screen coordinates back to window coordinates 19460 19461 *pdwEffect = win32DragAndDropAction(res.action); 19462 // same as DragEnter basically 19463 return S_OK; 19464 } 19465 19466 HRESULT Drop(IDataObject pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { 19467 DropPackage pkg = DropPackage(pDataObj); 19468 handler.drop(&pkg); 19469 19470 return S_OK; 19471 } 19472 }; 19473 // Windows can hold on to the handler and try to call it 19474 // during which time the GC can't see it. so important to 19475 // manually manage this. At some point i'll FIXME and make 19476 // all my com instances manually managed since they supposed 19477 // to respect the refcount. 19478 import core.memory; 19479 GC.addRoot(cast(void*) dropTarget); 19480 19481 if(RegisterDragDrop(window.impl.hwnd, dropTarget) != S_OK) 19482 throw new Exception("register"); 19483 19484 window.dropHandler = handler; 19485 } else throw new NotYetImplementedException(); 19486 } 19487 19488 19489 19490 static if(UsingSimpledisplayX11) { 19491 19492 enum _NET_WM_STATE_ADD = 1; 19493 enum _NET_WM_STATE_REMOVE = 0; 19494 enum _NET_WM_STATE_TOGGLE = 2; 19495 19496 /// X-specific. Use [SimpleWindow.requestAttention] instead for most casesl 19497 void demandAttention(SimpleWindow window, bool needs = true) { 19498 demandAttention(window.impl.window, needs); 19499 } 19500 19501 /// ditto 19502 void demandAttention(Window window, bool needs = true) { 19503 setNetWmStateAtom(window, GetAtom!("_NET_WM_STATE_DEMANDS_ATTENTION", false)(XDisplayConnection.get), needs); 19504 } 19505 19506 void setNetWmStateAtom(Window window, Atom atom, bool set = true, Atom atom2 = None) { 19507 auto display = XDisplayConnection.get(); 19508 if(atom == None) 19509 return; // non-failure error 19510 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 19511 19512 XClientMessageEvent xclient; 19513 19514 xclient.type = EventType.ClientMessage; 19515 xclient.window = window; 19516 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 19517 xclient.format = 32; 19518 xclient.data.l[0] = set ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 19519 xclient.data.l[1] = atom; 19520 xclient.data.l[2] = atom2; 19521 xclient.data.l[3] = 1; 19522 // [3] == source. 0 == unknown, 1 == app, 2 == else 19523 19524 XSendEvent( 19525 display, 19526 RootWindow(display, DefaultScreen(display)), 19527 false, 19528 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 19529 cast(XEvent*) &xclient 19530 ); 19531 19532 /+ 19533 XChangeProperty( 19534 display, 19535 window.impl.window, 19536 GetAtom!"_NET_WM_STATE"(display), 19537 XA_ATOM, 19538 32 /* bits */, 19539 PropModeAppend, 19540 &atom, 19541 1); 19542 +/ 19543 } 19544 19545 private Atom dndActionAtom(Display* display, DragAndDropAction action) { 19546 Atom actionAtom; 19547 with(DragAndDropAction) 19548 final switch(action) { 19549 case none: actionAtom = None; break; 19550 case copy: actionAtom = GetAtom!"XdndActionCopy"(display); break; 19551 case move: actionAtom = GetAtom!"XdndActionMove"(display); break; 19552 case link: actionAtom = GetAtom!"XdndActionLink"(display); break; 19553 case ask: actionAtom = GetAtom!"XdndActionAsk"(display); break; 19554 case custom: actionAtom = GetAtom!"XdndActionCustom"(display); break; 19555 } 19556 19557 return actionAtom; 19558 } 19559 19560 private int doDragDropX11(SimpleWindow window, X11SetSelectionHandler handler, DragAndDropAction action) { 19561 // FIXME: I need to show user feedback somehow. 19562 auto display = XDisplayConnection.get; 19563 19564 auto actionAtom = dndActionAtom(display, action); 19565 assert(actionAtom, "Don't use action none to accept a drop"); 19566 19567 setX11Selection!"XdndSelection"(window, handler, null); 19568 19569 auto oldKeyHandler = window.handleKeyEvent; 19570 scope(exit) window.handleKeyEvent = oldKeyHandler; 19571 19572 auto oldCharHandler = window.handleCharEvent; 19573 scope(exit) window.handleCharEvent = oldCharHandler; 19574 19575 auto oldMouseHandler = window.handleMouseEvent; 19576 scope(exit) window.handleMouseEvent = oldMouseHandler; 19577 19578 Window[Window] eligibility; // 0 == not eligible, otherwise it is the window id of an eligible child 19579 19580 import core.sys.posix.sys.time; 19581 timeval tv; 19582 gettimeofday(&tv, null); 19583 19584 Time dataTimestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; 19585 19586 Time lastMouseTimestamp; 19587 19588 bool dnding = true; 19589 Window lastIn = None; 19590 19591 void leave() { 19592 if(lastIn == None) 19593 return; 19594 19595 XEvent ev; 19596 ev.xclient.type = EventType.ClientMessage; 19597 ev.xclient.window = lastIn; 19598 ev.xclient.message_type = GetAtom!("XdndLeave", true)(display); 19599 ev.xclient.format = 32; 19600 ev.xclient.data.l[0] = window.impl.window; 19601 19602 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 19603 XFlush(display); 19604 19605 lastIn = None; 19606 } 19607 19608 void enter(Window w) { 19609 assert(lastIn == None); 19610 19611 lastIn = w; 19612 19613 XEvent ev; 19614 ev.xclient.type = EventType.ClientMessage; 19615 ev.xclient.window = lastIn; 19616 ev.xclient.message_type = GetAtom!("XdndEnter", true)(display); 19617 ev.xclient.format = 32; 19618 ev.xclient.data.l[0] = window.impl.window; 19619 ev.xclient.data.l[1] = (5 << 24) | 0; // version 5, no more sources. FIXME source types 19620 19621 auto types = handler.availableFormats(); 19622 assert(types.length > 0); 19623 19624 ev.xclient.data.l[2] = types[0]; 19625 if(types.length > 1) 19626 ev.xclient.data.l[3] = types[1]; 19627 if(types.length > 2) 19628 ev.xclient.data.l[4] = types[2]; 19629 19630 // FIXME: other types?!?!? and make sure we skip TARGETS 19631 19632 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 19633 XFlush(display); 19634 } 19635 19636 void position(int rootX, int rootY) { 19637 assert(lastIn != None); 19638 19639 XEvent ev; 19640 ev.xclient.type = EventType.ClientMessage; 19641 ev.xclient.window = lastIn; 19642 ev.xclient.message_type = GetAtom!("XdndPosition", true)(display); 19643 ev.xclient.format = 32; 19644 ev.xclient.data.l[0] = window.impl.window; 19645 ev.xclient.data.l[1] = 0; // reserved 19646 ev.xclient.data.l[2] = (rootX << 16) | rootY; 19647 ev.xclient.data.l[3] = dataTimestamp; 19648 ev.xclient.data.l[4] = actionAtom; 19649 19650 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 19651 XFlush(display); 19652 19653 } 19654 19655 void drop() { 19656 XEvent ev; 19657 ev.xclient.type = EventType.ClientMessage; 19658 ev.xclient.window = lastIn; 19659 ev.xclient.message_type = GetAtom!("XdndDrop", true)(display); 19660 ev.xclient.format = 32; 19661 ev.xclient.data.l[0] = window.impl.window; 19662 ev.xclient.data.l[1] = 0; // reserved 19663 ev.xclient.data.l[2] = dataTimestamp; 19664 19665 XSendEvent(XDisplayConnection.get, lastIn, false, EventMask.NoEventMask, &ev); 19666 XFlush(display); 19667 19668 lastIn = None; 19669 dnding = false; 19670 } 19671 19672 // fyi nativeEventHandler can return 0 if it handles it, or otherwise it goes back to the normal handler 19673 // but idk if i should... 19674 19675 window.setEventHandlers( 19676 delegate(KeyEvent ev) { 19677 if(ev.pressed == true && ev.key == Key.Escape) { 19678 // cancel 19679 dnding = false; 19680 } 19681 }, 19682 delegate(MouseEvent ev) { 19683 if(ev.timestamp < lastMouseTimestamp) 19684 return; 19685 19686 lastMouseTimestamp = ev.timestamp; 19687 19688 if(ev.type == MouseEventType.motion) { 19689 auto display = XDisplayConnection.get; 19690 auto root = RootWindow(display, DefaultScreen(display)); 19691 19692 Window topWindow; 19693 int rootX, rootY; 19694 19695 XTranslateCoordinates(display, window.impl.window, root, ev.x, ev.y, &rootX, &rootY, &topWindow); 19696 19697 if(topWindow == None) 19698 return; 19699 19700 top: 19701 if(auto result = topWindow in eligibility) { 19702 auto dropWindow = *result; 19703 if(dropWindow == None) { 19704 leave(); 19705 return; 19706 } 19707 19708 if(dropWindow != lastIn) { 19709 leave(); 19710 enter(dropWindow); 19711 position(rootX, rootY); 19712 } else { 19713 position(rootX, rootY); 19714 } 19715 } else { 19716 // determine eligibility 19717 auto data = cast(Atom[]) getX11PropertyData(topWindow, GetAtom!"XdndAware"(display), XA_ATOM); 19718 if(data.length == 1) { 19719 // in case there is no WM or it isn't reparenting 19720 eligibility[topWindow] = (data[0] == 5) ? topWindow : None; // FIXME I'm supposed to handle older versions too but meh 19721 } else { 19722 19723 Window tryScanChildren(Window search, int maxRecurse) { 19724 // could be reparenting window manager, so gotta check the next few children too 19725 Window child; 19726 int x; 19727 int y; 19728 XTranslateCoordinates(display, window.impl.window, search, ev.x, ev.y, &x, &y, &child); 19729 19730 if(child == None) 19731 return None; 19732 auto data = cast(Atom[]) getX11PropertyData(child, GetAtom!"XdndAware"(display), XA_ATOM); 19733 if(data.length == 1) { 19734 return (data[0] == 5) ? child : None; // FIXME I'm supposed to handle older versions too but meh 19735 } else { 19736 if(maxRecurse) 19737 return tryScanChildren(child, maxRecurse - 1); 19738 else 19739 return None; 19740 } 19741 19742 } 19743 19744 // if a WM puts more than 3 layers on it, like wtf is it doing, screw that. 19745 auto topResult = tryScanChildren(topWindow, 3); 19746 // it is easy to have a false negative due to the mouse going over a WM 19747 // child window like the close button if separate from the frame... so I 19748 // can't really cache negatives, :( 19749 if(topResult != None) { 19750 eligibility[topWindow] = topResult; 19751 goto top; // reload to do the positioning iff eligibility changed lest we endless loop 19752 } 19753 } 19754 19755 } 19756 19757 } else if(ev.type == MouseEventType.buttonReleased) { 19758 drop(); 19759 dnding = false; 19760 } 19761 } 19762 ); 19763 19764 window.grabInput(); 19765 scope(exit) 19766 window.releaseInputGrab(); 19767 19768 19769 EventLoop.get.run(() => dnding); 19770 19771 return 0; 19772 } 19773 19774 /// X-specific 19775 TrueColorImage getWindowNetWmIcon(Window window) { 19776 try { 19777 auto display = XDisplayConnection.get; 19778 19779 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 19780 19781 if (data.length > arch_ulong.sizeof * 2) { 19782 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 19783 // these are an array of rgba images that we have to convert into pixmaps ourself 19784 19785 int width = cast(int) meta[0]; 19786 int height = cast(int) meta[1]; 19787 19788 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 19789 19790 static if(arch_ulong.sizeof == 4) { 19791 bytes = bytes[0 .. width * height * 4]; 19792 alias imageData = bytes; 19793 } else static if(arch_ulong.sizeof == 8) { 19794 bytes = bytes[0 .. width * height * 8]; 19795 auto imageData = new ubyte[](4 * width * height); 19796 } else static assert(0); 19797 19798 19799 19800 // this returns ARGB. Remember it is little-endian so 19801 // we have BGRA 19802 // our thing uses RGBA, which in little endian, is ABGR 19803 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 19804 auto r = bytes[idx + 2]; 19805 auto g = bytes[idx + 1]; 19806 auto b = bytes[idx + 0]; 19807 auto a = bytes[idx + 3]; 19808 19809 imageData[idx2 + 0] = r; 19810 imageData[idx2 + 1] = g; 19811 imageData[idx2 + 2] = b; 19812 imageData[idx2 + 3] = a; 19813 } 19814 19815 return new TrueColorImage(width, height, imageData); 19816 } 19817 19818 return null; 19819 } catch(Exception e) { 19820 return null; 19821 } 19822 } 19823 19824 } /* UsingSimpledisplayX11 */ 19825 19826 19827 void loadBinNameToWindowClassName () { 19828 import core.stdc.stdlib : realloc; 19829 version(linux) { 19830 // args[0] MAY be empty, so we'll just use this 19831 import core.sys.posix.unistd : readlink; 19832 char[1024] ebuf = void; // 1KB should be enough for everyone! 19833 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 19834 if (len < 1) return; 19835 } else /*version(Windows)*/ { 19836 import core.runtime : Runtime; 19837 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 19838 auto ebuf = Runtime.args[0]; 19839 auto len = ebuf.length; 19840 } 19841 auto pos = len; 19842 while (pos > 0 && ebuf[pos-1] != '/') --pos; 19843 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 19844 if (sdpyWindowClassStr is null) return; // oops 19845 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 19846 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 19847 } 19848 19849 /++ 19850 An interface representing a font. 19851 19852 This is still MAJOR work in progress. 19853 +/ 19854 interface DrawableFont { 19855 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 19856 } 19857 19858 /++ 19859 Loads a true type font using [arsd.ttf]. That module must be compiled 19860 in if you choose to use this function. 19861 19862 Be warned: this can be slow and memory hungry, especially on remote connections 19863 to the X server. 19864 19865 This is still MAJOR work in progress. 19866 +/ 19867 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 19868 import arsd.ttf; 19869 static class ArsdTtfFont : DrawableFont { 19870 TtfFont font; 19871 int size; 19872 this(in ubyte[] data, int size) { 19873 font = TtfFont(data); 19874 this.size = size; 19875 } 19876 19877 Sprite[string] cache; 19878 19879 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 19880 Sprite sprite = (text in cache) ? *(text in cache) : null; 19881 19882 auto fg = painter.impl._outlineColor; 19883 auto bg = painter.impl._fillColor; 19884 19885 if(sprite is null) { 19886 int width, height; 19887 auto data = font.renderString(text, size, width, height); 19888 auto image = new TrueColorImage(width, height); 19889 int pos = 0; 19890 foreach(y; 0 .. height) 19891 foreach(x; 0 .. width) { 19892 fg.a = data[0]; 19893 bg.a = 255; 19894 auto color = alphaBlend(fg, bg); 19895 image.imageData.bytes[pos++] = color.r; 19896 image.imageData.bytes[pos++] = color.g; 19897 image.imageData.bytes[pos++] = color.b; 19898 image.imageData.bytes[pos++] = data[0]; 19899 data = data[1 .. $]; 19900 } 19901 assert(data.length == 0); 19902 19903 sprite = new Sprite(painter.window, Image.fromMemoryImage(image)); 19904 cache[text.idup] = sprite; 19905 } 19906 19907 sprite.drawAt(painter, upperLeft); 19908 } 19909 } 19910 19911 return new ArsdTtfFont(data, size); 19912 } 19913 19914 class NotYetImplementedException : Exception { 19915 this(string file = __FILE__, size_t line = __LINE__) { 19916 super("Not yet implemented", file, line); 19917 } 19918 } 19919 19920 /// 19921 __gshared bool librariesSuccessfullyLoaded = true; 19922 /// 19923 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 19924 19925 private mixin template DynamicLoadSupplementalOpenGL(Iface) { 19926 mixin(staticForeachReplacement!Iface); 19927 19928 void loadDynamicLibrary() @nogc { 19929 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 19930 } 19931 19932 void loadDynamicLibraryForReal() { 19933 foreach(name; __traits(derivedMembers, Iface)) { 19934 mixin("alias tmp = " ~ name ~ ";"); 19935 tmp = cast(typeof(tmp)) glbindGetProcAddress(name); 19936 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from supplemental OpenGL"); 19937 } 19938 } 19939 } 19940 19941 private const(char)[] staticForeachReplacement(Iface)() pure { 19942 /* 19943 // just this for gdc 9.... 19944 // when i drop support for it and switch to gdc10, we can put this original back for a slight compile time ram decrease 19945 19946 static foreach(name; __traits(derivedMembers, Iface)) 19947 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 19948 */ 19949 19950 char[] code = new char[](__traits(derivedMembers, Iface).length * 64); 19951 size_t pos; 19952 19953 void append(in char[] what) { 19954 if(pos + what.length > code.length) 19955 code.length = (code.length * 3) / 2; 19956 code[pos .. pos + what.length] = what[]; 19957 pos += what.length; 19958 } 19959 19960 foreach(name; __traits(derivedMembers, Iface)) { 19961 append(`__gshared typeof(&__traits(getMember, Iface, "`); 19962 append(name); 19963 append(`")) `); 19964 append(name); 19965 append(";"); 19966 } 19967 19968 return code[0 .. pos]; 19969 } 19970 19971 private mixin template DynamicLoad(Iface, string library, int majorVersion, bool openGLRelated = false, bool optional = false) { 19972 mixin(staticForeachReplacement!Iface); 19973 19974 private void* libHandle; 19975 private bool attempted; 19976 19977 void loadDynamicLibrary() @nogc { 19978 (cast(void function() @nogc) &loadDynamicLibraryForReal)(); 19979 } 19980 19981 bool loadAttempted() { 19982 return attempted; 19983 } 19984 bool loadSuccessful() { 19985 return libHandle !is null; 19986 } 19987 19988 void loadDynamicLibraryForReal() { 19989 attempted = true; 19990 version(Posix) { 19991 import core.sys.posix.dlfcn; 19992 version(OSX) { 19993 version(X11) 19994 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 19995 else 19996 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 19997 } else { 19998 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 19999 if(libHandle is null) 20000 libHandle = dlopen(("lib" ~ library ~ ".so." ~ toInternal!string(majorVersion) ~ "\0").ptr, RTLD_NOW); 20001 } 20002 20003 static void* loadsym(void* l, const char* name) { 20004 import core.stdc.stdlib; 20005 if(l is null) 20006 return &abort; 20007 return dlsym(l, name); 20008 } 20009 } else version(Windows) { 20010 import core.sys.windows.windows; 20011 libHandle = LoadLibrary(library ~ ".dll"); 20012 static void* loadsym(void* l, const char* name) { 20013 import core.stdc.stdlib; 20014 if(l is null) 20015 return &abort; 20016 return GetProcAddress(l, name); 20017 } 20018 } 20019 if(libHandle is null && !optional) { 20020 if(openGLRelated) 20021 openGlLibrariesSuccessfullyLoaded = false; 20022 else 20023 librariesSuccessfullyLoaded = false; 20024 //throw new Exception("load failure of library " ~ library); 20025 } 20026 foreach(name; __traits(derivedMembers, Iface)) { 20027 mixin("alias tmp = " ~ name ~ ";"); 20028 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 20029 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 20030 } 20031 } 20032 20033 void unloadDynamicLibrary() { 20034 version(Posix) { 20035 import core.sys.posix.dlfcn; 20036 dlclose(libHandle); 20037 } else version(Windows) { 20038 import core.sys.windows.windows; 20039 FreeLibrary(libHandle); 20040 } 20041 foreach(name; __traits(derivedMembers, Iface)) 20042 mixin(name ~ " = null;"); 20043 } 20044 } 20045 20046 void guiAbortProcess(string msg) { 20047 import core.stdc.stdlib; 20048 version(Windows) { 20049 WCharzBuffer t = WCharzBuffer(msg); 20050 MessageBoxW(null, t.ptr, "Program Termination"w.ptr, 0); 20051 } else { 20052 import std.stdio; 20053 stderr.writeln(msg); 20054 stderr.flush(); 20055 } 20056 20057 abort(); 20058 } 20059 20060 private alias scriptable = arsd_jsvar_compatible;