1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 4 // on Mac with X11: -L-L/usr/X11/lib 5 6 /* 7 Event Loop would be nices: 8 9 * add on idle - runs when nothing else happens 10 * send messages without a recipient window 11 * setTimeout 12 * setInterval 13 */ 14 15 /* 16 Classic games I want to add: 17 * my tetris clone 18 * pac man 19 */ 20 21 /* 22 Text layout needs a lot of work. Plain drawText is useful but too 23 limited. It will need some kind of text context thing which it will 24 update and you can pass it on and get more details out of it. 25 26 It will need a bounding box, a current cursor location that is updated 27 as drawing continues, and various changable facts (which can also be 28 changed on the painter i guess) like font, color, size, background, 29 etc. 30 31 We can also fetch the caret location from it somehow. 32 33 Should prolly be an overload of drawText 34 35 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 36 37 WS_EX_NOACTIVATE 38 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 39 full screen windows. Can just set the atom on X. Windows will be harder. 40 41 moving windows. resizing windows. 42 43 hide cursor, capture cursor, change cursor. 44 45 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 46 sure the pieces are there to do its job easily and make other jobs possible. 47 */ 48 49 /++ 50 simpledisplay.d provides basic cross-platform GUI-related functionality, 51 including creating windows, drawing on them, working with the clipboard, 52 timers, OpenGL, and more. However, it does NOT provide high level GUI 53 widgets. See my minigui.d, an extension to this module, for that 54 functionality. 55 56 simpledisplay provides cross-platform wrapping for Windows and Linux 57 (and perhaps other OSes that use X11), but also does not prevent you 58 from using the underlying facilities if you need them. It has a goal 59 of working efficiently over a remote X link (at least as far as Xlib 60 reasonably allows.) 61 62 simpledisplay depends on [arsd.color|color.d], which should be available from the 63 same place where you got this file. Other than that, however, it has 64 very few dependencies and ones that don't come with the OS and/or the 65 compiler are all opt-in. 66 67 simpledisplay.d's home base is on my arsd repo on Github. The file is: 68 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 69 70 simpledisplay is basically stable. I plan to refactor the internals, 71 and may add new features and fix bugs, but It do not expect to 72 significantly change the API. It has been stable a few years already now. 73 74 Installation_instructions: 75 76 `simpledisplay.d` does not have any dependencies outside the 77 operating system and `color.d`, so it should just work most the 78 time, but there are a few caveats on some systems: 79 80 Please note when compiling on Win64, you need to explicitly list 81 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 82 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 83 84 If using ldc instead of dmd, use `-L/entry:wmainCRTstartup` instead of `mainCRTStartup`; 85 note the "w". 86 87 On Win32, you can pass `-L/subsystem:windows` if you don't want a 88 console to be automatically allocated. 89 90 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. 91 92 On Ubuntu, you might need to install X11 development libraries to 93 successfully link. 94 95 $(CONSOLE 96 $ sudo apt-get install libglc-dev 97 $ sudo apt-get install libx11-dev 98 ) 99 100 101 Jump_list: 102 103 Don't worry, you don't have to read this whole documentation file! 104 105 Check out the [#Event-example] and [#Pong-example] to get started quickly. 106 107 The main classes you may want to create are [SimpleWindow], [Timer], 108 [Image], and [Sprite]. 109 110 The main functions you'll want are [setClipboardText] and [getClipboardText]. 111 112 There are also platform-specific functions available such as [XDisplayConnection] 113 and [GetAtom] for X11, among others. 114 115 See the examples and topics list below to learn more. 116 117 $(WARNING 118 There should only be one GUI thread per application, 119 and all windows should be created in it and your 120 event loop should run there. 121 122 To do otherwise is undefined behavior and has no 123 cross platform guarantees. 124 ) 125 126 $(H2 About this documentation) 127 128 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. 129 130 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! 131 132 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. 133 134 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. 135 136 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. 137 138 At points, I will talk about implementation details in the documentation. These are sometimes 139 subject to change, but nevertheless useful to understand what is really going on. You can learn 140 more about some of the referenced things by searching the web for info about using them from C. 141 You can always look at the source of simpledisplay.d too for the most authoritative source on 142 its specific implementation. If you disagree with how I did something, please contact me so we 143 can discuss it! 144 145 Examples: 146 147 $(H3 Event-example) 148 This program creates a window and draws events inside them as they 149 happen, scrolling the text in the window as needed. Run this program 150 and experiment to get a feel for where basic input events take place 151 in the library. 152 153 --- 154 // dmd example.d simpledisplay.d color.d 155 import arsd.simpledisplay; 156 import std.conv; 157 158 void main() { 159 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 160 161 int y = 0; 162 163 void addLine(string text) { 164 auto painter = window.draw(); 165 166 if(y + painter.fontHeight >= window.height) { 167 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 168 y -= painter.fontHeight; 169 } 170 171 painter.outlineColor = Color.red; 172 painter.fillColor = Color.black; 173 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 174 175 painter.outlineColor = Color.white; 176 177 painter.drawText(Point(10, y), text); 178 179 y += painter.fontHeight; 180 } 181 182 window.eventLoop(1000, 183 () { 184 addLine("Timer went off!"); 185 }, 186 (KeyEvent event) { 187 addLine(to!string(event)); 188 }, 189 (MouseEvent event) { 190 addLine(to!string(event)); 191 }, 192 (dchar ch) { 193 addLine(to!string(ch)); 194 } 195 ); 196 } 197 --- 198 199 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. 200 201 This program displays a pie chart. Clicking on a color will increase its share of the pie. 202 203 --- 204 205 --- 206 207 $(H2 Topics) 208 209 $(H3 $(ID topic-windows) Windows) 210 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 211 window on the user's screen. 212 213 You may create multiple windows, if the underlying platform supports it. You may check 214 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 215 SimpleWindow's constructor at runtime to handle those cases. 216 217 A single running event loop will handle as many windows as needed. 218 219 setEventHandlers function 220 eventLoop function 221 draw function 222 title property 223 224 $(H3 $(ID topic-event-loops) Event loops) 225 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 226 227 The most common scenario is creating a window, then calling [SimpleWindow.eventLoop|window.eventLoop] when setup is complete. You can pass several handlers to the `eventLoop` method right there: 228 229 --- 230 // dmd example.d simpledisplay.d color.d 231 import arsd.simpledisplay; 232 void main() { 233 auto window = new SimpleWindow(200, 200); 234 window.eventLoop(0, 235 delegate (dchar) { /* got a character key press */ } 236 ); 237 } 238 --- 239 240 $(TIP If you get a compile error saying "I can't use this event handler", the most common thing in my experience is passing a function instead of a delegate. The simple solution is to use the `delegate` keyword, like I did in the example above.) 241 242 On Linux, the event loop is implemented with the `epoll` system call for efficiency an extensibility to other files. On Windows, it runs a traditional `GetMessage` + `DispatchMessage` loop, with a call to `SleepEx` in each iteration to allow the thread to enter an alertable wait state regularly, primarily so Overlapped I/O callbacks will get a chance to run. 243 244 On Linux, simpledisplay also supports my [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 245 246 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 247 248 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 249 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. 250 251 $(H3 $(ID topic-input-handling) Input handling) 252 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 253 254 $(H3 $(ID topic-2d-drawing) 2d Drawing) 255 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 256 257 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: 258 259 --- 260 // dmd example.d simpledisplay.d color.d 261 import arsd.simpledisplay; 262 void main() { 263 auto window = new SimpleWindow(200, 200); 264 { // introduce sub-scope 265 auto painter = window.draw(); // begin drawing 266 /* draw here */ 267 painter.outlineColor = Color.red; 268 painter.fillColor = Color.black; 269 painter.drawRectangle(Point(0, 0), 200, 200); 270 } // end scope, calling `painter`'s destructor, drawing to the screen. 271 window.eventLoop(0); // handle events 272 } 273 --- 274 275 Painting is done based on two color properties, a pen and a brush. 276 277 At this time, the 2d drawing does not support alpha blending. If you need that, use a 2d OpenGL context instead. 278 FIXME add example of 2d opengl drawing here 279 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 280 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 281 282 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. 283 284 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 285 286 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 287 288 To force a redraw of the scene, call [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlSceneNow()]. 289 290 Please note that my experience with OpenGL is very out-of-date, and the bindings in simpledisplay reflect that. If you want to use more modern functions, you may have to define the bindings yourself, or import them from another module. However, the OpenGL context creation done in simpledisplay will work for any version. 291 292 This example program will draw a rectangle on your window: 293 294 --- 295 // dmd example.d simpledisplay.d color.d 296 import arsd.simpledisplay; 297 298 void main() { 299 300 } 301 --- 302 303 $(H3 $(ID topic-images) Displaying images) 304 You can also load PNG images using [arsd.png]. 305 306 --- 307 // dmd example.d simpledisplay.d color.d png.d 308 import arsd.simpledisplay; 309 import arsd.png; 310 311 void main() { 312 auto image = Image.fromMemoryImage(readPng("image.png")); 313 displayImage(image); 314 } 315 --- 316 317 Compile with `dmd example.d simpledisplay.d png.d`. 318 319 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. 320 321 $(H3 $(ID topic-sprites) Sprites) 322 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. 323 324 $(H3 $(ID topic-clipboard) Clipboard) 325 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 326 327 It also has helpers for handling X-specific events. 328 329 $(H3 $(ID topic-timers) Timers) 330 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]. 331 332 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. 333 334 --- 335 import arsd.simpledisplay; 336 337 void main() { 338 auto window = new SimpleWindow(400, 400); 339 // every 100 ms, it will draw a random line 340 // on the window. 341 window.eventLoop(100, { 342 auto painter = window.draw(); 343 344 import std.random; 345 // random color 346 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 347 // random line 348 painter.drawLine( 349 Point(uniform(0, window.width), uniform(0, window.height)), 350 Point(uniform(0, window.width), uniform(0, window.height))); 351 352 }); 353 } 354 --- 355 356 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. 357 358 The pulse timer and instances of the [Timer] class may be combined at will. 359 360 --- 361 import arsd.simpledisplay; 362 363 void main() { 364 auto window = new SimpleWindow(400, 400); 365 auto timer = new Timer(1000, delegate { 366 auto painter = window.draw(); 367 painter.clear(); 368 }); 369 370 window.eventLoop(0); 371 } 372 --- 373 374 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 375 376 $(H3 $(ID topic-os-helpers) OS-specific helpers) 377 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. 378 379 See also: `xwindows.d` from my github. 380 381 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 382 `handleNativeEvent` and `handleNativeGlobalEvent`. 383 384 $(H3 $(ID topic-integration) Integration with other libraries) 385 Integration with a third-party event loop is possible. 386 387 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. 388 389 $(H3 $(ID topic-guis) GUI widgets) 390 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! 391 392 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. 393 394 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.) 395 396 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 397 398 $(H2 Platform-specific tips and tricks) 399 400 Windows_tips: 401 402 You can add icons or manifest files to your exe using a resource file. 403 404 To create a Windows .ico file, use the gimp or something. I'll write a helper 405 program later. 406 407 Create `yourapp.rc`: 408 409 ```rc 410 1 ICON filename.ico 411 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 412 ``` 413 414 And `yourapp.exe.manifest`: 415 416 ```xml 417 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 418 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 419 <assemblyIdentity 420 version="1.0.0.0" 421 processorArchitecture="*" 422 name="CompanyName.ProductName.YourApplication" 423 type="win32" 424 /> 425 <description>Your application description here.</description> 426 <dependency> 427 <dependentAssembly> 428 <assemblyIdentity 429 type="win32" 430 name="Microsoft.Windows.Common-Controls" 431 version="6.0.0.0" 432 processorArchitecture="*" 433 publicKeyToken="6595b64144ccf1df" 434 language="*" 435 /> 436 </dependentAssembly> 437 </dependency> 438 </assembly> 439 ``` 440 441 442 $(H2 $(ID developer-notes) Developer notes) 443 444 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 445 implementation though. 446 447 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 448 suck. If I was rewriting it, I wouldn't do it that way again. 449 450 This file must not have any more required dependencies. If you need bindings, add 451 them right to this file. Once it gets into druntime and is there for a while, remove 452 bindings from here to avoid conflicts (or put them in an appropriate version block 453 so it continues to just work on old dmd), but wait a couple releases before making the 454 transition so this module remains usable with older versions of dmd. 455 456 You may have optional dependencies if needed by putting them in version blocks or 457 template functions. You may also extend the module with other modules with UFCS without 458 actually editing this - that is nice to do if you can. 459 460 Try to make functions work the same way across operating systems. I typically make 461 it thinly wrap Windows, then emulate that on Linux. 462 463 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 464 Phobos! So try to avoid it. 465 466 See more comments throughout the source. 467 468 I realize this file is fairly large, but over half that is just bindings at the bottom 469 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 470 to understand. I suggest you jump around the source by looking for a particular 471 declaration you're interested in, like `class SimpleWindow` using your editor's search 472 function, then look at one piece at a time. 473 474 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 475 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode. I go by 476 Destructionator or adam_d_ruppe, depending on which computer I'm logged into. 477 478 I live in the eastern United States, so I will most likely not be around at night in 479 that US east timezone. 480 481 License: Copyright Adam D. Ruppe, 2011-2020. Released under the Boost Software License. 482 483 Building documentation: You may wish to use the `arsd.ddoc` file from my github with 484 building the documentation for simpledisplay yourself. It will give it a bit more style. 485 Simply download the arsd.ddoc file and add it to your compile command when building docs. 486 `dmd -c simpledisplay.d color.d -D arsd.ddoc` 487 +/ 488 module arsd.simpledisplay; 489 490 // FIXME: tetris demo 491 // FIXME: space invaders demo 492 // FIXME: asteroids demo 493 494 /++ $(ID Pong-example) 495 $(H3 Pong) 496 497 This program creates a little Pong-like game. Player one is controlled 498 with the keyboard. Player two is controlled with the mouse. It demos 499 the pulse timer, event handling, and some basic drawing. 500 +/ 501 version(demos) 502 unittest { 503 // dmd example.d simpledisplay.d color.d 504 import arsd.simpledisplay; 505 506 enum paddleMovementSpeed = 8; 507 enum paddleHeight = 48; 508 509 void main() { 510 auto window = new SimpleWindow(600, 400, "Pong game!"); 511 512 int playerOnePosition, playerTwoPosition; 513 int playerOneMovement, playerTwoMovement; 514 int playerOneScore, playerTwoScore; 515 516 int ballX, ballY; 517 int ballDx, ballDy; 518 519 void serve() { 520 import std.random; 521 522 ballX = window.width / 2; 523 ballY = window.height / 2; 524 ballDx = uniform(-4, 4) * 3; 525 ballDy = uniform(-4, 4) * 3; 526 if(ballDx == 0) 527 ballDx = uniform(0, 2) == 0 ? 3 : -3; 528 } 529 530 serve(); 531 532 window.eventLoop(50, // set a 50 ms timer pulls 533 // This runs once per timer pulse 534 delegate () { 535 auto painter = window.draw(); 536 537 painter.clear(); 538 539 // Update everyone's motion 540 playerOnePosition += playerOneMovement; 541 playerTwoPosition += playerTwoMovement; 542 543 ballX += ballDx; 544 ballY += ballDy; 545 546 // Bounce off the top and bottom edges of the window 547 if(ballY + 7 >= window.height) 548 ballDy = -ballDy; 549 if(ballY - 8 <= 0) 550 ballDy = -ballDy; 551 552 // Bounce off the paddle, if it is in position 553 if(ballX - 8 <= 16) { 554 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 555 ballDx = -ballDx + 1; // add some speed to keep it interesting 556 ballDy += playerOneMovement; // and y movement based on your controls too 557 ballX = 24; // move it past the paddle so it doesn't wiggle inside 558 } else { 559 // Missed it 560 playerTwoScore ++; 561 serve(); 562 } 563 } 564 565 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 566 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 567 ballDx = -ballDx - 1; 568 ballDy += playerTwoMovement; 569 ballX = window.width - 24; 570 } else { 571 // Missed it 572 playerOneScore ++; 573 serve(); 574 } 575 } 576 577 // Draw the paddles 578 painter.outlineColor = Color.black; 579 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 580 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 581 582 // Draw the ball 583 painter.fillColor = Color.red; 584 painter.outlineColor = Color.yellow; 585 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 586 587 // Draw the score 588 painter.outlineColor = Color.blue; 589 import std.conv; 590 painter.drawText(Point(64, 4), to!string(playerOneScore)); 591 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 592 593 }, 594 delegate (KeyEvent event) { 595 // Player 1's controls are the arrow keys on the keyboard 596 if(event.key == Key.Down) 597 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 598 if(event.key == Key.Up) 599 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 600 601 }, 602 delegate (MouseEvent event) { 603 // Player 2's controls are mouse movement while the left button is held down 604 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 605 if(event.dy > 0) 606 playerTwoMovement = paddleMovementSpeed; 607 else if(event.dy < 0) 608 playerTwoMovement = -paddleMovementSpeed; 609 } else { 610 playerTwoMovement = 0; 611 } 612 } 613 ); 614 } 615 } 616 617 /++ $(ID example-minesweeper) 618 619 This minesweeper demo shows how we can implement another classic 620 game with simpledisplay and shows some mouse input and basic output 621 code. 622 +/ 623 version(demos) 624 unittest { 625 import arsd.simpledisplay; 626 627 enum GameSquare { 628 mine = 0, 629 clear, 630 m1, m2, m3, m4, m5, m6, m7, m8 631 } 632 633 enum UserSquare { 634 unknown, 635 revealed, 636 flagged, 637 questioned 638 } 639 640 enum GameState { 641 inProgress, 642 lose, 643 win 644 } 645 646 GameSquare[] board; 647 UserSquare[] userState; 648 GameState gameState; 649 int boardWidth; 650 int boardHeight; 651 652 bool isMine(int x, int y) { 653 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 654 return false; 655 return board[y * boardWidth + x] == GameSquare.mine; 656 } 657 658 GameState reveal(int x, int y) { 659 if(board[y * boardWidth + x] == GameSquare.clear) { 660 floodFill(userState, boardWidth, boardHeight, 661 UserSquare.unknown, UserSquare.revealed, 662 x, y, 663 (x, y) { 664 if(board[y * boardWidth + x] == GameSquare.clear) 665 return true; 666 else { 667 userState[y * boardWidth + x] = UserSquare.revealed; 668 return false; 669 } 670 }); 671 } else { 672 userState[y * boardWidth + x] = UserSquare.revealed; 673 if(isMine(x, y)) 674 return GameState.lose; 675 } 676 677 foreach(state; userState) { 678 if(state == UserSquare.unknown || state == UserSquare.questioned) 679 return GameState.inProgress; 680 } 681 682 return GameState.win; 683 } 684 685 void initializeBoard(int width, int height, int numberOfMines) { 686 boardWidth = width; 687 boardHeight = height; 688 board.length = width * height; 689 690 userState.length = width * height; 691 userState[] = UserSquare.unknown; 692 693 import std.algorithm, std.random, std.range; 694 695 board[] = GameSquare.clear; 696 697 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 698 board[minePosition] = GameSquare.mine; 699 700 int x; 701 int y; 702 foreach(idx, ref square; board) { 703 if(square == GameSquare.clear) { 704 int danger = 0; 705 danger += isMine(x-1, y-1)?1:0; 706 danger += isMine(x-1, y)?1:0; 707 danger += isMine(x-1, y+1)?1:0; 708 danger += isMine(x, y-1)?1:0; 709 danger += isMine(x, y+1)?1:0; 710 danger += isMine(x+1, y-1)?1:0; 711 danger += isMine(x+1, y)?1:0; 712 danger += isMine(x+1, y+1)?1:0; 713 714 square = cast(GameSquare) (danger + 1); 715 } 716 717 x++; 718 if(x == width) { 719 x = 0; 720 y++; 721 } 722 } 723 } 724 725 void redraw(SimpleWindow window) { 726 import std.conv; 727 728 auto painter = window.draw(); 729 730 painter.clear(); 731 732 final switch(gameState) with(GameState) { 733 case inProgress: 734 break; 735 case win: 736 painter.fillColor = Color.green; 737 painter.drawRectangle(Point(0, 0), window.width, window.height); 738 return; 739 case lose: 740 painter.fillColor = Color.red; 741 painter.drawRectangle(Point(0, 0), window.width, window.height); 742 return; 743 } 744 745 int x = 0; 746 int y = 0; 747 748 foreach(idx, square; board) { 749 auto state = userState[idx]; 750 751 final switch(state) with(UserSquare) { 752 case unknown: 753 painter.outlineColor = Color.black; 754 painter.fillColor = Color(128,128,128); 755 756 painter.drawRectangle( 757 Point(x * 20, y * 20), 758 20, 20 759 ); 760 break; 761 case revealed: 762 if(square == GameSquare.clear) { 763 painter.outlineColor = Color.white; 764 painter.fillColor = Color.white; 765 766 painter.drawRectangle( 767 Point(x * 20, y * 20), 768 20, 20 769 ); 770 } else { 771 painter.outlineColor = Color.black; 772 painter.fillColor = Color.white; 773 774 painter.drawText( 775 Point(x * 20, y * 20), 776 to!string(square)[1..2], 777 Point(x * 20 + 20, y * 20 + 20), 778 TextAlignment.Center | TextAlignment.VerticalCenter); 779 } 780 break; 781 case flagged: 782 painter.outlineColor = Color.black; 783 painter.fillColor = Color.red; 784 painter.drawRectangle( 785 Point(x * 20, y * 20), 786 20, 20 787 ); 788 break; 789 case questioned: 790 painter.outlineColor = Color.black; 791 painter.fillColor = Color.yellow; 792 painter.drawRectangle( 793 Point(x * 20, y * 20), 794 20, 20 795 ); 796 break; 797 } 798 799 x++; 800 if(x == boardWidth) { 801 x = 0; 802 y++; 803 } 804 } 805 806 } 807 808 void main() { 809 auto window = new SimpleWindow(200, 200); 810 811 initializeBoard(10, 10, 10); 812 813 redraw(window); 814 window.eventLoop(0, 815 delegate (MouseEvent me) { 816 if(me.type != MouseEventType.buttonPressed) 817 return; 818 auto x = me.x / 20; 819 auto y = me.y / 20; 820 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 821 if(me.button == MouseButton.left) { 822 gameState = reveal(x, y); 823 } else { 824 userState[y*boardWidth+x] = UserSquare.flagged; 825 } 826 redraw(window); 827 } 828 } 829 ); 830 } 831 } 832 833 /* 834 version(OSX) { 835 version=without_opengl; 836 version=allow_unimplemented_features; 837 version=OSXCocoa; 838 pragma(linkerDirective, "-framework Cocoa"); 839 } 840 */ 841 842 version(without_opengl) { 843 enum SdpyIsUsingIVGLBinds = false; 844 } else /*version(Posix)*/ { 845 static if (__traits(compiles, (){import iv.glbinds;})) { 846 enum SdpyIsUsingIVGLBinds = true; 847 public import iv.glbinds; 848 //pragma(msg, "SDPY: using iv.glbinds"); 849 } else { 850 enum SdpyIsUsingIVGLBinds = false; 851 } 852 //} else { 853 // enum SdpyIsUsingIVGLBinds = false; 854 } 855 856 857 version(Windows) { 858 //import core.sys.windows.windows; 859 import core.sys.windows.winnls; 860 import core.sys.windows.windef; 861 import core.sys.windows.basetyps; 862 import core.sys.windows.winbase; 863 import core.sys.windows.winuser; 864 import core.sys.windows.shellapi; 865 import core.sys.windows.wingdi; 866 static import gdi = core.sys.windows.wingdi; // so i 867 868 pragma(lib, "gdi32"); 869 pragma(lib, "user32"); 870 } else version (linux) { 871 //k8: this is hack for rdmd. sorry. 872 static import core.sys.linux.epoll; 873 static import core.sys.linux.timerfd; 874 } 875 876 877 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 878 879 // http://wiki.dlang.org/Simpledisplay.d 880 881 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 882 883 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 884 // but can i control the scroll lock led 885 886 887 // Note: if you are using Image on X, you might want to do: 888 /* 889 static if(UsingSimpledisplayX11) { 890 if(!Image.impl.xshmAvailable) { 891 // the images will use the slower XPutImage, you might 892 // want to consider an alternative method to get better speed 893 } 894 } 895 896 If the shared memory extension is available though, simpledisplay uses it 897 for a significant speed boost whenever you draw large Images. 898 */ 899 900 // 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. 901 902 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 903 904 /* 905 Biggest FIXME: 906 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 907 908 clean up opengl contexts when their windows close 909 910 fix resizing the bitmaps/pixmaps 911 */ 912 913 // BTW on Windows: 914 // -L/SUBSYSTEM:WINDOWS:5.0 915 // to dmd will make a nice windows binary w/o a console if you want that. 916 917 /* 918 Stuff to add: 919 920 use multibyte functions everywhere we can 921 922 OpenGL windows 923 more event stuff 924 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 925 926 927 resizeEvent 928 and make the windows non-resizable by default, 929 or perhaps stretched (if I can find something in X like StretchBlt) 930 931 take a screenshot function! 932 933 Pens and brushes? 934 Maybe a global event loop? 935 936 Mouse deltas 937 Key items 938 */ 939 940 /* 941 From MSDN: 942 943 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 944 945 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. 946 947 */ 948 949 version(linux) { 950 version = X11; 951 version(without_libnotify) { 952 // we cool 953 } 954 else 955 version = libnotify; 956 } 957 958 version(libnotify) { 959 pragma(lib, "dl"); 960 import core.sys.posix.dlfcn; 961 962 void delegate()[int] libnotify_action_delegates; 963 int libnotify_action_delegates_count; 964 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 965 auto idx = cast(int) user_data; 966 if(auto dgptr = idx in libnotify_action_delegates) { 967 (*dgptr)(); 968 libnotify_action_delegates.remove(idx); 969 } 970 } 971 972 struct C_DynamicLibrary { 973 void* handle; 974 this(string name) { 975 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 976 if(handle is null) 977 throw new Exception("dlopen"); 978 } 979 980 void close() { 981 dlclose(handle); 982 } 983 984 ~this() { 985 // close 986 } 987 988 // FIXME: this looks up by name every time.... 989 template call(string func, Ret, Args...) { 990 extern(C) Ret function(Args) fptr; 991 typeof(fptr) call() { 992 fptr = cast(typeof(fptr)) dlsym(handle, func); 993 return fptr; 994 } 995 } 996 } 997 998 C_DynamicLibrary* libnotify; 999 } 1000 1001 version(OSX) { 1002 version(OSXCocoa) {} 1003 else { version = X11; } 1004 } 1005 //version = OSXCocoa; // this was written by KennyTM 1006 version(FreeBSD) 1007 version = X11; 1008 version(Solaris) 1009 version = X11; 1010 1011 1012 void featureNotImplemented()() { 1013 version(allow_unimplemented_features) 1014 throw new NotYetImplementedException(); 1015 else 1016 static assert(0); 1017 } 1018 1019 // these are so the static asserts don't trigger unless you want to 1020 // add support to it for an OS 1021 version(Windows) 1022 version = with_timer; 1023 version(linux) 1024 version = with_timer; 1025 1026 version(with_timer) 1027 enum bool SimpledisplayTimerAvailable = true; 1028 else 1029 enum bool SimpledisplayTimerAvailable = false; 1030 1031 /// 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. 1032 version(Windows) 1033 enum bool UsingSimpledisplayWindows = true; 1034 else 1035 enum bool UsingSimpledisplayWindows = false; 1036 1037 /// 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. 1038 version(X11) 1039 enum bool UsingSimpledisplayX11 = true; 1040 else 1041 enum bool UsingSimpledisplayX11 = false; 1042 1043 /// 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. 1044 version(OSXCocoa) 1045 enum bool UsingSimpledisplayCocoa = true; 1046 else 1047 enum bool UsingSimpledisplayCocoa = false; 1048 1049 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1050 version(Windows) 1051 enum multipleWindowsSupported = true; 1052 else version(X11) 1053 enum multipleWindowsSupported = true; 1054 else version(OSXCocoa) 1055 enum multipleWindowsSupported = true; 1056 else 1057 static assert(0); 1058 1059 version(without_opengl) 1060 enum bool OpenGlEnabled = false; 1061 else 1062 enum bool OpenGlEnabled = true; 1063 1064 1065 /++ 1066 After selecting a type from [WindowTypes], you may further customize 1067 its behavior by setting one or more of these flags. 1068 1069 1070 The different window types have different meanings of `normal`. If the 1071 window type already is a good match for what you want to do, you should 1072 just use [WindowFlags.normal], the default, which will do the right thing 1073 for your users. 1074 1075 The window flags will not always be honored by the operating system 1076 and window managers; they are hints, not commands. 1077 +/ 1078 enum WindowFlags : int { 1079 normal = 0, /// 1080 skipTaskbar = 1, /// 1081 alwaysOnTop = 2, /// 1082 alwaysOnBottom = 4, /// 1083 cannotBeActivated = 8, /// 1084 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. 1085 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. 1086 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1087 } 1088 1089 /++ 1090 When creating a window, you can pass a type to SimpleWindow's constructor, 1091 then further customize the window by changing `WindowFlags`. 1092 1093 1094 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1095 use. The others are there to build a foundation for a higher level GUI toolkit, 1096 but are themselves not as high level as you might think from their names. 1097 1098 This list is based on the EMWH spec for X11. 1099 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1100 +/ 1101 enum WindowTypes : int { 1102 /// An ordinary application window. 1103 normal, 1104 /// 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. 1105 undecorated, 1106 /// 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. 1107 eventOnly, 1108 /// A drop down menu, such as from a menu bar 1109 dropdownMenu, 1110 /// A popup menu, such as from a right click 1111 popupMenu, 1112 /// A popup bubble notification 1113 notification, 1114 /* 1115 menu, /// a tearable menu bar 1116 splashScreen, /// a loading splash screen for your application 1117 tooltip, /// A tiny window showing temporary help text or something. 1118 comboBoxDropdown, 1119 dialog, 1120 toolbar 1121 */ 1122 /// a child nested inside the parent. You must pass a parent window to the ctor 1123 nestedChild, 1124 } 1125 1126 1127 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1128 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1129 private __gshared char* sdpyWindowClassStr = null; 1130 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1131 1132 /** 1133 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1134 You may want to change context version if you want to use advanced shaders or 1135 other modern OpenGL techinques. This setting doesn't affect already created 1136 windows. You may use version 2.1 as your default, which should be supported 1137 by any box since 2006, so seems to be a reasonable choice. 1138 1139 Note that by default version is set to `0`, which forces SimpleDisplay to use 1140 old context creation code without any version specified. This is the safest 1141 way to init OpenGL, but it may not give you access to advanced features. 1142 1143 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1144 */ 1145 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1146 1147 /** 1148 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1149 pipeline functions, and without "compatible" mode you won't be able to use 1150 your old non-shader-based code with such contexts. By default SimpleDisplay 1151 creates compatible context, so you can gradually upgrade your OpenGL code if 1152 you want to (or leave it as is, as it should "just work"). 1153 */ 1154 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1155 1156 /** 1157 Set to `true` to allow creating OpenGL context with lower version than requested 1158 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1159 `openGLContextFallbackActivated()` will return `true`. 1160 */ 1161 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1162 1163 /** 1164 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1165 */ 1166 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1167 1168 1169 /** 1170 Set window class name for all following `new SimpleWindow()` calls. 1171 1172 WARNING! For Windows, you should set your class name before creating any 1173 window, and NEVER change it after that! 1174 */ 1175 void sdpyWindowClass (const(char)[] v) { 1176 import core.stdc.stdlib : realloc; 1177 if (v.length == 0) v = "SimpleDisplayWindow"; 1178 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1179 if (sdpyWindowClassStr is null) return; // oops 1180 sdpyWindowClassStr[0..v.length+1] = 0; 1181 sdpyWindowClassStr[0..v.length] = v[]; 1182 } 1183 1184 /** 1185 Get current window class name. 1186 */ 1187 string sdpyWindowClass () { 1188 if (sdpyWindowClassStr is null) return null; 1189 foreach (immutable idx; 0..size_t.max-1) { 1190 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1191 } 1192 return null; 1193 } 1194 1195 /++ 1196 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. 1197 +/ 1198 float[2] getDpi() { 1199 float[2] dpi; 1200 version(Windows) { 1201 HDC screen = GetDC(null); 1202 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1203 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1204 } else version(X11) { 1205 auto display = XDisplayConnection.get; 1206 auto screen = DefaultScreen(display); 1207 1208 void fallback() { 1209 // 25.4 millimeters in an inch... 1210 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1211 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1212 } 1213 1214 char* resourceString = XResourceManagerString(display); 1215 XrmInitialize(); 1216 1217 auto db = XrmGetStringDatabase(resourceString); 1218 1219 if (resourceString) { 1220 XrmValue value; 1221 char* type; 1222 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1223 if (value.addr) { 1224 import core.stdc.stdlib; 1225 dpi[0] = atof(cast(char*) value.addr); 1226 dpi[1] = dpi[0]; 1227 } else { 1228 fallback(); 1229 } 1230 } else { 1231 fallback(); 1232 } 1233 } else { 1234 fallback(); 1235 } 1236 } 1237 1238 return dpi; 1239 } 1240 1241 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width, int height) { 1242 throw new Exception("not implemented"); 1243 version(none) { 1244 version(X11) { 1245 auto display = XDisplayConnection.get; 1246 auto image = XGetImage(display, handle, 0, 0, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ZPixmap); 1247 1248 // https://github.com/adamdruppe/arsd/issues/98 1249 1250 // FIXME: copy that shit 1251 1252 XDestroyImage(image); 1253 } else version(Windows) { 1254 // I just need to BitBlt that shit... BUT WAIT IT IS ALREADY IN A DIB!!!!!!! 1255 1256 } else featureNotImplemented(); 1257 1258 return null; 1259 } 1260 } 1261 1262 /++ 1263 The flagship window class. 1264 1265 1266 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1267 out of more advanced or complex features of the underlying windowing system. 1268 1269 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1270 and get a suitable window to work with. 1271 1272 From there, you can opt into additional features, like custom resizability and OpenGL support 1273 with the next two constructor arguments. Or, if you need even more, you can set a window type 1274 and customization flags with the final two constructor arguments. 1275 1276 If none of that works for you, you can also create a window using native function calls, then 1277 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1278 though, if you do this, managing the window is still your own responsibility! Notably, you 1279 will need to destroy it yourself. 1280 +/ 1281 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1282 1283 /// Be warned: this can be a very slow operation 1284 /// FIXME NOT IMPLEMENTED 1285 TrueColorImage takeScreenshot() { 1286 version(Windows) 1287 return trueColorImageFromNativeHandle(impl.hwnd, width, height); 1288 else version(OSXCocoa) 1289 throw new NotYetImplementedException(); 1290 else 1291 return trueColorImageFromNativeHandle(impl.window, width, height); 1292 } 1293 1294 version(X11) { 1295 void recreateAfterDisconnect() { 1296 if(!stateDiscarded) return; 1297 1298 if(_parent !is null && _parent.stateDiscarded) 1299 _parent.recreateAfterDisconnect(); 1300 1301 bool wasHidden = hidden; 1302 1303 activeScreenPainter = null; // should already be done but just to confirm 1304 1305 impl.createWindow(_width, _height, _title, openglMode, _parent); 1306 1307 if(recreateAdditionalConnectionState) 1308 recreateAdditionalConnectionState(); 1309 1310 hidden = wasHidden; 1311 stateDiscarded = false; 1312 } 1313 1314 bool stateDiscarded; 1315 void discardConnectionState() { 1316 if(XDisplayConnection.display) 1317 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 1318 if(discardAdditionalConnectionState) 1319 discardAdditionalConnectionState(); 1320 stateDiscarded = true; 1321 } 1322 1323 void delegate() discardAdditionalConnectionState; 1324 void delegate() recreateAdditionalConnectionState; 1325 } 1326 1327 1328 SimpleWindow _parent; 1329 bool beingOpenKeepsAppOpen = true; 1330 /++ 1331 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. 1332 1333 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 1334 1335 Params: 1336 1337 width = the width of the window's client area, in pixels 1338 height = the height of the window's client area, in pixels 1339 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title] property. 1340 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 1341 resizable = [Resizability] has three options: 1342 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 1343 $(P `fixedSize` will not allow the user to resize the window.) 1344 $(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.) 1345 windowType = The type of window you want to make. 1346 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. 1347 parent = the parent window, if applicable 1348 +/ 1349 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) { 1350 claimGuiThread(); 1351 version(sdpy_thread_checks) assert(thisIsGuiThread); 1352 this._width = width; 1353 this._height = height; 1354 this.openglMode = opengl; 1355 this.resizability = resizable; 1356 this.windowType = windowType; 1357 this.customizationFlags = customizationFlags; 1358 this._title = (title is null ? "D Application" : title); 1359 this._parent = parent; 1360 impl.createWindow(width, height, this._title, opengl, parent); 1361 1362 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild) 1363 beingOpenKeepsAppOpen = false; 1364 } 1365 1366 /// Same as above, except using the `Size` struct instead of separate width and height. 1367 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 1368 this(size.width, size.height, title, opengl, resizable); 1369 } 1370 1371 1372 /++ 1373 Creates a window based on the given [Image]. It's client area 1374 width and height is equal to the image. (A window's client area 1375 is the drawable space inside; it excludes the title bar, etc.) 1376 1377 Windows based on images will not be resizable and do not use OpenGL. 1378 1379 It will draw the image in upon creation, but this will be overwritten 1380 upon any draws, including the initial window visible event. 1381 1382 You probably do not want to use this and it may be removed from 1383 the library eventually, or I might change it to be a "permanent" 1384 background image; one that is automatically drawn on it before any 1385 other drawing event. idk. 1386 +/ 1387 this(Image image, string title = null) { 1388 this(image.width, image.height, title); 1389 this.image = image; 1390 } 1391 1392 /++ 1393 Wraps a native window handle with very little additional processing - notably no destruction 1394 this is incomplete so don't use it for much right now. The purpose of this is to make native 1395 windows created through the low level API (so you can use platform-specific options and 1396 other details SimpleWindow does not expose) available to the event loop wrappers. 1397 +/ 1398 this(NativeWindowHandle nativeWindow) { 1399 version(Windows) 1400 impl.hwnd = nativeWindow; 1401 else version(X11) { 1402 impl.window = nativeWindow; 1403 display = XDisplayConnection.get(); // get initial display to not segfault 1404 } else version(OSXCocoa) 1405 throw new NotYetImplementedException(); 1406 else featureNotImplemented(); 1407 // FIXME: set the size correctly 1408 _width = 1; 1409 _height = 1; 1410 nativeMapping[nativeWindow] = this; 1411 1412 beingOpenKeepsAppOpen = false; 1413 1414 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 1415 _suppressDestruction = true; // so it doesn't try to close 1416 } 1417 1418 /// Experimental, do not use yet 1419 /++ 1420 Grabs exclusive input from the user until you release it with 1421 [releaseInputGrab]. 1422 1423 1424 Note: it is extremely rude to do this without good reason. 1425 Reasons may include doing some kind of mouse drag operation 1426 or popping up a temporary menu that should get events and will 1427 be dismissed at ease by the user clicking away. 1428 1429 Params: 1430 keyboard = do you want to grab keyboard input? 1431 mouse = grab mouse input? 1432 confine = confine the mouse cursor to inside this window? 1433 +/ 1434 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 1435 static if(UsingSimpledisplayX11) { 1436 XSync(XDisplayConnection.get, 0); 1437 if(keyboard) 1438 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 1439 if(mouse) { 1440 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 1441 EventMask.PointerMotionMask // FIXME: not efficient 1442 | EventMask.ButtonPressMask 1443 | EventMask.ButtonReleaseMask 1444 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 1445 ) 1446 { 1447 XSync(XDisplayConnection.get, 0); 1448 import core.stdc.stdio; 1449 printf("Grab input failed %d\n", res); 1450 //throw new Exception("Grab input failed"); 1451 } else { 1452 // cool 1453 } 1454 } 1455 1456 } else version(Windows) { 1457 // FIXME: keyboard? 1458 SetCapture(impl.hwnd); 1459 if(confine) { 1460 RECT rcClip; 1461 //RECT rcOldClip; 1462 //GetClipCursor(&rcOldClip); 1463 GetWindowRect(hwnd, &rcClip); 1464 ClipCursor(&rcClip); 1465 } 1466 } else version(OSXCocoa) { 1467 throw new NotYetImplementedException(); 1468 } else static assert(0); 1469 } 1470 1471 /++ 1472 Releases the grab acquired by [grabInput]. 1473 +/ 1474 void releaseInputGrab() { 1475 static if(UsingSimpledisplayX11) { 1476 XUngrabPointer(XDisplayConnection.get, CurrentTime); 1477 } else version(Windows) { 1478 ReleaseCapture(); 1479 ClipCursor(null); 1480 } else version(OSXCocoa) { 1481 throw new NotYetImplementedException(); 1482 } else static assert(0); 1483 } 1484 1485 /++ 1486 Sets the input focus to this window. 1487 1488 You shouldn't call this very often - please let the user control the input focus. 1489 +/ 1490 void focus() { 1491 static if(UsingSimpledisplayX11) { 1492 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 1493 } else version(Windows) { 1494 SetFocus(this.impl.hwnd); 1495 } else version(OSXCocoa) { 1496 throw new NotYetImplementedException(); 1497 } else static assert(0); 1498 } 1499 1500 /++ 1501 Requests attention from the user for this window. 1502 1503 1504 The typical result of this function is to change the color 1505 of the taskbar icon, though it may be tweaked on specific 1506 platforms. 1507 1508 It is meant to unobtrusively tell the user that something 1509 relevant to them happened in the background and they should 1510 check the window when they get a chance. Upon receiving the 1511 keyboard focus, the window will automatically return to its 1512 natural state. 1513 1514 If the window already has the keyboard focus, this function 1515 may do nothing, because the user is presumed to already be 1516 giving the window attention. 1517 1518 Implementation_note: 1519 1520 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 1521 atom on X11 and the FlashWindow function on Windows. 1522 +/ 1523 void requestAttention() { 1524 if(_focused) 1525 return; 1526 1527 version(Windows) { 1528 FLASHWINFO info; 1529 info.cbSize = info.sizeof; 1530 info.hwnd = impl.hwnd; 1531 info.dwFlags = FLASHW_TRAY; 1532 info.uCount = 1; 1533 1534 FlashWindowEx(&info); 1535 1536 } else version(X11) { 1537 demandingAttention = true; 1538 demandAttention(this, true); 1539 } else version(OSXCocoa) { 1540 throw new NotYetImplementedException(); 1541 } else static assert(0); 1542 } 1543 1544 private bool _focused; 1545 1546 version(X11) private bool demandingAttention; 1547 1548 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 1549 /// You'll have to call `close()` manually if you set this delegate. 1550 void delegate () closeQuery; 1551 1552 /// This will be called when window visibility was changed. 1553 void delegate (bool becomesVisible) visibilityChanged; 1554 1555 /// This will be called when window becomes visible for the first time. 1556 /// You can do OpenGL initialization here. Note that in X11 you can't call 1557 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 1558 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 1559 private bool _visibleForTheFirstTimeCalled; 1560 void delegate () visibleForTheFirstTime; 1561 1562 /// Returns true if the window has been closed. 1563 final @property bool closed() { return _closed; } 1564 1565 /// Returns true if the window is focused. 1566 final @property bool focused() { return _focused; } 1567 1568 private bool _visible; 1569 /// Returns true if the window is visible (mapped). 1570 final @property bool visible() { return _visible; } 1571 1572 /// Closes the window. If there are no more open windows, the event loop will terminate. 1573 void close() { 1574 if (!_closed) { 1575 if (onClosing !is null) onClosing(); 1576 impl.closeWindow(); 1577 _closed = true; 1578 } 1579 } 1580 1581 /// Alias for `hidden = false` 1582 void show() { 1583 hidden = false; 1584 } 1585 1586 /// Alias for `hidden = true` 1587 void hide() { 1588 hidden = true; 1589 } 1590 1591 /// Hide cursor when it enters the window. 1592 void hideCursor() { 1593 version(OSXCocoa) throw new NotYetImplementedException(); else 1594 if (!_closed) impl.hideCursor(); 1595 } 1596 1597 /// Don't hide cursor when it enters the window. 1598 void showCursor() { 1599 version(OSXCocoa) throw new NotYetImplementedException(); else 1600 if (!_closed) impl.showCursor(); 1601 } 1602 1603 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 1604 * 1605 * Currently only supported on X11, so Windows implementation will return `false`. 1606 * 1607 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 1608 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 1609 * receive "mouse moved here" event. 1610 */ 1611 bool warpMouse (int x, int y) { 1612 version(X11) { 1613 if (!_closed) { impl.warpMouse(x, y); return true; } 1614 } 1615 return false; 1616 } 1617 1618 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 1619 void sendDummyEvent () { 1620 version(X11) { 1621 if (!_closed) { impl.sendDummyEvent(); } 1622 } 1623 } 1624 1625 /// Set window minimal size. 1626 void setMinSize (int minwidth, int minheight) { 1627 version(OSXCocoa) throw new NotYetImplementedException(); else 1628 if (!_closed) impl.setMinSize(minwidth, minheight); 1629 } 1630 1631 /// Set window maximal size. 1632 void setMaxSize (int maxwidth, int maxheight) { 1633 version(OSXCocoa) throw new NotYetImplementedException(); else 1634 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 1635 } 1636 1637 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 1638 /// Currently only supported on X11. 1639 void setResizeGranularity (int granx, int grany) { 1640 version(OSXCocoa) throw new NotYetImplementedException(); else 1641 if (!_closed) impl.setResizeGranularity(granx, grany); 1642 } 1643 1644 /// Move window. 1645 void move(int x, int y) { 1646 version(OSXCocoa) throw new NotYetImplementedException(); else 1647 if (!_closed) impl.move(x, y); 1648 } 1649 1650 /// ditto 1651 void move(Point p) { 1652 version(OSXCocoa) throw new NotYetImplementedException(); else 1653 if (!_closed) impl.move(p.x, p.y); 1654 } 1655 1656 /++ 1657 Resize window. 1658 1659 Note that the width and height of the window are NOT instantly 1660 updated - it waits for the window manager to approve the resize 1661 request, which means you must return to the event loop before the 1662 width and height are actually changed. 1663 +/ 1664 void resize(int w, int h) { 1665 version(OSXCocoa) throw new NotYetImplementedException(); else 1666 if (!_closed) impl.resize(w, h); 1667 } 1668 1669 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 1670 void moveResize (int x, int y, int w, int h) { 1671 version(OSXCocoa) throw new NotYetImplementedException(); else 1672 if (!_closed) impl.moveResize(x, y, w, h); 1673 } 1674 1675 private bool _hidden; 1676 1677 /// Returns true if the window is hidden. 1678 final @property bool hidden() { 1679 return _hidden; 1680 } 1681 1682 /// Shows or hides the window based on the bool argument. 1683 final @property void hidden(bool b) { 1684 _hidden = b; 1685 version(Windows) { 1686 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 1687 } else version(X11) { 1688 if(b) 1689 //XUnmapWindow(impl.display, impl.window); 1690 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 1691 else 1692 XMapWindow(impl.display, impl.window); 1693 } else version(OSXCocoa) { 1694 throw new NotYetImplementedException(); 1695 } else static assert(0); 1696 } 1697 1698 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 1699 void opacity(double opacity) @property 1700 in { 1701 assert(opacity >= 0 && opacity <= 1); 1702 } body { 1703 version (Windows) { 1704 impl.setOpacity(cast(ubyte)(255 * opacity)); 1705 } else version (X11) { 1706 impl.setOpacity(cast(uint)(uint.max * opacity)); 1707 } else throw new NotYetImplementedException(); 1708 } 1709 1710 /++ 1711 Sets your event handlers, without entering the event loop. Useful if you 1712 have multiple windows - set the handlers on each window, then only do eventLoop on your main window. 1713 +/ 1714 void setEventHandlers(T...)(T eventHandlers) { 1715 // FIXME: add more events 1716 foreach(handler; eventHandlers) { 1717 static if(__traits(compiles, handleKeyEvent = handler)) { 1718 handleKeyEvent = handler; 1719 } else static if(__traits(compiles, handleCharEvent = handler)) { 1720 handleCharEvent = handler; 1721 } else static if(__traits(compiles, handlePulse = handler)) { 1722 handlePulse = handler; 1723 } else static if(__traits(compiles, handleMouseEvent = handler)) { 1724 handleMouseEvent = handler; 1725 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 1726 } 1727 } 1728 1729 /// The event loop automatically returns when the window is closed 1730 /// pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 1731 /// pulse timer is created. The event loop will block until an event 1732 /// arrives or the pulse timer goes off. 1733 final int eventLoop(T...)( 1734 long pulseTimeout, /// set to zero if you don't want a pulse. 1735 T eventHandlers) /// delegate list like std.concurrency.receive 1736 { 1737 setEventHandlers(eventHandlers); 1738 1739 version(with_eventloop) { 1740 // delegates event loop to my other module 1741 version(X11) 1742 XFlush(display); 1743 1744 import arsd.eventloop; 1745 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 1746 scope(exit) clearInterval(handle); 1747 1748 loop(); 1749 return 0; 1750 } else version(OSXCocoa) { 1751 // FIXME 1752 if (handlePulse !is null && pulseTimeout != 0) { 1753 timer = scheduledTimer(pulseTimeout*1e-3, 1754 view, sel_registerName("simpledisplay_pulse"), 1755 null, true); 1756 } 1757 1758 setNeedsDisplay(view, true); 1759 run(NSApp); 1760 return 0; 1761 } else { 1762 EventLoop el = EventLoop(pulseTimeout, handlePulse); 1763 return el.run(); 1764 } 1765 } 1766 1767 /++ 1768 This lets you draw on the window (or its backing buffer) using basic 1769 2D primitives. 1770 1771 Be sure to call this in a limited scope because your changes will not 1772 actually appear on the window until ScreenPainter's destructor runs. 1773 1774 Returns: an instance of [ScreenPainter], which has the drawing methods 1775 on it to draw on this window. 1776 +/ 1777 ScreenPainter draw() { 1778 return impl.getPainter(); 1779 } 1780 1781 // This is here to implement the interface we use for various native handlers. 1782 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 1783 1784 // maps native window handles to SimpleWindow instances, if there are any 1785 // you shouldn't need this, but it is public in case you do in a native event handler or something 1786 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 1787 1788 /// Width of the window's drawable client area, in pixels. 1789 @scriptable 1790 final @property int width() { return _width; } 1791 1792 /// Height of the window's drawable client area, in pixels. 1793 @scriptable 1794 final @property int height() { return _height; } 1795 1796 private int _width; 1797 private int _height; 1798 1799 // HACK: making the best of some copy constructor woes with refcounting 1800 private ScreenPainterImplementation* activeScreenPainter_; 1801 1802 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 1803 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 1804 1805 private OpenGlOptions openglMode; 1806 private Resizability resizability; 1807 private WindowTypes windowType; 1808 private int customizationFlags; 1809 1810 /// `true` if OpenGL was initialized for this window. 1811 @property bool isOpenGL () const pure nothrow @safe @nogc { 1812 version(without_opengl) 1813 return false; 1814 else 1815 return (openglMode == OpenGlOptions.yes); 1816 } 1817 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 1818 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 1819 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 1820 1821 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 1822 /// to call this, as it's not recommended to share window between threads. 1823 void mtLock () { 1824 version(X11) { 1825 XLockDisplay(this.display); 1826 } 1827 } 1828 1829 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 1830 /// to call this, as it's not recommended to share window between threads. 1831 void mtUnlock () { 1832 version(X11) { 1833 XUnlockDisplay(this.display); 1834 } 1835 } 1836 1837 /// Emit a beep to get user's attention. 1838 void beep () { 1839 version(X11) { 1840 XBell(this.display, 100); 1841 } else version(Windows) { 1842 MessageBeep(0xFFFFFFFF); 1843 } 1844 } 1845 1846 1847 1848 version(without_opengl) {} else { 1849 1850 /// 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`. 1851 void delegate() redrawOpenGlScene; 1852 1853 /// This will allow you to change OpenGL vsync state. 1854 final @property void vsync (bool wait) { 1855 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 1856 version(X11) { 1857 setAsCurrentOpenGlContext(); 1858 glxSetVSync(display, impl.window, wait); 1859 } 1860 } 1861 1862 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 1863 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 1864 /// enough without waiting 'em to finish their frame bussiness. 1865 bool useGLFinish = true; 1866 1867 // FIXME: it should schedule it for the end of the current iteration of the event loop... 1868 /// 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. 1869 void redrawOpenGlSceneNow() { 1870 version(X11) if (!this._visible) return; // no need to do this if window is invisible 1871 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 1872 if(redrawOpenGlScene is null) 1873 return; 1874 1875 this.mtLock(); 1876 scope(exit) this.mtUnlock(); 1877 1878 this.setAsCurrentOpenGlContext(); 1879 1880 redrawOpenGlScene(); 1881 1882 this.swapOpenGlBuffers(); 1883 // 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. 1884 if (useGLFinish) glFinish(); 1885 } 1886 1887 1888 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 1889 void setAsCurrentOpenGlContext() { 1890 assert(openglMode == OpenGlOptions.yes); 1891 version(X11) { 1892 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 1893 throw new Exception("glXMakeCurrent"); 1894 } else version(Windows) { 1895 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 1896 if (!wglMakeCurrent(ghDC, ghRC)) 1897 throw new Exception("wglMakeCurrent"); // let windows users suffer too 1898 } 1899 } 1900 1901 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 1902 /// This doesn't throw, returning success flag instead. 1903 bool setAsCurrentOpenGlContextNT() nothrow { 1904 assert(openglMode == OpenGlOptions.yes); 1905 version(X11) { 1906 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 1907 } else version(Windows) { 1908 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 1909 return wglMakeCurrent(ghDC, ghRC) ? true : false; 1910 } 1911 } 1912 1913 /// 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. 1914 /// This doesn't throw, returning success flag instead. 1915 bool releaseCurrentOpenGlContext() nothrow { 1916 assert(openglMode == OpenGlOptions.yes); 1917 version(X11) { 1918 return (glXMakeCurrent(display, 0, null) != 0); 1919 } else version(Windows) { 1920 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 1921 return wglMakeCurrent(ghDC, null) ? true : false; 1922 } 1923 } 1924 1925 /++ 1926 simpledisplay always uses double buffering, usually automatically. This 1927 manually swaps the OpenGL buffers. 1928 1929 1930 You should not need to call this yourself because simpledisplay will do it 1931 for you after calling your `redrawOpenGlScene`. 1932 1933 Remember that this may throw an exception, which you can catch in a multithreaded 1934 application to keep your thread from dying from an unhandled exception. 1935 +/ 1936 void swapOpenGlBuffers() { 1937 assert(openglMode == OpenGlOptions.yes); 1938 version(X11) { 1939 if (!this._visible) return; // no need to do this if window is invisible 1940 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 1941 glXSwapBuffers(display, impl.window); 1942 } else version(Windows) { 1943 SwapBuffers(ghDC); 1944 } 1945 } 1946 } 1947 1948 /++ 1949 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 1950 1951 1952 --- 1953 auto window = new SimpleWindow(100, 100, "First title"); 1954 window.title = "A new title"; 1955 --- 1956 1957 You may call this function at any time. 1958 +/ 1959 @property void title(string title) { 1960 _title = title; 1961 version(OSXCocoa) throw new NotYetImplementedException(); else 1962 impl.setTitle(title); 1963 } 1964 1965 private string _title; 1966 1967 /// Gets the title 1968 @property string title() { 1969 if(_title is null) 1970 _title = getRealTitle(); 1971 return _title; 1972 } 1973 1974 /++ 1975 Get the title as set by the window manager. 1976 May not match what you attempted to set. 1977 +/ 1978 string getRealTitle() { 1979 static if(is(typeof(impl.getTitle()))) 1980 return impl.getTitle(); 1981 else 1982 return null; 1983 } 1984 1985 // don't use this generally it is not yet really released 1986 version(X11) 1987 @property Image secret_icon() { 1988 return secret_icon_inner; 1989 } 1990 private Image secret_icon_inner; 1991 1992 1993 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. 1994 @property void icon(MemoryImage icon) { 1995 auto tci = icon.getAsTrueColorImage(); 1996 version(Windows) { 1997 winIcon = new WindowsIcon(icon); 1998 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 1999 } else version(X11) { 2000 secret_icon_inner = Image.fromMemoryImage(icon); 2001 // FIXME: ensure this is correct 2002 auto display = XDisplayConnection.get; 2003 arch_ulong[] buffer; 2004 buffer ~= icon.width; 2005 buffer ~= icon.height; 2006 foreach(c; tci.imageData.colors) { 2007 arch_ulong b; 2008 b |= c.a << 24; 2009 b |= c.r << 16; 2010 b |= c.g << 8; 2011 b |= c.b; 2012 buffer ~= b; 2013 } 2014 2015 XChangeProperty( 2016 display, 2017 impl.window, 2018 GetAtom!("_NET_WM_ICON", true)(display), 2019 GetAtom!"CARDINAL"(display), 2020 32 /* bits */, 2021 0 /*PropModeReplace*/, 2022 buffer.ptr, 2023 cast(int) buffer.length); 2024 } else version(OSXCocoa) { 2025 throw new NotYetImplementedException(); 2026 } else static assert(0); 2027 } 2028 2029 version(Windows) 2030 private WindowsIcon winIcon; 2031 2032 bool _suppressDestruction; 2033 2034 ~this() { 2035 if(_suppressDestruction) 2036 return; 2037 impl.dispose(); 2038 } 2039 2040 private bool _closed; 2041 2042 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 2043 /* 2044 ScreenPainter drawTransiently() { 2045 return impl.getPainter(); 2046 } 2047 */ 2048 2049 /// Draws an image on the window. This is meant to provide quick look 2050 /// of a static image generated elsewhere. 2051 @property void image(Image i) { 2052 version(Windows) { 2053 BITMAP bm; 2054 HDC hdc = GetDC(hwnd); 2055 HDC hdcMem = CreateCompatibleDC(hdc); 2056 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 2057 2058 GetObject(i.handle, bm.sizeof, &bm); 2059 2060 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 2061 2062 SelectObject(hdcMem, hbmOld); 2063 DeleteDC(hdcMem); 2064 DeleteDC(hwnd); 2065 2066 /* 2067 RECT r; 2068 r.right = i.width; 2069 r.bottom = i.height; 2070 InvalidateRect(hwnd, &r, false); 2071 */ 2072 } else 2073 version(X11) { 2074 if(!destroyed) { 2075 if(i.usingXshm) 2076 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 2077 else 2078 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 2079 } 2080 } else 2081 version(OSXCocoa) { 2082 draw().drawImage(Point(0, 0), i); 2083 setNeedsDisplay(view, true); 2084 } else static assert(0); 2085 } 2086 2087 /++ 2088 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 2089 2090 --- 2091 window.cursor = GenericCursor.Help; 2092 // now the window mouse cursor is set to a generic help 2093 --- 2094 2095 +/ 2096 @property void cursor(MouseCursor cursor) { 2097 version(OSXCocoa) 2098 featureNotImplemented(); 2099 else 2100 if(this.impl.curHidden <= 0) { 2101 static if(UsingSimpledisplayX11) { 2102 auto ch = cursor.cursorHandle; 2103 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 2104 } else version(Windows) { 2105 auto ch = cursor.cursorHandle; 2106 impl.currentCursor = ch; 2107 SetCursor(ch); // redraw without waiting for mouse movement to update 2108 } else featureNotImplemented(); 2109 } 2110 2111 } 2112 2113 /// What follows are the event handlers. These are set automatically 2114 /// by the eventLoop function, but are still public so you can change 2115 /// them later. wasPressed == true means key down. false == key up. 2116 2117 /// Handles a low-level keyboard event. Settable through setEventHandlers. 2118 void delegate(KeyEvent ke) handleKeyEvent; 2119 2120 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 2121 void delegate(dchar c) handleCharEvent; 2122 2123 /// Handles a timer pulse. Settable through setEventHandlers. 2124 void delegate() handlePulse; 2125 2126 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 2127 void delegate(bool) onFocusChange; 2128 2129 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 2130 * Sometimes it is easier to setup the delegate instead of subclassing. */ 2131 void delegate() onClosing; 2132 2133 /** Called when we received destroy notification. At this stage we cannot do much with our window 2134 * (as it is already dead, and it's native handle cannot be used), but we still can do some 2135 * last minute cleanup. */ 2136 void delegate() onDestroyed; 2137 2138 static if (UsingSimpledisplayX11) 2139 /** Called when Expose event comes. See Xlib manual to understand the arguments. 2140 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 2141 * You will probably never need to setup this handler, it is for very low-level stuff. 2142 * 2143 * WARNING! Xlib is multithread-locked when this handles is called! */ 2144 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 2145 2146 //version(Windows) 2147 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 2148 2149 private { 2150 int lastMouseX = int.min; 2151 int lastMouseY = int.min; 2152 void mdx(ref MouseEvent ev) { 2153 if(lastMouseX == int.min || lastMouseY == int.min) { 2154 ev.dx = 0; 2155 ev.dy = 0; 2156 } else { 2157 ev.dx = ev.x - lastMouseX; 2158 ev.dy = ev.y - lastMouseY; 2159 } 2160 2161 lastMouseX = ev.x; 2162 lastMouseY = ev.y; 2163 } 2164 } 2165 2166 /// Mouse event handler. Settable through setEventHandlers. 2167 void delegate(MouseEvent) handleMouseEvent; 2168 2169 /// use to redraw child widgets if you use system apis to add stuff 2170 void delegate() paintingFinished; 2171 2172 void delegate() paintingFinishedDg() { 2173 return paintingFinished; 2174 } 2175 2176 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 2177 /// for this to ever happen. 2178 void delegate(int width, int height) windowResized; 2179 2180 /** Platform specific - handle any native messages this window gets. 2181 * 2182 * Note: this is called *in addition to* other event handlers, unless you return zero indicating that you handled it. 2183 2184 * On Windows, it takes the form of int delegate(HWND,UINT, WPARAM, LPARAM). 2185 2186 * On X11, it takes the form of int delegate(XEvent). 2187 2188 * IMPORTANT: it used to be static in old versions of simpledisplay.d, but I always used 2189 * it as if it wasn't static... so now I just fixed it so it isn't anymore. 2190 **/ 2191 NativeEventHandler handleNativeEvent; 2192 2193 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 2194 /// If you used to use handleNativeEvent depending on it being static, just change it to use 2195 /// this instead and it will work the same way. 2196 __gshared NativeEventHandler handleNativeGlobalEvent; 2197 2198 // private: 2199 /// The native implementation is available, but you shouldn't use it unless you are 2200 /// familiar with the underlying operating system, don't mind depending on it, and 2201 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 2202 /// do what you need to do with handleNativeEvent instead. 2203 /// 2204 /// This is likely to eventually change to be just a struct holding platform-specific 2205 /// handles instead of a template mixin at some point because I'm not happy with the 2206 /// code duplication here (ironically). 2207 mixin NativeSimpleWindowImplementation!() impl; 2208 2209 /** 2210 This is in-process one-way (from anything to window) event sending mechanics. 2211 It is thread-safe, so it can be used in multi-threaded applications to send, 2212 for example, "wake up and repaint" events when thread completed some operation. 2213 This will allow to avoid using timer pulse to check events with synchronization, 2214 'cause event handler will be called in UI thread. You can stop guessing which 2215 pulse frequency will be enough for your app. 2216 Note that events handlers may be called in arbitrary order, i.e. last registered 2217 handler can be called first, and vice versa. 2218 */ 2219 public: 2220 /** Is our custom event queue empty? Can be used in simple cases to prevent 2221 * "spamming" window with events it can't cope with. 2222 * It is safe to call this from non-UI threads. 2223 */ 2224 @property bool eventQueueEmpty() () { 2225 synchronized(this) { 2226 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 2227 } 2228 return true; 2229 } 2230 2231 /** Does our custom event queue contains at least one with the given type? 2232 * Can be used in simple cases to prevent "spamming" window with events 2233 * it can't cope with. 2234 * It is safe to call this from non-UI threads. 2235 */ 2236 @property bool eventQueued(ET:Object) () { 2237 synchronized(this) { 2238 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 2239 if (!o.doProcess) { 2240 if (cast(ET)(o.evt)) return true; 2241 } 2242 } 2243 } 2244 return false; 2245 } 2246 2247 /** Add listener for custom event. Can be used like this: 2248 * 2249 * --------------------- 2250 * auto eid = win.addEventListener((MyStruct evt) { ... }); 2251 * ... 2252 * win.removeEventListener(eid); 2253 * --------------------- 2254 * 2255 * Returns: 0 on failure (should never happen, so ignore it) 2256 * 2257 * $(WARNING Don't use this method in object destructors!) 2258 * 2259 * $(WARNING It is better to register all event handlers and don't remove 'em, 2260 * 'cause if event handler id counter will overflow, you won't be able 2261 * to register any more events.) 2262 */ 2263 uint addEventListener(ET:Object) (void delegate (ET) dg) { 2264 if (dg is null) return 0; // ignore empty handlers 2265 synchronized(this) { 2266 //FIXME: abort on overflow? 2267 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 2268 EventHandlerEntry e; 2269 e.dg = delegate (Object o) { 2270 if (auto co = cast(ET)o) { 2271 try { 2272 dg(co); 2273 } catch (Exception) { 2274 // sorry! 2275 } 2276 return true; 2277 } 2278 return false; 2279 }; 2280 e.id = lastUsedHandlerId; 2281 auto optr = eventHandlers.ptr; 2282 eventHandlers ~= e; 2283 if (eventHandlers.ptr !is optr) { 2284 import core.memory : GC; 2285 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 2286 } 2287 return lastUsedHandlerId; 2288 } 2289 } 2290 2291 /// Remove event listener. It is safe to pass invalid event id here. 2292 /// $(WARNING Don't use this method in object destructors!) 2293 void removeEventListener() (uint id) { 2294 if (id == 0 || id > lastUsedHandlerId) return; 2295 synchronized(this) { 2296 foreach (immutable idx; 0..eventHandlers.length) { 2297 if (eventHandlers[idx].id == id) { 2298 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 2299 eventHandlers[$-1].dg = null; 2300 eventHandlers.length -= 1; 2301 eventHandlers.assumeSafeAppend; 2302 return; 2303 } 2304 } 2305 } 2306 } 2307 2308 /// Post event to queue. It is safe to call this from non-UI threads. 2309 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 2310 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 2311 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 2312 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 2313 if (this.closed) return false; // closed windows can't handle events 2314 2315 // remove all events of type `ET` 2316 void removeAllET () { 2317 uint eidx = 0, ec = eventQueueUsed; 2318 auto eptr = eventQueue.ptr; 2319 while (eidx < ec) { 2320 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 2321 if (cast(ET)eptr.evt !is null) { 2322 // i found her! 2323 if (inCustomEventProcessor) { 2324 // if we're in custom event processing loop, processor will clear it for us 2325 eptr.evt = null; 2326 ++eidx; 2327 ++eptr; 2328 } else { 2329 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 2330 ec = --eventQueueUsed; 2331 // clear last event (it is already copied) 2332 eventQueue.ptr[ec].evt = null; 2333 } 2334 } else { 2335 ++eidx; 2336 ++eptr; 2337 } 2338 } 2339 } 2340 2341 if (evt is null) { 2342 if (replace) { synchronized(this) removeAllET(); } 2343 // ignore empty events, they can't be handled anyway 2344 return false; 2345 } 2346 2347 // add events even if no event FD/event object created yet 2348 synchronized(this) { 2349 if (replace) removeAllET(); 2350 if (eventQueueUsed == uint.max) return false; // just in case 2351 if (eventQueueUsed < eventQueue.length) { 2352 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 2353 } else { 2354 if (eventQueue.capacity == eventQueue.length) { 2355 // need to reallocate; do a trick to ensure that old array is cleared 2356 auto oarr = eventQueue; 2357 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 2358 // just in case, do yet another check 2359 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 2360 import core.memory : GC; 2361 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 2362 } else { 2363 auto optr = eventQueue.ptr; 2364 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 2365 assert(eventQueue.ptr is optr); 2366 } 2367 ++eventQueueUsed; 2368 assert(eventQueueUsed == eventQueue.length); 2369 } 2370 if (!eventWakeUp()) { 2371 // can't wake up event processor, so there is no reason to keep the event 2372 assert(eventQueueUsed > 0); 2373 eventQueue[--eventQueueUsed].evt = null; 2374 return false; 2375 } 2376 return true; 2377 } 2378 } 2379 2380 /// Post event to queue. It is safe to call this from non-UI threads. 2381 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 2382 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 2383 bool postEvent(ET:Object) (ET evt, bool replace=false) { 2384 return postTimeout!ET(evt, 0, replace); 2385 } 2386 2387 private: 2388 private import core.time : MonoTime; 2389 2390 version(Posix) { 2391 __gshared int customEventFDRead = -1; 2392 __gshared int customEventFDWrite = -1; 2393 __gshared int customSignalFD = -1; 2394 } else version(Windows) { 2395 __gshared HANDLE customEventH = null; 2396 } 2397 2398 // wake up event processor 2399 static bool eventWakeUp () { 2400 version(X11) { 2401 import core.sys.posix.unistd : write; 2402 ulong n = 1; 2403 if (customEventFDWrite >= 0) write(customEventFDWrite, &n, n.sizeof); 2404 return true; 2405 } else version(Windows) { 2406 if (customEventH !is null) SetEvent(customEventH); 2407 return true; 2408 } else { 2409 // not implemented for other OSes 2410 return false; 2411 } 2412 } 2413 2414 static struct QueuedEvent { 2415 Object evt; 2416 bool timed = false; 2417 MonoTime hittime = MonoTime.zero; 2418 bool doProcess = false; // process event at the current iteration (internal flag) 2419 2420 this (Object aevt, uint toutmsecs) { 2421 evt = aevt; 2422 if (toutmsecs > 0) { 2423 import core.time : msecs; 2424 timed = true; 2425 hittime = MonoTime.currTime+toutmsecs.msecs; 2426 } 2427 } 2428 } 2429 2430 alias CustomEventHandler = bool delegate (Object o) nothrow; 2431 static struct EventHandlerEntry { 2432 CustomEventHandler dg; 2433 uint id; 2434 } 2435 2436 uint lastUsedHandlerId; 2437 EventHandlerEntry[] eventHandlers; 2438 QueuedEvent[] eventQueue = null; 2439 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 2440 bool inCustomEventProcessor = false; // required to properly remove events 2441 2442 // process queued events and call custom event handlers 2443 // this will not process events posted from called handlers (such events are postponed for the next iteration) 2444 void processCustomEvents () { 2445 bool hasSomethingToDo = false; 2446 uint ecount; 2447 bool ocep; 2448 synchronized(this) { 2449 ocep = inCustomEventProcessor; 2450 inCustomEventProcessor = true; 2451 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 2452 auto ctt = MonoTime.currTime; 2453 bool hasEmpty = false; 2454 // mark events to process (this is required for `eventQueued()`) 2455 foreach (ref qe; eventQueue[0..ecount]) { 2456 if (qe.evt is null) { hasEmpty = true; continue; } 2457 if (qe.timed) { 2458 qe.doProcess = (qe.hittime <= ctt); 2459 } else { 2460 qe.doProcess = true; 2461 } 2462 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 2463 } 2464 if (!hasSomethingToDo) { 2465 // remove empty events 2466 if (hasEmpty) { 2467 uint eidx = 0, ec = eventQueueUsed; 2468 auto eptr = eventQueue.ptr; 2469 while (eidx < ec) { 2470 if (eptr.evt is null) { 2471 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 2472 ec = --eventQueueUsed; 2473 eventQueue.ptr[ec].evt = null; // make GC life easier 2474 } else { 2475 ++eidx; 2476 ++eptr; 2477 } 2478 } 2479 } 2480 inCustomEventProcessor = ocep; 2481 return; 2482 } 2483 } 2484 // process marked events 2485 uint efree = 0; // non-processed events will be put at this index 2486 EventHandlerEntry[] eh; 2487 Object evt; 2488 foreach (immutable eidx; 0..ecount) { 2489 synchronized(this) { 2490 if (!eventQueue[eidx].doProcess) { 2491 // skip this event 2492 assert(efree <= eidx); 2493 if (efree != eidx) { 2494 // copy this event to queue start 2495 eventQueue[efree] = eventQueue[eidx]; 2496 eventQueue[eidx].evt = null; // just in case 2497 } 2498 ++efree; 2499 continue; 2500 } 2501 evt = eventQueue[eidx].evt; 2502 eventQueue[eidx].evt = null; // in case event handler will hit GC 2503 if (evt is null) continue; // just in case 2504 // try all handlers; this can be slow, but meh... 2505 eh = eventHandlers; 2506 } 2507 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 2508 evt = null; 2509 eh = null; 2510 } 2511 synchronized(this) { 2512 // move all unprocessed events to queue top; efree holds first "free index" 2513 foreach (immutable eidx; ecount..eventQueueUsed) { 2514 assert(efree <= eidx); 2515 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 2516 ++efree; 2517 } 2518 eventQueueUsed = efree; 2519 // wake up event processor on next event loop iteration if we have more queued events 2520 // also, remove empty events 2521 bool awaken = false; 2522 uint eidx = 0, ec = eventQueueUsed; 2523 auto eptr = eventQueue.ptr; 2524 while (eidx < ec) { 2525 if (eptr.evt is null) { 2526 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 2527 ec = --eventQueueUsed; 2528 eventQueue.ptr[ec].evt = null; // make GC life easier 2529 } else { 2530 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 2531 ++eidx; 2532 ++eptr; 2533 } 2534 } 2535 inCustomEventProcessor = ocep; 2536 } 2537 } 2538 2539 // for all windows in nativeMapping 2540 static void processAllCustomEvents () { 2541 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 2542 if (sw is null || sw.closed) continue; 2543 sw.processCustomEvents(); 2544 } 2545 2546 // run pending [runInGuiThread] delegates 2547 more: 2548 RunQueueMember* next; 2549 synchronized(runInGuiThreadLock) { 2550 if(runInGuiThreadQueue.length) { 2551 next = runInGuiThreadQueue[0]; 2552 runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; 2553 } else { 2554 next = null; 2555 } 2556 } 2557 2558 if(next) { 2559 try { 2560 next.dg(); 2561 next.thrown = null; 2562 } catch(Throwable t) { 2563 next.thrown = t; 2564 } 2565 2566 next.signal.notify(); 2567 2568 goto more; 2569 } 2570 } 2571 2572 // 0: infinite (i.e. no scheduled events in queue) 2573 uint eventQueueTimeoutMSecs () { 2574 synchronized(this) { 2575 if (eventQueueUsed == 0) return 0; 2576 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 2577 uint res = int.max; 2578 auto ctt = MonoTime.currTime; 2579 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 2580 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 2581 if (qe.doProcess) continue; // just in case 2582 if (!qe.timed) return 1; // minimal 2583 if (qe.hittime <= ctt) return 1; // minimal 2584 auto tms = (qe.hittime-ctt).total!"msecs"; 2585 if (tms < 1) tms = 1; // safety net 2586 if (tms >= int.max) tms = int.max-1; // and another safety net 2587 if (res > tms) res = cast(uint)tms; 2588 } 2589 return (res >= int.max ? 0 : res); 2590 } 2591 } 2592 2593 // for all windows in nativeMapping 2594 static uint eventAllQueueTimeoutMSecs () { 2595 uint res = uint.max; 2596 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 2597 if (sw is null || sw.closed) continue; 2598 uint to = sw.eventQueueTimeoutMSecs(); 2599 if (to && to < res) { 2600 res = to; 2601 if (to == 1) break; // can't have less than this 2602 } 2603 } 2604 return (res >= int.max ? 0 : res); 2605 } 2606 } 2607 2608 2609 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 2610 /// See [GenericCursor] 2611 class MouseCursor { 2612 int osId; 2613 bool isStockCursor; 2614 private this(int osId) { 2615 this.osId = osId; 2616 this.isStockCursor = true; 2617 } 2618 2619 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 2620 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 2621 2622 version(Windows) { 2623 HCURSOR cursor_; 2624 HCURSOR cursorHandle() { 2625 if(cursor_ is null) 2626 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 2627 return cursor_; 2628 } 2629 2630 } else static if(UsingSimpledisplayX11) { 2631 Cursor cursor_ = None; 2632 int xDisplaySequence; 2633 2634 Cursor cursorHandle() { 2635 if(this.osId == None) 2636 return None; 2637 2638 // we need to reload if we on a new X connection 2639 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 2640 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 2641 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 2642 } 2643 return cursor_; 2644 } 2645 } 2646 } 2647 2648 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 2649 // https://tronche.com/gui/x/xlib/appendix/b/ 2650 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 2651 /// 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 2652 enum GenericCursorType { 2653 Default, /// The default arrow pointer. 2654 Wait, /// A cursor indicating something is loading and the user must wait. 2655 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 2656 Help, /// A cursor indicating the user can get help about the pointer location. 2657 Cross, /// A crosshair. 2658 Text, /// An i-beam shape, typically used to indicate text selection is possible. 2659 Move, /// Pointer indicating movement is possible. May also be used as SizeAll 2660 UpArrow, /// An arrow pointing straight up. 2661 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 2662 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 2663 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator) 2664 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator) 2665 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator) 2666 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator) 2667 2668 } 2669 2670 /* 2671 X_plus == css cell == Windows ? 2672 */ 2673 2674 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 2675 static struct GenericCursor { 2676 static: 2677 /// 2678 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 2679 static MouseCursor mc; 2680 2681 auto type = __traits(getMember, GenericCursorType, str); 2682 2683 if(mc is null) { 2684 2685 version(Windows) { 2686 int osId; 2687 final switch(type) { 2688 case GenericCursorType.Default: osId = IDC_ARROW; break; 2689 case GenericCursorType.Wait: osId = IDC_WAIT; break; 2690 case GenericCursorType.Hand: osId = IDC_HAND; break; 2691 case GenericCursorType.Help: osId = IDC_HELP; break; 2692 case GenericCursorType.Cross: osId = IDC_CROSS; break; 2693 case GenericCursorType.Text: osId = IDC_IBEAM; break; 2694 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 2695 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 2696 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 2697 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 2698 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 2699 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 2700 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 2701 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 2702 } 2703 } else static if(UsingSimpledisplayX11) { 2704 int osId; 2705 final switch(type) { 2706 case GenericCursorType.Default: osId = None; break; 2707 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 2708 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 2709 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 2710 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 2711 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 2712 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 2713 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 2714 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 2715 2716 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 2717 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 2718 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 2719 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 2720 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 2721 } 2722 2723 } else featureNotImplemented(); 2724 2725 mc = new MouseCursor(osId); 2726 } 2727 return mc; 2728 } 2729 } 2730 2731 2732 /++ 2733 If you want to get more control over the event loop, you can use this. 2734 2735 Typically though, you can just call [SimpleWindow.eventLoop]. 2736 +/ 2737 struct EventLoop { 2738 @disable this(); 2739 2740 /// Gets a reference to an existing event loop 2741 static EventLoop get() { 2742 return EventLoop(0, null); 2743 } 2744 2745 __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi 2746 2747 /// Construct an application-global event loop for yourself 2748 /// See_Also: [SimpleWindow.setEventHandlers] 2749 this(long pulseTimeout, void delegate() handlePulse) { 2750 synchronized(monitor) { 2751 if(impl is null) { 2752 claimGuiThread(); 2753 version(sdpy_thread_checks) assert(thisIsGuiThread); 2754 impl = new EventLoopImpl(pulseTimeout, handlePulse); 2755 } else { 2756 if(pulseTimeout) { 2757 impl.pulseTimeout = pulseTimeout; 2758 impl.handlePulse = handlePulse; 2759 } 2760 } 2761 impl.refcount++; 2762 } 2763 } 2764 2765 ~this() { 2766 if(impl is null) 2767 return; 2768 impl.refcount--; 2769 if(impl.refcount == 0) 2770 impl.dispose(); 2771 2772 } 2773 2774 this(this) { 2775 if(impl is null) 2776 return; 2777 impl.refcount++; 2778 } 2779 2780 /// Runs the event loop until the whileCondition, if present, returns false 2781 int run(bool delegate() whileCondition = null) { 2782 assert(impl !is null); 2783 impl.notExited = true; 2784 return impl.run(whileCondition); 2785 } 2786 2787 /// Exits the event loop 2788 void exit() { 2789 assert(impl !is null); 2790 impl.notExited = false; 2791 } 2792 2793 version(linux) 2794 ref void delegate(int) signalHandler() { 2795 assert(impl !is null); 2796 return impl.signalHandler; 2797 } 2798 2799 __gshared static EventLoopImpl* impl; 2800 } 2801 2802 version(linux) 2803 void delegate(int, int) globalHupHandler; 2804 2805 version(Posix) 2806 void makeNonBlocking(int fd) { 2807 import fcntl = core.sys.posix.fcntl; 2808 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 2809 if(flags == -1) 2810 throw new Exception("fcntl get"); 2811 flags |= fcntl.O_NONBLOCK; 2812 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 2813 if(s == -1) 2814 throw new Exception("fcntl set"); 2815 } 2816 2817 struct EventLoopImpl { 2818 int refcount; 2819 2820 bool notExited = true; 2821 2822 version(linux) { 2823 static import ep = core.sys.linux.epoll; 2824 static import unix = core.sys.posix.unistd; 2825 static import err = core.stdc.errno; 2826 import core.sys.linux.timerfd; 2827 2828 void delegate(int) signalHandler; 2829 } 2830 2831 version(X11) { 2832 int pulseFd = -1; 2833 version(linux) ep.epoll_event[16] events = void; 2834 } else version(Windows) { 2835 Timer pulser; 2836 HANDLE[] handles; 2837 } 2838 2839 2840 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2841 /// to call this, as it's not recommended to share window between threads. 2842 void mtLock () { 2843 version(X11) { 2844 XLockDisplay(this.display); 2845 } 2846 } 2847 2848 version(X11) 2849 auto display() { return XDisplayConnection.get; } 2850 2851 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2852 /// to call this, as it's not recommended to share window between threads. 2853 void mtUnlock () { 2854 version(X11) { 2855 XUnlockDisplay(this.display); 2856 } 2857 } 2858 2859 version(with_eventloop) 2860 void initialize(long pulseTimeout) {} 2861 else 2862 void initialize(long pulseTimeout) { 2863 version(Windows) { 2864 if(pulseTimeout) 2865 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 2866 2867 if (customEventH is null) { 2868 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 2869 if (customEventH !is null) { 2870 handles ~= customEventH; 2871 } else { 2872 // this is something that should not be; better be safe than sorry 2873 throw new Exception("can't create eventfd for custom event processing"); 2874 } 2875 } 2876 2877 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 2878 } 2879 2880 version(linux) { 2881 prepareEventLoop(); 2882 { 2883 auto display = XDisplayConnection.get; 2884 // adding Xlib file 2885 ep.epoll_event ev = void; 2886 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 2887 ev.events = ep.EPOLLIN; 2888 ev.data.fd = display.fd; 2889 //import std.conv; 2890 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 2891 throw new Exception("add x fd");// ~ to!string(epollFd)); 2892 displayFd = display.fd; 2893 } 2894 2895 if(pulseTimeout) { 2896 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 2897 if(pulseFd == -1) 2898 throw new Exception("pulse timer create failed"); 2899 2900 itimerspec value; 2901 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 2902 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 2903 2904 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 2905 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 2906 2907 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 2908 throw new Exception("couldn't make pulse timer"); 2909 2910 ep.epoll_event ev = void; 2911 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 2912 ev.events = ep.EPOLLIN; 2913 ev.data.fd = pulseFd; 2914 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 2915 } 2916 2917 // eventfd for custom events 2918 if (customEventFDWrite == -1) { 2919 customEventFDWrite = eventfd(0, 0); 2920 customEventFDRead = customEventFDWrite; 2921 if (customEventFDRead >= 0) { 2922 ep.epoll_event ev = void; 2923 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 2924 ev.events = ep.EPOLLIN; 2925 ev.data.fd = customEventFDRead; 2926 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFDRead, &ev); 2927 } else { 2928 // this is something that should not be; better be safe than sorry 2929 throw new Exception("can't create eventfd for custom event processing"); 2930 } 2931 } 2932 2933 if (customSignalFD == -1) { 2934 import core.sys.linux.sys.signalfd; 2935 2936 sigset_t sigset; 2937 auto err = sigemptyset(&sigset); 2938 assert(!err); 2939 err = sigaddset(&sigset, SIGINT); 2940 assert(!err); 2941 err = sigaddset(&sigset, SIGHUP); 2942 assert(!err); 2943 err = sigprocmask(SIG_BLOCK, &sigset, null); 2944 assert(!err); 2945 2946 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 2947 assert(customSignalFD != -1); 2948 2949 ep.epoll_event ev = void; 2950 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 2951 ev.events = ep.EPOLLIN; 2952 ev.data.fd = customSignalFD; 2953 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 2954 } 2955 } else version(Posix) { 2956 prepareEventLoop(); 2957 if (customEventFDRead == -1) { 2958 int[2] bfr; 2959 import core.sys.posix.unistd; 2960 auto ret = pipe(bfr); 2961 if(ret == -1) throw new Exception("pipe"); 2962 customEventFDRead = bfr[0]; 2963 customEventFDWrite = bfr[1]; 2964 } 2965 2966 } 2967 2968 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 2969 2970 version(linux) { 2971 this.mtLock(); 2972 scope(exit) this.mtUnlock(); 2973 XPending(display); // no, really 2974 } 2975 2976 disposed = false; 2977 } 2978 2979 bool disposed = true; 2980 version(X11) 2981 int displayFd = -1; 2982 2983 version(with_eventloop) 2984 void dispose() {} 2985 else 2986 void dispose() { 2987 disposed = true; 2988 version(X11) { 2989 if(pulseFd != -1) { 2990 import unix = core.sys.posix.unistd; 2991 unix.close(pulseFd); 2992 pulseFd = -1; 2993 } 2994 2995 version(linux) 2996 if(displayFd != -1) { 2997 // 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 2998 ep.epoll_event ev = void; 2999 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 3000 ev.events = ep.EPOLLIN; 3001 ev.data.fd = displayFd; 3002 //import std.conv; 3003 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 3004 displayFd = -1; 3005 } 3006 3007 } else version(Windows) { 3008 if(pulser !is null) { 3009 pulser.destroy(); 3010 pulser = null; 3011 } 3012 if (customEventH !is null) { 3013 CloseHandle(customEventH); 3014 customEventH = null; 3015 } 3016 } 3017 } 3018 3019 this(long pulseTimeout, void delegate() handlePulse) { 3020 this.pulseTimeout = pulseTimeout; 3021 this.handlePulse = handlePulse; 3022 initialize(pulseTimeout); 3023 } 3024 3025 private long pulseTimeout; 3026 void delegate() handlePulse; 3027 3028 ~this() { 3029 dispose(); 3030 } 3031 3032 version(Posix) 3033 ref int customEventFDRead() { return SimpleWindow.customEventFDRead; } 3034 version(Posix) 3035 ref int customEventFDWrite() { return SimpleWindow.customEventFDWrite; } 3036 version(linux) 3037 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 3038 version(Windows) 3039 ref auto customEventH() { return SimpleWindow.customEventH; } 3040 3041 version(with_eventloop) { 3042 int loopHelper(bool delegate() whileCondition) { 3043 // FIXME: whileCondition 3044 import arsd.eventloop; 3045 loop(); 3046 return 0; 3047 } 3048 } else 3049 int loopHelper(bool delegate() whileCondition) { 3050 version(X11) { 3051 bool done = false; 3052 3053 XFlush(display); 3054 insideXEventLoop = true; 3055 scope(exit) insideXEventLoop = false; 3056 3057 version(linux) { 3058 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 3059 bool forceXPending = false; 3060 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 3061 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 3062 { 3063 this.mtLock(); 3064 scope(exit) this.mtUnlock(); 3065 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 3066 } 3067 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 3068 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 3069 if(nfds == -1) { 3070 if(err.errno == err.EINTR) { 3071 continue; // interrupted by signal, just try again 3072 } 3073 throw new Exception("epoll wait failure"); 3074 } 3075 3076 SimpleWindow.processAllCustomEvents(); // anyway 3077 //version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 3078 foreach(idx; 0 .. nfds) { 3079 if(done) break; 3080 auto fd = events[idx].data.fd; 3081 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 3082 auto flags = events[idx].events; 3083 if(flags & ep.EPOLLIN) { 3084 if (fd == customSignalFD) { 3085 version(linux) { 3086 import core.sys.linux.sys.signalfd; 3087 import core.sys.posix.unistd : read; 3088 signalfd_siginfo info; 3089 read(customSignalFD, &info, info.sizeof); 3090 3091 auto sig = info.ssi_signo; 3092 3093 if(EventLoop.get.signalHandler !is null) { 3094 EventLoop.get.signalHandler()(sig); 3095 } else { 3096 EventLoop.get.exit(); 3097 } 3098 } 3099 } else if(fd == display.fd) { 3100 version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); } 3101 this.mtLock(); 3102 scope(exit) this.mtUnlock(); 3103 while(!done && XPending(display)) { 3104 done = doXNextEvent(this.display); 3105 } 3106 forceXPending = false; 3107 } else if(fd == pulseFd) { 3108 long expirationCount; 3109 // 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... 3110 3111 handlePulse(); 3112 3113 // read just to clear the buffer so poll doesn't trigger again 3114 // BTW I read AFTER the pulse because if the pulse handler takes 3115 // a lot of time to execute, we don't want the app to get stuck 3116 // in a loop of timer hits without a chance to do anything else 3117 // 3118 // IOW handlePulse happens at most once per pulse interval. 3119 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 3120 } else if (fd == customEventFDRead) { 3121 // we have some custom events; process 'em 3122 import core.sys.posix.unistd : read; 3123 ulong n; 3124 read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again 3125 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 3126 //SimpleWindow.processAllCustomEvents(); 3127 } else { 3128 // some other timer 3129 version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); } 3130 3131 if(Timer* t = fd in Timer.mapping) 3132 (*t).trigger(); 3133 3134 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 3135 (*pfr).ready(flags); 3136 3137 // or i might add support for other FDs too 3138 // but for now it is just timer 3139 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 3140 } 3141 } 3142 if(flags & ep.EPOLLHUP) { 3143 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 3144 (*pfr).hup(flags); 3145 if(globalHupHandler) 3146 globalHupHandler(fd, flags); 3147 } 3148 /+ 3149 } else { 3150 // not interested in OUT, we are just reading here. 3151 // 3152 // error or hup might also be reported 3153 // but it shouldn't here since we are only 3154 // using a few types of FD and Xlib will report 3155 // if it dies. 3156 // so instead of thoughtfully handling it, I'll 3157 // just throw. for now at least 3158 3159 throw new Exception("epoll did something else"); 3160 } 3161 +/ 3162 } 3163 // if we won't call `XPending()` here, libX may delay some internal event delivery. 3164 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 3165 if (!done && forceXPending) { 3166 this.mtLock(); 3167 scope(exit) this.mtUnlock(); 3168 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 3169 while(!done && XPending(display)) { 3170 done = doXNextEvent(this.display); 3171 } 3172 } 3173 } 3174 } else { 3175 // Generic fallback: yes to simple pulse support, 3176 // but NO timer support! 3177 3178 // FIXME: we could probably support the POSIX timer_create 3179 // signal-based option, but I'm in no rush to write it since 3180 // I prefer the fd-based functions. 3181 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 3182 3183 import core.sys.posix.poll; 3184 3185 pollfd[] pfds; 3186 pollfd[32] pfdsBuffer; 3187 auto len = PosixFdReader.mapping.length + 2; 3188 // FIXME: i should just reuse the buffer 3189 if(len < pfdsBuffer.length) 3190 pfds = pfdsBuffer[0 .. len]; 3191 else 3192 pfds = new pollfd[](len); 3193 3194 pfds[0].fd = display.fd; 3195 pfds[0].events = POLLIN; 3196 pfds[0].revents = 0; 3197 3198 int slot = 1; 3199 3200 if(customEventFDRead != -1) { 3201 pfds[slot].fd = customEventFDRead; 3202 pfds[slot].events = POLLIN; 3203 pfds[slot].revents = 0; 3204 3205 slot++; 3206 } 3207 3208 foreach(fd, obj; PosixFdReader.mapping) { 3209 if(!obj.enabled) continue; 3210 pfds[slot].fd = fd; 3211 pfds[slot].events = POLLIN; 3212 pfds[slot].revents = 0; 3213 3214 slot++; 3215 } 3216 3217 auto ret = poll(pfds.ptr, slot, pulseTimeout > 0 ? cast(int) pulseTimeout : -1); 3218 if(ret == -1) throw new Exception("poll"); 3219 3220 if(ret == 0) { 3221 // FIXME it may not necessarily time out if events keep coming 3222 if(handlePulse !is null) 3223 handlePulse(); 3224 } else { 3225 foreach(s; 0 .. slot) { 3226 if(pfds[s].revents == 0) continue; 3227 3228 if(pfds[s].fd == display.fd) { 3229 while(!done && XPending(display)) { 3230 this.mtLock(); 3231 scope(exit) this.mtUnlock(); 3232 done = doXNextEvent(this.display); 3233 } 3234 } else if(customEventFDRead != -1 && pfds[s].fd == customEventFDRead) { 3235 3236 import core.sys.posix.unistd : read; 3237 ulong n; 3238 read(customEventFDRead, &n, n.sizeof); 3239 SimpleWindow.processAllCustomEvents(); 3240 } else { 3241 auto obj = PosixFdReader.mapping[pfds[s].fd]; 3242 if(pfds[s].revents & POLLNVAL) { 3243 obj.dispose(); 3244 } else { 3245 obj.ready(pfds[s].revents); 3246 } 3247 } 3248 3249 ret--; 3250 if(ret == 0) break; 3251 } 3252 } 3253 } 3254 } 3255 } 3256 3257 version(Windows) { 3258 int ret = -1; 3259 MSG message; 3260 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 3261 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 3262 auto waitResult = MsgWaitForMultipleObjectsEx( 3263 cast(int) handles.length, handles.ptr, 3264 (wto == 0 ? INFINITE : wto), /* timeout */ 3265 0x04FF, /* QS_ALLINPUT */ 3266 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 3267 3268 SimpleWindow.processAllCustomEvents(); // anyway 3269 enum WAIT_OBJECT_0 = 0; 3270 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 3271 auto h = handles[waitResult - WAIT_OBJECT_0]; 3272 if(auto e = h in WindowsHandleReader.mapping) { 3273 (*e).ready(); 3274 } 3275 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 3276 // message ready 3277 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 3278 ret = GetMessage(&message, null, 0, 0); 3279 if(ret == -1) 3280 throw new Exception("GetMessage failed"); 3281 TranslateMessage(&message); 3282 DispatchMessage(&message); 3283 3284 if(ret == 0) // WM_QUIT 3285 break; 3286 } 3287 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 3288 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 3289 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 3290 // timeout, should never happen since we aren't using it 3291 } else if(waitResult == 0xFFFFFFFF) { 3292 // failed 3293 throw new Exception("MsgWaitForMultipleObjectsEx failed"); 3294 } else { 3295 // idk.... 3296 } 3297 } 3298 3299 // return message.wParam; 3300 return 0; 3301 } else { 3302 return 0; 3303 } 3304 } 3305 3306 int run(bool delegate() whileCondition = null) { 3307 if(disposed) 3308 initialize(this.pulseTimeout); 3309 3310 version(X11) { 3311 try { 3312 return loopHelper(whileCondition); 3313 } catch(XDisconnectException e) { 3314 if(e.userRequested) { 3315 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 3316 item.discardConnectionState(); 3317 XCloseDisplay(XDisplayConnection.display); 3318 } 3319 3320 XDisplayConnection.display = null; 3321 3322 this.dispose(); 3323 3324 throw e; 3325 } 3326 } else { 3327 return loopHelper(whileCondition); 3328 } 3329 } 3330 } 3331 3332 3333 /++ 3334 Provides an icon on the system notification area (also known as the system tray). 3335 3336 3337 If a notification area is not available with the NotificationIcon object is created, 3338 it will silently succeed and simply attempt to create one when an area becomes available. 3339 3340 3341 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. 3342 If this is wrong, pass -version=WindowsXP to dmd when compiling and it will 3343 use the older version. 3344 +/ 3345 version(OSXCocoa) {} else // NotYetImplementedException 3346 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 3347 3348 version(X11) { 3349 void recreateAfterDisconnect() { 3350 stateDiscarded = false; 3351 clippixmap = None; 3352 throw new Exception("NOT IMPLEMENTED"); 3353 } 3354 3355 bool stateDiscarded; 3356 void discardConnectionState() { 3357 stateDiscarded = true; 3358 } 3359 } 3360 3361 3362 version(X11) { 3363 Image img; 3364 3365 NativeEventHandler getNativeEventHandler() { 3366 return delegate int(XEvent e) { 3367 switch(e.type) { 3368 case EventType.Expose: 3369 //case EventType.VisibilityNotify: 3370 redraw(); 3371 break; 3372 case EventType.ClientMessage: 3373 version(sddddd) { 3374 import std.stdio; 3375 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 3376 writeln("\t", e.xclient.format); 3377 writeln("\t", e.xclient.data.l); 3378 } 3379 break; 3380 case EventType.ButtonPress: 3381 auto event = e.xbutton; 3382 if (onClick !is null || onClickEx !is null) { 3383 MouseButton mb = cast(MouseButton)0; 3384 switch (event.button) { 3385 case 1: mb = MouseButton.left; break; // left 3386 case 2: mb = MouseButton.middle; break; // middle 3387 case 3: mb = MouseButton.right; break; // right 3388 case 4: mb = MouseButton.wheelUp; break; // scroll up 3389 case 5: mb = MouseButton.wheelDown; break; // scroll down 3390 case 6: break; // idk 3391 case 7: break; // idk 3392 case 8: mb = MouseButton.backButton; break; 3393 case 9: mb = MouseButton.forwardButton; break; 3394 default: 3395 } 3396 if (mb) { 3397 try { onClick()(mb); } catch (Exception) {} 3398 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 3399 } 3400 } 3401 break; 3402 case EventType.EnterNotify: 3403 if (onEnter !is null) { 3404 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 3405 } 3406 break; 3407 case EventType.LeaveNotify: 3408 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 3409 break; 3410 case EventType.DestroyNotify: 3411 active = false; 3412 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 3413 break; 3414 case EventType.ConfigureNotify: 3415 auto event = e.xconfigure; 3416 this.width = event.width; 3417 this.height = event.height; 3418 //import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y); 3419 redraw(); 3420 break; 3421 default: return 1; 3422 } 3423 return 1; 3424 }; 3425 } 3426 3427 /* private */ void hideBalloon() { 3428 balloon.close(); 3429 version(with_timer) 3430 timer.destroy(); 3431 balloon = null; 3432 version(with_timer) 3433 timer = null; 3434 } 3435 3436 void redraw() { 3437 if (!active) return; 3438 3439 auto display = XDisplayConnection.get; 3440 auto gc = DefaultGC(display, DefaultScreen(display)); 3441 XClearWindow(display, nativeHandle); 3442 3443 XSetClipMask(display, gc, clippixmap); 3444 3445 XSetForeground(display, gc, 3446 cast(uint) 0 << 16 | 3447 cast(uint) 0 << 8 | 3448 cast(uint) 0); 3449 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 3450 3451 if (img is null) { 3452 XSetForeground(display, gc, 3453 cast(uint) 0 << 16 | 3454 cast(uint) 127 << 8 | 3455 cast(uint) 0); 3456 XFillArc(display, nativeHandle, 3457 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 3458 } else { 3459 int dx = 0; 3460 int dy = 0; 3461 if(width > img.width) 3462 dx = (width - img.width) / 2; 3463 if(height > img.height) 3464 dy = (height - img.height) / 2; 3465 XSetClipOrigin(display, gc, dx, dy); 3466 3467 if (img.usingXshm) 3468 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false); 3469 else 3470 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height); 3471 } 3472 XSetClipMask(display, gc, None); 3473 flushGui(); 3474 } 3475 3476 static Window getTrayOwner() { 3477 auto display = XDisplayConnection.get; 3478 auto i = cast(int) DefaultScreen(display); 3479 if(i < 10 && i >= 0) { 3480 static Atom atom; 3481 if(atom == None) 3482 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 3483 return XGetSelectionOwner(display, atom); 3484 } 3485 return None; 3486 } 3487 3488 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 3489 auto to = getTrayOwner(); 3490 auto display = XDisplayConnection.get; 3491 XEvent ev; 3492 ev.xclient.type = EventType.ClientMessage; 3493 ev.xclient.window = to; 3494 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 3495 ev.xclient.format = 32; 3496 ev.xclient.data.l[0] = CurrentTime; 3497 ev.xclient.data.l[1] = message; 3498 ev.xclient.data.l[2] = d1; 3499 ev.xclient.data.l[3] = d2; 3500 ev.xclient.data.l[4] = d3; 3501 3502 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 3503 } 3504 3505 private static NotificationAreaIcon[] activeIcons; 3506 3507 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 3508 private void newManager() { 3509 close(); 3510 createXWin(); 3511 3512 if(this.clippixmap) 3513 XFreePixmap(XDisplayConnection.get, clippixmap); 3514 if(this.originalMemoryImage) 3515 this.icon = this.originalMemoryImage; 3516 else if(this.img) 3517 this.icon = this.img; 3518 } 3519 3520 private void createXWin () { 3521 // create window 3522 auto display = XDisplayConnection.get; 3523 3524 // to check for MANAGER on root window to catch new/changed tray owners 3525 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 3526 // so if a thing does appear, we can handle it 3527 foreach(ai; activeIcons) 3528 if(ai is this) 3529 goto alreadythere; 3530 activeIcons ~= this; 3531 alreadythere: 3532 3533 // and check for an existing tray 3534 auto trayOwner = getTrayOwner(); 3535 if(trayOwner == None) 3536 return; 3537 //throw new Exception("No notification area found"); 3538 3539 Visual* v = cast(Visual*) CopyFromParent; 3540 /+ 3541 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 3542 if(visualProp !is null) { 3543 c_ulong[] info = cast(c_ulong[]) visualProp; 3544 if(info.length == 1) { 3545 auto vid = info[0]; 3546 int returned; 3547 XVisualInfo t; 3548 t.visualid = vid; 3549 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 3550 if(got !is null) { 3551 if(returned == 1) { 3552 v = got.visual; 3553 import std.stdio; 3554 writeln("using special visual ", *got); 3555 } 3556 XFree(got); 3557 } 3558 } 3559 } 3560 +/ 3561 3562 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null); 3563 assert(nativeWindow); 3564 3565 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 3566 3567 nativeHandle = nativeWindow; 3568 3569 ///+ 3570 arch_ulong[2] info; 3571 info[0] = 0; 3572 info[1] = 1; 3573 3574 string title = this.name is null ? "simpledisplay.d program" : this.name; 3575 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 3576 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 3577 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 3578 3579 XChangeProperty( 3580 display, 3581 nativeWindow, 3582 GetAtom!("_XEMBED_INFO", true)(display), 3583 GetAtom!("_XEMBED_INFO", true)(display), 3584 32 /* bits */, 3585 0 /*PropModeReplace*/, 3586 info.ptr, 3587 2); 3588 3589 import core.sys.posix.unistd; 3590 arch_ulong pid = getpid(); 3591 3592 XChangeProperty( 3593 display, 3594 nativeWindow, 3595 GetAtom!("_NET_WM_PID", true)(display), 3596 XA_CARDINAL, 3597 32 /* bits */, 3598 0 /*PropModeReplace*/, 3599 &pid, 3600 1); 3601 3602 updateNetWmIcon(); 3603 3604 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 3605 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 3606 XClassHint klass; 3607 XWMHints wh; 3608 XSizeHints size; 3609 klass.res_name = sdpyWindowClassStr; 3610 klass.res_class = sdpyWindowClassStr; 3611 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 3612 } 3613 3614 // believe it or not, THIS is what xfce needed for the 9999 issue 3615 XSizeHints sh; 3616 c_long spr; 3617 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 3618 sh.flags |= PMaxSize | PMinSize; 3619 // FIXME maybe nicer resizing 3620 sh.min_width = 16; 3621 sh.min_height = 16; 3622 sh.max_width = 16; 3623 sh.max_height = 16; 3624 XSetWMNormalHints(display, nativeWindow, &sh); 3625 3626 3627 //+/ 3628 3629 3630 XSelectInput(display, nativeWindow, 3631 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 3632 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 3633 3634 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 3635 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 3636 active = true; 3637 } 3638 3639 void updateNetWmIcon() { 3640 if(img is null) return; 3641 auto display = XDisplayConnection.get; 3642 // FIXME: ensure this is correct 3643 arch_ulong[] buffer; 3644 auto imgMi = img.toTrueColorImage; 3645 buffer ~= imgMi.width; 3646 buffer ~= imgMi.height; 3647 foreach(c; imgMi.imageData.colors) { 3648 arch_ulong b; 3649 b |= c.a << 24; 3650 b |= c.r << 16; 3651 b |= c.g << 8; 3652 b |= c.b; 3653 buffer ~= b; 3654 } 3655 3656 XChangeProperty( 3657 display, 3658 nativeHandle, 3659 GetAtom!"_NET_WM_ICON"(display), 3660 GetAtom!"CARDINAL"(display), 3661 32 /* bits */, 3662 0 /*PropModeReplace*/, 3663 buffer.ptr, 3664 cast(int) buffer.length); 3665 } 3666 3667 3668 3669 private SimpleWindow balloon; 3670 version(with_timer) 3671 private Timer timer; 3672 3673 private Window nativeHandle; 3674 private Pixmap clippixmap = None; 3675 private int width = 16; 3676 private int height = 16; 3677 private bool active = false; 3678 3679 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 3680 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 3681 void delegate () onLeave; /// X11 only. 3682 3683 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 3684 3685 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 3686 void getWindowRect (out int x, out int y, out int width, out int height) { 3687 if (!active) { width = 1; height = 1; return; } // 1: just in case 3688 Window dummyw; 3689 auto dpy = XDisplayConnection.get; 3690 //XWindowAttributes xwa; 3691 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 3692 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 3693 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 3694 width = this.width; 3695 height = this.height; 3696 } 3697 } 3698 3699 /+ 3700 What I actually want from this: 3701 3702 * set / change: icon, tooltip 3703 * handle: mouse click, right click 3704 * show: notification bubble. 3705 +/ 3706 3707 version(Windows) { 3708 WindowsIcon win32Icon; 3709 HWND hwnd; 3710 3711 NOTIFYICONDATAW data; 3712 3713 NativeEventHandler getNativeEventHandler() { 3714 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 3715 if(msg == WM_USER) { 3716 auto event = LOWORD(lParam); 3717 auto iconId = HIWORD(lParam); 3718 //auto x = GET_X_LPARAM(wParam); 3719 //auto y = GET_Y_LPARAM(wParam); 3720 switch(event) { 3721 case WM_LBUTTONDOWN: 3722 onClick()(MouseButton.left); 3723 break; 3724 case WM_RBUTTONDOWN: 3725 onClick()(MouseButton.right); 3726 break; 3727 case WM_MBUTTONDOWN: 3728 onClick()(MouseButton.middle); 3729 break; 3730 case WM_MOUSEMOVE: 3731 // sent, we could use it. 3732 break; 3733 case WM_MOUSEWHEEL: 3734 // NOT SENT 3735 break; 3736 //case NIN_KEYSELECT: 3737 //case NIN_SELECT: 3738 //break; 3739 default: {} 3740 } 3741 } 3742 return 0; 3743 }; 3744 } 3745 3746 enum NIF_SHOWTIP = 0x00000080; 3747 3748 private static struct NOTIFYICONDATAW { 3749 DWORD cbSize; 3750 HWND hWnd; 3751 UINT uID; 3752 UINT uFlags; 3753 UINT uCallbackMessage; 3754 HICON hIcon; 3755 WCHAR[128] szTip; 3756 DWORD dwState; 3757 DWORD dwStateMask; 3758 WCHAR[256] szInfo; 3759 union { 3760 UINT uTimeout; 3761 UINT uVersion; 3762 } 3763 WCHAR[64] szInfoTitle; 3764 DWORD dwInfoFlags; 3765 GUID guidItem; 3766 HICON hBalloonIcon; 3767 } 3768 3769 } 3770 3771 /++ 3772 Note that on Windows, only left, right, and middle buttons are sent. 3773 Mouse wheel buttons are NOT set, so don't rely on those events if your 3774 program is meant to be used on Windows too. 3775 +/ 3776 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 3777 // The canonical constructor for Windows needs the MemoryImage, so it is here, 3778 // but on X, we need an Image, so its canonical ctor is there. They should 3779 // forward to each other though. 3780 version(X11) { 3781 this.name = name; 3782 this.onClick = onClick; 3783 createXWin(); 3784 this.icon = icon; 3785 } else version(Windows) { 3786 this.onClick = onClick; 3787 this.win32Icon = new WindowsIcon(icon); 3788 3789 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 3790 3791 static bool registered = false; 3792 if(!registered) { 3793 WNDCLASSEX wc; 3794 wc.cbSize = wc.sizeof; 3795 wc.hInstance = hInstance; 3796 wc.lpfnWndProc = &WndProc; 3797 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 3798 if(!RegisterClassExW(&wc)) 3799 throw new WindowsApiException("RegisterClass"); 3800 registered = true; 3801 } 3802 3803 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 3804 if(hwnd is null) 3805 throw new Exception("CreateWindow"); 3806 3807 data.cbSize = data.sizeof; 3808 data.hWnd = hwnd; 3809 data.uID = cast(uint) cast(void*) this; 3810 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 3811 // NIF_INFO means show balloon 3812 data.uCallbackMessage = WM_USER; 3813 data.hIcon = this.win32Icon.hIcon; 3814 data.szTip = ""; // FIXME 3815 data.dwState = 0; // NIS_HIDDEN; // windows vista 3816 data.dwStateMask = NIS_HIDDEN; // windows vista 3817 3818 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 3819 3820 3821 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 3822 3823 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 3824 } else version(OSXCocoa) { 3825 throw new NotYetImplementedException(); 3826 } else static assert(0); 3827 } 3828 3829 /// ditto 3830 this(string name, Image icon, void delegate(MouseButton button) onClick) { 3831 version(X11) { 3832 this.onClick = onClick; 3833 this.name = name; 3834 createXWin(); 3835 this.icon = icon; 3836 } else version(Windows) { 3837 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 3838 } else version(OSXCocoa) { 3839 throw new NotYetImplementedException(); 3840 } else static assert(0); 3841 } 3842 3843 version(X11) { 3844 /++ 3845 X-specific extension (for now at least) 3846 +/ 3847 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 3848 this.onClickEx = onClickEx; 3849 createXWin(); 3850 if (icon !is null) this.icon = icon; 3851 } 3852 3853 /// ditto 3854 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 3855 this.onClickEx = onClickEx; 3856 createXWin(); 3857 this.icon = icon; 3858 } 3859 } 3860 3861 private void delegate (MouseButton button) onClick_; 3862 3863 /// 3864 @property final void delegate(MouseButton) onClick() { 3865 if(onClick_ is null) 3866 onClick_ = delegate void(MouseButton) {}; 3867 return onClick_; 3868 } 3869 3870 /// ditto 3871 @property final void onClick(void delegate(MouseButton) handler) { 3872 // I made this a property setter so we can wrap smaller arg 3873 // delegates and just forward all to onClickEx or something. 3874 onClick_ = handler; 3875 } 3876 3877 3878 string name_; 3879 @property void name(string n) { 3880 name_ = n; 3881 } 3882 3883 @property string name() { 3884 return name_; 3885 } 3886 3887 private MemoryImage originalMemoryImage; 3888 3889 /// 3890 @property void icon(MemoryImage i) { 3891 version(X11) { 3892 this.originalMemoryImage = i; 3893 if (!active) return; 3894 if (i !is null) { 3895 this.img = Image.fromMemoryImage(i); 3896 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 3897 //import std.stdio; writeln("using pixmap ", clippixmap); 3898 updateNetWmIcon(); 3899 redraw(); 3900 } else { 3901 if (this.img !is null) { 3902 this.img = null; 3903 redraw(); 3904 } 3905 } 3906 } else version(Windows) { 3907 this.win32Icon = new WindowsIcon(i); 3908 3909 data.uFlags = NIF_ICON; 3910 data.hIcon = this.win32Icon.hIcon; 3911 3912 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 3913 } else version(OSXCocoa) { 3914 throw new NotYetImplementedException(); 3915 } else static assert(0); 3916 } 3917 3918 /// ditto 3919 @property void icon (Image i) { 3920 version(X11) { 3921 if (!active) return; 3922 if (i !is img) { 3923 originalMemoryImage = null; 3924 img = i; 3925 redraw(); 3926 } 3927 } else version(Windows) { 3928 this.icon(i is null ? null : i.toTrueColorImage()); 3929 } else version(OSXCocoa) { 3930 throw new NotYetImplementedException(); 3931 } else static assert(0); 3932 } 3933 3934 /++ 3935 Shows a balloon notification. You can only show one balloon at a time, if you call 3936 it twice while one is already up, the first balloon will be replaced. 3937 3938 3939 The user is free to block notifications and they will automatically disappear after 3940 a timeout period. 3941 3942 Params: 3943 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 3944 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 3945 icon = the icon to display with the notification. If null, it uses your existing icon. 3946 onclick = delegate called if the user clicks the balloon. (not yet implemented) 3947 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 3948 +/ 3949 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 3950 bool useCustom = true; 3951 version(libnotify) { 3952 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 3953 try { 3954 if(!active) return; 3955 3956 if(libnotify is null) { 3957 libnotify = new C_DynamicLibrary("libnotify.so"); 3958 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 3959 } 3960 3961 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 3962 3963 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 3964 3965 if(onclick) { 3966 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 3967 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); 3968 libnotify_action_delegates_count++; 3969 } 3970 3971 // FIXME icon 3972 3973 // set hint image-data 3974 // set default action for onclick 3975 3976 void* error; 3977 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 3978 3979 useCustom = false; 3980 } catch(Exception e) { 3981 3982 } 3983 } 3984 3985 version(X11) { 3986 if(useCustom) { 3987 if(!active) return; 3988 if(balloon) { 3989 hideBalloon(); 3990 } 3991 // I know there are two specs for this, but one is never 3992 // implemented by any window manager I have ever seen, and 3993 // the other is a bloated mess and too complicated for simpledisplay... 3994 // so doing my own little window instead. 3995 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 3996 3997 int x, y, width, height; 3998 getWindowRect(x, y, width, height); 3999 4000 int bx = x - balloon.width; 4001 int by = y - balloon.height; 4002 if(bx < 0) 4003 bx = x + width + balloon.width; 4004 if(by < 0) 4005 by = y + height; 4006 4007 // just in case, make sure it is actually on scren 4008 if(bx < 0) 4009 bx = 0; 4010 if(by < 0) 4011 by = 0; 4012 4013 balloon.move(bx, by); 4014 auto painter = balloon.draw(); 4015 painter.fillColor = Color(220, 220, 220); 4016 painter.outlineColor = Color.black; 4017 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 4018 auto iconWidth = icon is null ? 0 : icon.width; 4019 if(icon) 4020 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 4021 iconWidth += 6; // margin around the icon 4022 4023 // draw a close button 4024 painter.outlineColor = Color(44, 44, 44); 4025 painter.fillColor = Color(255, 255, 255); 4026 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 4027 painter.pen = Pen(Color.black, 3); 4028 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 4029 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 4030 painter.pen = Pen(Color.black, 1); 4031 painter.fillColor = Color(220, 220, 220); 4032 4033 // Draw the title and message 4034 painter.drawText(Point(4 + iconWidth, 4), title); 4035 painter.drawLine( 4036 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 4037 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 4038 ); 4039 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 4040 4041 balloon.setEventHandlers( 4042 (MouseEvent ev) { 4043 if(ev.type == MouseEventType.buttonPressed) { 4044 if(ev.x > balloon.width - 16 && ev.y < 16) 4045 hideBalloon(); 4046 else if(onclick) 4047 onclick(); 4048 } 4049 } 4050 ); 4051 balloon.show(); 4052 4053 version(with_timer) 4054 timer = new Timer(timeout, &hideBalloon); 4055 else {} // FIXME 4056 } 4057 } else version(Windows) { 4058 enum NIF_INFO = 0x00000010; 4059 4060 data.uFlags = NIF_INFO; 4061 4062 // FIXME: go back to the last valid unicode code point 4063 if(title.length > 40) 4064 title = title[0 .. 40]; 4065 if(message.length > 220) 4066 message = message[0 .. 220]; 4067 4068 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 4069 enum NIIF_LARGE_ICON = 0x00000020; 4070 enum NIIF_NOSOUND = 0x00000010; 4071 enum NIIF_USER = 0x00000004; 4072 enum NIIF_ERROR = 0x00000003; 4073 enum NIIF_WARNING = 0x00000002; 4074 enum NIIF_INFO = 0x00000001; 4075 enum NIIF_NONE = 0; 4076 4077 WCharzBuffer t = WCharzBuffer(title); 4078 WCharzBuffer m = WCharzBuffer(message); 4079 4080 t.copyInto(data.szInfoTitle); 4081 m.copyInto(data.szInfo); 4082 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 4083 4084 if(icon !is null) { 4085 auto i = new WindowsIcon(icon); 4086 data.hBalloonIcon = i.hIcon; 4087 data.dwInfoFlags |= NIIF_USER; 4088 } 4089 4090 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4091 } else version(OSXCocoa) { 4092 throw new NotYetImplementedException(); 4093 } else static assert(0); 4094 } 4095 4096 /// 4097 //version(Windows) 4098 void show() { 4099 version(X11) { 4100 if(!hidden) 4101 return; 4102 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 4103 hidden = false; 4104 } else version(Windows) { 4105 data.uFlags = NIF_STATE; 4106 data.dwState = 0; // NIS_HIDDEN; // windows vista 4107 data.dwStateMask = NIS_HIDDEN; // windows vista 4108 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4109 } else version(OSXCocoa) { 4110 throw new NotYetImplementedException(); 4111 } else static assert(0); 4112 } 4113 4114 version(X11) 4115 bool hidden = false; 4116 4117 /// 4118 //version(Windows) 4119 void hide() { 4120 version(X11) { 4121 if(hidden) 4122 return; 4123 hidden = true; 4124 XUnmapWindow(XDisplayConnection.get, nativeHandle); 4125 } else version(Windows) { 4126 data.uFlags = NIF_STATE; 4127 data.dwState = NIS_HIDDEN; // windows vista 4128 data.dwStateMask = NIS_HIDDEN; // windows vista 4129 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 4130 } else version(OSXCocoa) { 4131 throw new NotYetImplementedException(); 4132 } else static assert(0); 4133 } 4134 4135 /// 4136 void close () { 4137 version(X11) { 4138 if (active) { 4139 active = false; // event handler will set this too, but meh 4140 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 4141 XDestroyWindow(XDisplayConnection.get, nativeHandle); 4142 flushGui(); 4143 } 4144 } else version(Windows) { 4145 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 4146 } else version(OSXCocoa) { 4147 throw new NotYetImplementedException(); 4148 } else static assert(0); 4149 } 4150 4151 ~this() { 4152 version(X11) 4153 if(clippixmap != None) 4154 XFreePixmap(XDisplayConnection.get, clippixmap); 4155 close(); 4156 } 4157 } 4158 4159 version(X11) 4160 /// call XFreePixmap on the return value 4161 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 4162 char[] data = new char[](i.width * i.height / 8 + 2); 4163 data[] = 0; 4164 4165 int bitOffset = 0; 4166 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 4167 ubyte v = c.a > 128 ? 1 : 0; 4168 data[bitOffset / 8] |= v << (bitOffset%8); 4169 bitOffset++; 4170 } 4171 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 4172 return handle; 4173 } 4174 4175 4176 // basic functions to make timers 4177 /** 4178 A timer that will trigger your function on a given interval. 4179 4180 4181 You create a timer with an interval and a callback. It will continue 4182 to fire on the interval until it is destroyed. 4183 4184 There are currently no one-off timers (instead, just create one and 4185 destroy it when it is triggered) nor are there pause/resume functions - 4186 the timer must again be destroyed and recreated if you want to pause it. 4187 4188 auto timer = new Timer(50, { it happened!; }); 4189 timer.destroy(); 4190 4191 Timers can only be expected to fire when the event loop is running. 4192 */ 4193 version(with_timer) { 4194 class Timer { 4195 // FIXME: needs pause and unpause 4196 // FIXME: I might add overloads for ones that take a count of 4197 // how many elapsed since last time (on Windows, it will divide 4198 // the ticks thing given, on Linux it is just available) and 4199 // maybe one that takes an instance of the Timer itself too 4200 /// Create a timer with a callback when it triggers. 4201 this(int intervalInMilliseconds, void delegate() onPulse) { 4202 assert(onPulse !is null); 4203 4204 this.onPulse = onPulse; 4205 4206 version(Windows) { 4207 /* 4208 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 4209 if(handle == 0) 4210 throw new Exception("SetTimer fail"); 4211 */ 4212 4213 // thanks to Archival 998 for the WaitableTimer blocks 4214 handle = CreateWaitableTimer(null, false, null); 4215 long initialTime = 0; 4216 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 4217 throw new Exception("SetWaitableTimer Failed"); 4218 4219 mapping[handle] = this; 4220 4221 } else version(linux) { 4222 static import ep = core.sys.linux.epoll; 4223 4224 import core.sys.linux.timerfd; 4225 4226 fd = timerfd_create(CLOCK_MONOTONIC, 0); 4227 if(fd == -1) 4228 throw new Exception("timer create failed"); 4229 4230 mapping[fd] = this; 4231 4232 itimerspec value; 4233 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 4234 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 4235 4236 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 4237 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 4238 4239 if(timerfd_settime(fd, 0, &value, null) == -1) 4240 throw new Exception("couldn't make pulse timer"); 4241 4242 version(with_eventloop) { 4243 import arsd.eventloop; 4244 addFileEventListeners(fd, &trigger, null, null); 4245 } else { 4246 prepareEventLoop(); 4247 4248 ep.epoll_event ev = void; 4249 ev.events = ep.EPOLLIN; 4250 ev.data.fd = fd; 4251 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 4252 } 4253 } else featureNotImplemented(); 4254 } 4255 4256 /// Stop and destroy the timer object. 4257 void destroy() { 4258 version(Windows) { 4259 if(handle) { 4260 // KillTimer(null, handle); 4261 CancelWaitableTimer(cast(void*)handle); 4262 mapping.remove(handle); 4263 CloseHandle(handle); 4264 handle = null; 4265 } 4266 } else version(linux) { 4267 if(fd != -1) { 4268 import unix = core.sys.posix.unistd; 4269 static import ep = core.sys.linux.epoll; 4270 4271 version(with_eventloop) { 4272 import arsd.eventloop; 4273 removeFileEventListeners(fd); 4274 } else { 4275 ep.epoll_event ev = void; 4276 ev.events = ep.EPOLLIN; 4277 ev.data.fd = fd; 4278 4279 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 4280 } 4281 unix.close(fd); 4282 mapping.remove(fd); 4283 fd = -1; 4284 } 4285 } else featureNotImplemented(); 4286 } 4287 4288 ~this() { 4289 destroy(); 4290 } 4291 4292 4293 void changeTime(int intervalInMilliseconds) 4294 { 4295 version(Windows) 4296 { 4297 if(handle) 4298 { 4299 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 4300 long initialTime = 0; 4301 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 4302 throw new Exception("couldn't change pulse timer"); 4303 } 4304 } 4305 } 4306 4307 4308 private: 4309 4310 void delegate() onPulse; 4311 4312 void trigger() { 4313 version(linux) { 4314 import unix = core.sys.posix.unistd; 4315 long val; 4316 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 4317 } else version(Windows) { 4318 4319 } else featureNotImplemented(); 4320 4321 onPulse(); 4322 } 4323 4324 version(Windows) 4325 extern(Windows) 4326 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 4327 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 4328 if(Timer* t = timer in mapping) { 4329 try 4330 (*t).trigger(); 4331 catch(Exception e) { throw new Error(e.msg, e.file, e.line); } 4332 } 4333 } 4334 4335 version(Windows) { 4336 //UINT_PTR handle; 4337 //static Timer[UINT_PTR] mapping; 4338 HANDLE handle; 4339 __gshared Timer[HANDLE] mapping; 4340 } else version(linux) { 4341 int fd = -1; 4342 __gshared Timer[int] mapping; 4343 } else static assert(0, "timer not supported"); 4344 } 4345 } 4346 4347 version(Windows) 4348 /// 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 4349 class WindowsHandleReader { 4350 /// 4351 this(void delegate() onReady, HANDLE handle) { 4352 this.onReady = onReady; 4353 this.handle = handle; 4354 4355 mapping[handle] = this; 4356 4357 enable(); 4358 } 4359 4360 /// 4361 void enable() { 4362 auto el = EventLoop.get().impl; 4363 el.handles ~= handle; 4364 } 4365 4366 /// 4367 void disable() { 4368 auto el = EventLoop.get().impl; 4369 for(int i = 0; i < el.handles.length; i++) { 4370 if(el.handles[i] is handle) { 4371 el.handles[i] = el.handles[$-1]; 4372 el.handles = el.handles[0 .. $-1]; 4373 return; 4374 } 4375 } 4376 } 4377 4378 void dispose() { 4379 disable(); 4380 mapping.remove(handle); 4381 handle = null; 4382 } 4383 4384 void ready() { 4385 if(onReady) 4386 onReady(); 4387 } 4388 4389 HANDLE handle; 4390 void delegate() onReady; 4391 4392 __gshared WindowsHandleReader[HANDLE] mapping; 4393 } 4394 4395 version(Posix) 4396 /// Lets you add files to the event loop for reading. Use at your own risk. 4397 class PosixFdReader { 4398 /// 4399 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 4400 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 4401 } 4402 4403 /// 4404 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 4405 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 4406 } 4407 4408 /// 4409 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 4410 this.onReady = onReady; 4411 this.fd = fd; 4412 this.captureWrites = captureWrites; 4413 this.captureReads = captureReads; 4414 4415 mapping[fd] = this; 4416 4417 version(with_eventloop) { 4418 import arsd.eventloop; 4419 addFileEventListeners(fd, &readyel); 4420 } else { 4421 enable(); 4422 } 4423 } 4424 4425 bool captureReads; 4426 bool captureWrites; 4427 4428 version(with_eventloop) {} else 4429 /// 4430 void enable() { 4431 prepareEventLoop(); 4432 4433 enabled = true; 4434 4435 version(linux) { 4436 static import ep = core.sys.linux.epoll; 4437 ep.epoll_event ev = void; 4438 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 4439 //import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites); 4440 ev.data.fd = fd; 4441 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 4442 } else { 4443 4444 } 4445 } 4446 4447 version(with_eventloop) {} else 4448 /// 4449 void disable() { 4450 prepareEventLoop(); 4451 4452 enabled = false; 4453 4454 version(linux) { 4455 static import ep = core.sys.linux.epoll; 4456 ep.epoll_event ev = void; 4457 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 4458 //import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites); 4459 ev.data.fd = fd; 4460 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 4461 } 4462 } 4463 4464 version(with_eventloop) {} else 4465 /// 4466 void dispose() { 4467 disable(); 4468 mapping.remove(fd); 4469 fd = -1; 4470 } 4471 4472 void delegate(int, bool, bool) onReady; 4473 4474 version(with_eventloop) 4475 void readyel() { 4476 onReady(fd, true, true); 4477 } 4478 4479 void ready(uint flags) { 4480 version(linux) { 4481 static import ep = core.sys.linux.epoll; 4482 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 4483 } else { 4484 import core.sys.posix.poll; 4485 onReady(fd, (flags & POLLIN) ? true : false, (flags & POLLOUT) ? true : false); 4486 } 4487 } 4488 4489 void hup(uint flags) { 4490 if(onHup) 4491 onHup(); 4492 } 4493 4494 void delegate() onHup; 4495 4496 int fd = -1; 4497 private bool enabled; 4498 __gshared PosixFdReader[int] mapping; 4499 } 4500 4501 // basic functions to access the clipboard 4502 /+ 4503 4504 4505 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 4506 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 4507 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 4508 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 4509 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 4510 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 4511 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 4512 4513 +/ 4514 4515 /++ 4516 this does a delegate because it is actually an async call on X... 4517 the receiver may never be called if the clipboard is empty or unavailable 4518 gets plain text from the clipboard 4519 +/ 4520 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 4521 version(Windows) { 4522 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 4523 if(OpenClipboard(hwndOwner) == 0) 4524 throw new Exception("OpenClipboard"); 4525 scope(exit) 4526 CloseClipboard(); 4527 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 4528 4529 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 4530 scope(exit) 4531 GlobalUnlock(dataHandle); 4532 4533 // FIXME: CR/LF conversions 4534 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 4535 int len = 0; 4536 auto d = data; 4537 while(*d) { 4538 d++; 4539 len++; 4540 } 4541 string s; 4542 s.reserve(len); 4543 foreach(dchar ch; data[0 .. len]) { 4544 s ~= ch; 4545 } 4546 receiver(s); 4547 } 4548 } 4549 } else version(X11) { 4550 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 4551 } else version(OSXCocoa) { 4552 throw new NotYetImplementedException(); 4553 } else static assert(0); 4554 } 4555 4556 version(Windows) 4557 struct WCharzBuffer { 4558 wchar[256] staticBuffer; 4559 wchar[] buffer; 4560 4561 size_t length() { 4562 return buffer.length; 4563 } 4564 4565 wchar* ptr() { 4566 return buffer.ptr; 4567 } 4568 4569 wchar[] slice() { 4570 return buffer; 4571 } 4572 4573 void copyInto(R)(ref R r) { 4574 static if(is(R == wchar[N], size_t N)) { 4575 r[0 .. this.length] = slice[]; 4576 r[this.length] = 0; 4577 } else static assert(0, "can only copy into wchar[n], not " ~ R.stringof); 4578 } 4579 4580 /++ 4581 conversionFlags = [WindowsStringConversionFlags] 4582 +/ 4583 this(in char[] data, int conversionFlags = 0) { 4584 conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name 4585 auto sz = sizeOfConvertedWstring(data, conversionFlags); 4586 if(sz > staticBuffer.length) 4587 buffer = new wchar[](sz); 4588 else 4589 buffer = staticBuffer[]; 4590 4591 buffer = makeWindowsString(data, buffer, conversionFlags); 4592 } 4593 } 4594 4595 version(Windows) 4596 int sizeOfConvertedWstring(in char[] s, int conversionFlags) { 4597 int size = 0; 4598 4599 if(conversionFlags & WindowsStringConversionFlags.convertNewLines) { 4600 // need to convert line endings, which means the length will get bigger. 4601 4602 // BTW I betcha this could be faster with some simd stuff. 4603 char last; 4604 foreach(char ch; s) { 4605 if(ch == 10 && last != 13) 4606 size++; // will add a 13 before it... 4607 size++; 4608 last = ch; 4609 } 4610 } else { 4611 // no conversion necessary, just estimate based on length 4612 /* 4613 I don't think there's any string with a longer length 4614 in code units when encoded in UTF-16 than it has in UTF-8. 4615 This will probably over allocate, but that's OK. 4616 */ 4617 size = cast(int) s.length; 4618 } 4619 4620 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) 4621 size++; 4622 4623 return size; 4624 } 4625 4626 version(Windows) 4627 enum WindowsStringConversionFlags : int { 4628 zeroTerminate = 1, 4629 convertNewLines = 2, 4630 } 4631 4632 version(Windows) 4633 class WindowsApiException : Exception { 4634 char[256] buffer; 4635 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 4636 assert(msg.length < 100); 4637 4638 auto error = GetLastError(); 4639 buffer[0 .. msg.length] = msg; 4640 buffer[msg.length] = ' '; 4641 4642 int pos = cast(int) msg.length + 1; 4643 4644 if(error == 0) 4645 buffer[pos++] = '0'; 4646 else { 4647 auto init = pos; 4648 while(error) { 4649 buffer[pos++] = (error % 10) + '0'; 4650 error /= 10; 4651 } 4652 for(int i = 0; i < (pos - init) / 2; i++) { 4653 char c = buffer[i + init]; 4654 buffer[i + init] = buffer[pos - (i + init) - 1]; 4655 buffer[pos - (i + init) - 1] = c; 4656 } 4657 } 4658 4659 4660 super(cast(string) buffer[0 .. pos], file, line, next); 4661 } 4662 } 4663 4664 class ErrnoApiException : Exception { 4665 char[256] buffer; 4666 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 4667 assert(msg.length < 100); 4668 4669 import core.stdc.errno; 4670 auto error = errno; 4671 buffer[0 .. msg.length] = msg; 4672 buffer[msg.length] = ' '; 4673 4674 int pos = cast(int) msg.length + 1; 4675 4676 if(error == 0) 4677 buffer[pos++] = '0'; 4678 else { 4679 auto init = pos; 4680 while(error) { 4681 buffer[pos++] = (error % 10) + '0'; 4682 error /= 10; 4683 } 4684 for(int i = 0; i < (pos - init) / 2; i++) { 4685 char c = buffer[i + init]; 4686 buffer[i + init] = buffer[pos - (i + init) - 1]; 4687 buffer[pos - (i + init) - 1] = c; 4688 } 4689 } 4690 4691 4692 super(cast(string) buffer[0 .. pos], file, line, next); 4693 } 4694 4695 } 4696 4697 version(Windows) 4698 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) { 4699 if(str.length == 0) 4700 return null; 4701 4702 int pos = 0; 4703 dchar last; 4704 foreach(dchar c; str) { 4705 if(c <= 0xFFFF) { 4706 if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13) 4707 buffer[pos++] = 13; 4708 buffer[pos++] = cast(wchar) c; 4709 } else if(c <= 0x10FFFF) { 4710 buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 4711 buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); 4712 } 4713 4714 last = c; 4715 } 4716 4717 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) { 4718 buffer[pos] = 0; 4719 } 4720 4721 return buffer[0 .. pos]; 4722 } 4723 4724 version(Windows) 4725 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) { 4726 if(str.length == 0) 4727 return null; 4728 4729 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null); 4730 if(got == 0) { 4731 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 4732 throw new Exception("not enough buffer"); 4733 else 4734 throw new Exception("conversion"); // FIXME: GetLastError 4735 } 4736 return buffer[0 .. got]; 4737 } 4738 4739 version(Windows) 4740 string makeUtf8StringFromWindowsString(in wchar[] str) { 4741 char[] buffer; 4742 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null); 4743 buffer.length = got; 4744 4745 // it is unique because we just allocated it above! 4746 return cast(string) makeUtf8StringFromWindowsString(str, buffer); 4747 } 4748 4749 version(Windows) 4750 string makeUtf8StringFromWindowsString(wchar* str) { 4751 char[] buffer; 4752 auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null); 4753 buffer.length = got; 4754 4755 got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null); 4756 if(got == 0) { 4757 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 4758 throw new Exception("not enough buffer"); 4759 else 4760 throw new Exception("conversion"); // FIXME: GetLastError 4761 } 4762 return cast(string) buffer[0 .. got]; 4763 } 4764 4765 int findIndexOfZero(in wchar[] str) { 4766 foreach(idx, wchar ch; str) 4767 if(ch == 0) 4768 return cast(int) idx; 4769 return cast(int) str.length; 4770 } 4771 int findIndexOfZero(in char[] str) { 4772 foreach(idx, char ch; str) 4773 if(ch == 0) 4774 return cast(int) idx; 4775 return cast(int) str.length; 4776 } 4777 4778 /// copies some text to the clipboard 4779 void setClipboardText(SimpleWindow clipboardOwner, string text) { 4780 assert(clipboardOwner !is null); 4781 version(Windows) { 4782 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 4783 throw new Exception("OpenClipboard"); 4784 scope(exit) 4785 CloseClipboard(); 4786 EmptyClipboard(); 4787 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 4788 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 4789 if(handle is null) throw new Exception("GlobalAlloc"); 4790 if(auto data = cast(wchar*) GlobalLock(handle)) { 4791 auto slice = data[0 .. sz]; 4792 scope(failure) 4793 GlobalUnlock(handle); 4794 4795 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 4796 4797 GlobalUnlock(handle); 4798 SetClipboardData(CF_UNICODETEXT, handle); 4799 } 4800 } else version(X11) { 4801 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 4802 } else version(OSXCocoa) { 4803 throw new NotYetImplementedException(); 4804 } else static assert(0); 4805 } 4806 4807 // FIXME: functions for doing images would be nice too - CF_DIB and whatever it is on X would be ok if we took the MemoryImage from color.d, or an Image from here. hell it might even be a variadic template that sets all the formats in one call. that might be cool. 4808 4809 version(X11) { 4810 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 4811 4812 private Atom*[] interredAtoms; // for discardAndRecreate 4813 4814 /// Platform specific for X11 4815 @property Atom GetAtom(string name, bool create = false)(Display* display) { 4816 static Atom a; 4817 if(!a) { 4818 a = XInternAtom(display, name, !create); 4819 interredAtoms ~= &a; 4820 } 4821 if(a == None) 4822 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 4823 return a; 4824 } 4825 4826 /// Platform specific for X11 - gets atom names as a string 4827 string getAtomName(Atom atom, Display* display) { 4828 auto got = XGetAtomName(display, atom); 4829 scope(exit) XFree(got); 4830 import core.stdc.string; 4831 string s = got[0 .. strlen(got)].idup; 4832 return s; 4833 } 4834 4835 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later 4836 void setPrimarySelection(SimpleWindow window, string text) { 4837 setX11Selection!"PRIMARY"(window, text); 4838 } 4839 4840 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later 4841 void setSecondarySelection(SimpleWindow window, string text) { 4842 setX11Selection!"SECONDARY"(window, text); 4843 } 4844 4845 /// The `after` delegate is called after a client requests the UTF8_STRING thing. it is a mega-hack right now! 4846 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 4847 assert(window !is null); 4848 4849 auto display = XDisplayConnection.get(); 4850 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 4851 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 4852 else Atom a = GetAtom!atomName(display); 4853 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 4854 window.impl.setSelectionHandler = (XEvent ev) { 4855 XSelectionRequestEvent* event = &ev.xselectionrequest; 4856 XSelectionEvent selectionEvent; 4857 selectionEvent.type = EventType.SelectionNotify; 4858 selectionEvent.display = event.display; 4859 selectionEvent.requestor = event.requestor; 4860 selectionEvent.selection = event.selection; 4861 selectionEvent.time = event.time; 4862 selectionEvent.target = event.target; 4863 4864 if(event.property == None) 4865 selectionEvent.property = event.target; 4866 if(event.target == GetAtom!"TARGETS"(display)) { 4867 /* respond with the supported types */ 4868 Atom[3] tlist;// = [XA_UTF8, XA_STRING, XA_TARGETS]; 4869 tlist[0] = GetAtom!"UTF8_STRING"(display); 4870 tlist[1] = XA_STRING; 4871 tlist[2] = GetAtom!"TARGETS"(display); 4872 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, 3); 4873 selectionEvent.property = event.property; 4874 } else if(event.target == XA_STRING) { 4875 selectionEvent.property = event.property; 4876 XChangeProperty (display, 4877 selectionEvent.requestor, 4878 selectionEvent.property, 4879 event.target, 4880 8 /* bits */, 0 /* PropModeReplace */, 4881 text.ptr, cast(int) text.length); 4882 } else if(event.target == GetAtom!"UTF8_STRING"(display)) { 4883 selectionEvent.property = event.property; 4884 XChangeProperty (display, 4885 selectionEvent.requestor, 4886 selectionEvent.property, 4887 event.target, 4888 8 /* bits */, 0 /* PropModeReplace */, 4889 text.ptr, cast(int) text.length); 4890 4891 if(after) 4892 after(); 4893 } else { 4894 selectionEvent.property = None; // I don't know how to handle this type... 4895 } 4896 4897 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 4898 }; 4899 } 4900 4901 /// 4902 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 4903 getX11Selection!"PRIMARY"(window, handler); 4904 } 4905 4906 /// 4907 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler) { 4908 assert(window !is null); 4909 4910 auto display = XDisplayConnection.get(); 4911 auto atom = GetAtom!atomName(display); 4912 4913 window.impl.getSelectionHandler = handler; 4914 4915 auto target = GetAtom!"TARGETS"(display); 4916 4917 // SDD_DATA is "simpledisplay.d data" 4918 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 4919 } 4920 4921 /// 4922 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 4923 Atom actualType; 4924 int actualFormat; 4925 arch_ulong actualItems; 4926 arch_ulong bytesRemaining; 4927 void* data; 4928 4929 auto display = XDisplayConnection.get(); 4930 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 4931 if(actualFormat == 0) 4932 return null; 4933 else { 4934 int byteLength; 4935 if(actualFormat == 32) { 4936 // 32 means it is a C long... which is variable length 4937 actualFormat = cast(int) arch_long.sizeof * 8; 4938 } 4939 4940 // then it is just a bit count 4941 byteLength = cast(int) (actualItems * actualFormat / 8); 4942 4943 auto d = new ubyte[](byteLength); 4944 d[] = cast(ubyte[]) data[0 .. byteLength]; 4945 XFree(data); 4946 return d; 4947 } 4948 } 4949 return null; 4950 } 4951 4952 /* defined in the systray spec */ 4953 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 4954 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 4955 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 4956 4957 4958 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 4959 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 4960 public class GlobalHotkey { 4961 KeyEvent key; 4962 void delegate () handler; 4963 4964 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 4965 4966 /// Create from initialzed KeyEvent object 4967 this (KeyEvent akey, void delegate () ahandler=null) { 4968 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 4969 key = akey; 4970 handler = ahandler; 4971 } 4972 4973 /// Create from emacs-like key name ("C-M-Y", etc.) 4974 this (const(char)[] akey, void delegate () ahandler=null) { 4975 key = KeyEvent.parse(akey); 4976 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 4977 handler = ahandler; 4978 } 4979 4980 } 4981 4982 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 4983 //conwriteln("failed to grab key"); 4984 GlobalHotkeyManager.ghfailed = true; 4985 return 0; 4986 } 4987 4988 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 4989 import core.stdc.stdio; 4990 char[265] buffer; 4991 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 4992 printf("ERROR: %s\n", buffer.ptr); 4993 return 0; 4994 } 4995 4996 /++ 4997 Global hotkey manager. It contains static methods to manage global hotkeys. 4998 4999 --- 5000 try { 5001 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 5002 } catch (Exception e) { 5003 conwriteln("ERROR registering hotkey!"); 5004 } 5005 --- 5006 5007 The key strings are based on Emacs. In practical terms, 5008 `M` means `alt` and `H` means the Windows logo key. `C` 5009 is `ctrl`. 5010 5011 $(WARNING 5012 This is X-specific right now. If you are on 5013 Windows, try [registerHotKey] instead. 5014 5015 We will probably merge these into a single 5016 interface later. 5017 ) 5018 +/ 5019 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 5020 version(X11) { 5021 void recreateAfterDisconnect() { 5022 throw new Exception("NOT IMPLEMENTED"); 5023 } 5024 void discardConnectionState() { 5025 throw new Exception("NOT IMPLEMENTED"); 5026 } 5027 } 5028 5029 private static immutable uint[8] masklist = [ 0, 5030 KeyOrButtonMask.LockMask, 5031 KeyOrButtonMask.Mod2Mask, 5032 KeyOrButtonMask.Mod3Mask, 5033 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 5034 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 5035 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 5036 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 5037 ]; 5038 private __gshared GlobalHotkeyManager ghmanager; 5039 private __gshared bool ghfailed = false; 5040 5041 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 5042 if (modmask == 0) return false; 5043 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 5044 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 5045 return true; 5046 } 5047 5048 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 5049 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 5050 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 5051 return modmask; 5052 } 5053 5054 private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) { 5055 uint keycode = cast(uint)ke.key; 5056 auto dpy = XDisplayConnection.get; 5057 return XKeysymToKeycode(dpy, keycode); 5058 } 5059 5060 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 5061 5062 private __gshared GlobalHotkey[ulong] globalHotkeyList; 5063 5064 NativeEventHandler getNativeEventHandler () { 5065 return delegate int (XEvent e) { 5066 if (e.type != EventType.KeyPress) return 1; 5067 auto kev = cast(const(XKeyEvent)*)&e; 5068 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 5069 if (auto ghkp = hash in globalHotkeyList) { 5070 try { 5071 ghkp.doHandle(); 5072 } catch (Exception e) { 5073 import core.stdc.stdio : stderr, fprintf; 5074 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 5075 } 5076 } 5077 return 1; 5078 }; 5079 } 5080 5081 private this () { 5082 auto dpy = XDisplayConnection.get; 5083 auto root = RootWindow(dpy, DefaultScreen(dpy)); 5084 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 5085 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 5086 } 5087 5088 /// Register new global hotkey with initialized `GlobalHotkey` object. 5089 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 5090 static void register (GlobalHotkey gh) { 5091 if (gh is null) return; 5092 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 5093 5094 auto dpy = XDisplayConnection.get; 5095 immutable keycode = keyEvent2KeyCode(gh.key); 5096 5097 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 5098 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 5099 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 5100 XSync(dpy, 0/*False*/); 5101 5102 Window root = RootWindow(dpy, DefaultScreen(dpy)); 5103 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 5104 ghfailed = false; 5105 foreach (immutable uint ormask; masklist[]) { 5106 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 5107 } 5108 XSync(dpy, 0/*False*/); 5109 XSetErrorHandler(savedErrorHandler); 5110 5111 if (ghfailed) { 5112 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 5113 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 5114 XSync(dpy, 0/*False*/); 5115 XSetErrorHandler(savedErrorHandler); 5116 throw new Exception("cannot register global hotkey"); 5117 } 5118 5119 globalHotkeyList[hash] = gh; 5120 } 5121 5122 /// Ditto 5123 static void register (const(char)[] akey, void delegate () ahandler) { 5124 register(new GlobalHotkey(akey, ahandler)); 5125 } 5126 5127 private static void removeByHash (ulong hash) { 5128 if (auto ghp = hash in globalHotkeyList) { 5129 auto dpy = XDisplayConnection.get; 5130 immutable keycode = keyEvent2KeyCode(ghp.key); 5131 Window root = RootWindow(dpy, DefaultScreen(dpy)); 5132 XSync(dpy, 0/*False*/); 5133 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 5134 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 5135 XSync(dpy, 0/*False*/); 5136 XSetErrorHandler(savedErrorHandler); 5137 globalHotkeyList.remove(hash); 5138 } 5139 } 5140 5141 /// Register new global hotkey with previously used `GlobalHotkey` object. 5142 /// It is safe to unregister unknown or invalid hotkey. 5143 static void unregister (GlobalHotkey gh) { 5144 //TODO: add second AA for faster search? prolly doesn't worth it. 5145 if (gh is null) return; 5146 foreach (const ref kv; globalHotkeyList.byKeyValue) { 5147 if (kv.value is gh) { 5148 removeByHash(kv.key); 5149 return; 5150 } 5151 } 5152 } 5153 5154 /// Ditto. 5155 static void unregister (const(char)[] key) { 5156 auto kev = KeyEvent.parse(key); 5157 immutable keycode = keyEvent2KeyCode(kev); 5158 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 5159 } 5160 } 5161 } 5162 5163 version(Windows) { 5164 /// Platform-specific for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application) 5165 void sendSyntheticInput(wstring s) { 5166 INPUT[] inputs; 5167 inputs.reserve(s.length * 2); 5168 5169 foreach(wchar c; s) { 5170 INPUT input; 5171 input.type = INPUT_KEYBOARD; 5172 input.ki.wScan = c; 5173 input.ki.dwFlags = KEYEVENTF_UNICODE; 5174 inputs ~= input; 5175 5176 input.ki.dwFlags |= KEYEVENTF_KEYUP; 5177 inputs ~= input; 5178 } 5179 5180 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 5181 throw new Exception("SendInput failed"); 5182 } 5183 } 5184 5185 5186 // global hotkey helper function 5187 5188 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. 5189 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 5190 __gshared int hotkeyId = 0; 5191 int id = ++hotkeyId; 5192 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 5193 throw new Exception("RegisterHotKey failed"); 5194 5195 __gshared void delegate()[WPARAM][HWND] handlers; 5196 5197 handlers[window.impl.hwnd][id] = handler; 5198 5199 int delegate(HWND, UINT, WPARAM, LPARAM) oldHandler; 5200 5201 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 5202 switch(msg) { 5203 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 5204 case WM_HOTKEY: 5205 if(auto list = hwnd in handlers) { 5206 if(auto h = wParam in *list) { 5207 (*h)(); 5208 return 0; 5209 } 5210 } 5211 goto default; 5212 default: 5213 } 5214 if(oldHandler) 5215 return oldHandler(hwnd, msg, wParam, lParam); 5216 return 1; // pass it on 5217 }; 5218 5219 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 5220 oldHandler = window.handleNativeEvent; 5221 window.handleNativeEvent = nativeEventHandler; 5222 } 5223 5224 return id; 5225 } 5226 5227 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by registerHotKey. 5228 void unregisterHotKey(SimpleWindow window, int id) { 5229 if(!UnregisterHotKey(window.impl.hwnd, id)) 5230 throw new Exception("UnregisterHotKey"); 5231 } 5232 } 5233 5234 version (X11) { 5235 pragma(lib, "dl"); 5236 import core.sys.posix.dlfcn; 5237 5238 /++ 5239 Allows for sending synthetic input to the X server via the Xtst 5240 extension. 5241 5242 Please remember user input is meant to be user - don't use this 5243 if you have some other alternative! 5244 5245 If you need this on Windows btw, the top-level [sendSyntheticInput] shows 5246 the Win32 api to start it, but I only did basics there, PR welcome if you like, 5247 it is an easy enough function to use. 5248 5249 History: Added May 17, 2020. 5250 +/ 5251 struct SyntheticInput { 5252 @disable this(); 5253 5254 private void* lib; 5255 private int* refcount; 5256 5257 private extern(C) { 5258 void function(Display*, uint keycode, bool press, arch_ulong delay) XTestFakeKeyEvent; 5259 void function(Display*, uint button, bool press, arch_ulong delay) XTestFakeButtonEvent; 5260 } 5261 5262 /// The dummy param must be 0. 5263 this(int dummy) { 5264 lib = dlopen("libXtst.so", RTLD_NOW); 5265 if(lib is null) 5266 throw new Exception("cannot load xtest lib extension"); 5267 scope(failure) 5268 dlclose(lib); 5269 5270 XTestFakeButtonEvent = cast(typeof(XTestFakeButtonEvent)) dlsym(lib, "XTestFakeButtonEvent"); 5271 XTestFakeKeyEvent = cast(typeof(XTestFakeKeyEvent)) dlsym(lib, "XTestFakeKeyEvent"); 5272 5273 if(XTestFakeKeyEvent is null) 5274 throw new Exception("No XTestFakeKeyEvent"); 5275 if(XTestFakeButtonEvent is null) 5276 throw new Exception("No XTestFakeButtonEvent"); 5277 5278 refcount = new int; 5279 *refcount = 1; 5280 } 5281 5282 this(this) { 5283 if(refcount) 5284 *refcount += 1; 5285 } 5286 5287 ~this() { 5288 if(refcount) { 5289 *refcount -= 1; 5290 if(*refcount == 0) 5291 // I commented this because if I close the lib before 5292 // XCloseDisplay, it is liable to segfault... so just 5293 // gonna keep it loaded if it is loaded, no big deal 5294 // anyway. 5295 {} // dlclose(lib); 5296 } 5297 } 5298 5299 /// This ONLY works with basic ascii! 5300 void sendSyntheticInput(string s) { 5301 int delay = 0; 5302 foreach(ch; s) { 5303 pressKey(cast(Key) ch, true, delay); 5304 pressKey(cast(Key) ch, false, delay); 5305 delay += 5; 5306 } 5307 } 5308 5309 /// 5310 void pressKey(Key key, bool pressed, int delay = 0) { 5311 XTestFakeKeyEvent(XDisplayConnection.get, XKeysymToKeycode(XDisplayConnection.get, key), pressed, delay + pressed ? 0 : 5); 5312 } 5313 5314 /// 5315 void pressMouseButton(MouseButton button, bool pressed, int delay = 0) { 5316 int btn; 5317 5318 switch(button) { 5319 case MouseButton.left: btn = 1; break; 5320 case MouseButton.middle: btn = 2; break; 5321 case MouseButton.right: btn = 3; break; 5322 case MouseButton.wheelUp: btn = 4; break; 5323 case MouseButton.wheelDown: btn = 5; break; 5324 case MouseButton.backButton: btn = 8; break; 5325 case MouseButton.forwardButton: btn = 9; break; 5326 default: 5327 } 5328 5329 assert(btn); 5330 5331 XTestFakeButtonEvent(XDisplayConnection.get, btn, pressed, delay); 5332 } 5333 5334 /// 5335 static void moveMouseArrowBy(int dx, int dy) { 5336 auto disp = XDisplayConnection.get(); 5337 XWarpPointer(disp, None, None, 0, 0, 0, 0, dx, dy); 5338 XFlush(disp); 5339 } 5340 5341 /// 5342 static void moveMouseArrowTo(int x, int y) { 5343 auto disp = XDisplayConnection.get(); 5344 auto root = RootWindow(disp, DefaultScreen(disp)); 5345 XWarpPointer(disp, None, root, 0, 0, 0, 0, x, y); 5346 XFlush(disp); 5347 } 5348 } 5349 } 5350 5351 5352 5353 /++ 5354 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 5355 5356 See_Also: 5357 $(LIST 5358 *[ScreenPainter] 5359 *[ScreenPainter.rasterOp] 5360 ) 5361 +/ 5362 enum RasterOp { 5363 normal, /// Replaces the pixel. 5364 xor, /// Uses bitwise xor to draw. 5365 } 5366 5367 // being phobos-free keeps the size WAY down 5368 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 5369 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 5370 package(arsd) const(wchar)* toWStringz(string s) { 5371 wstring r; 5372 foreach(dchar c; s) 5373 r ~= c; 5374 r ~= '\0'; 5375 return r.ptr; 5376 } 5377 private string[] split(in void[] a, char c) { 5378 string[] ret; 5379 size_t previous = 0; 5380 foreach(i, char ch; cast(ubyte[]) a) { 5381 if(ch == c) { 5382 ret ~= cast(string) a[previous .. i]; 5383 previous = i + 1; 5384 } 5385 } 5386 if(previous != a.length) 5387 ret ~= cast(string) a[previous .. $]; 5388 return ret; 5389 } 5390 5391 version(without_opengl) { 5392 enum OpenGlOptions { 5393 no, 5394 } 5395 } else { 5396 /++ 5397 Determines if you want an OpenGL context created on the new window. 5398 5399 5400 See more: [#topics-3d|in the 3d topic]. 5401 5402 --- 5403 import arsd.simpledisplay; 5404 void main() { 5405 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 5406 5407 // Set up the matrix 5408 window.setAsCurrentOpenGlContext(); // make this window active 5409 5410 // This is called on each frame, we will draw our scene 5411 window.redrawOpenGlScene = delegate() { 5412 5413 }; 5414 5415 window.eventLoop(0); 5416 } 5417 --- 5418 +/ 5419 enum OpenGlOptions { 5420 no, /// No OpenGL context is created 5421 yes, /// Yes, create an OpenGL context 5422 } 5423 5424 version(X11) { 5425 static if (!SdpyIsUsingIVGLBinds) { 5426 5427 5428 struct __GLXFBConfigRec {} 5429 alias GLXFBConfig = __GLXFBConfigRec*; 5430 5431 //pragma(lib, "GL"); 5432 //pragma(lib, "GLU"); 5433 interface GLX { 5434 extern(C) nothrow @nogc { 5435 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 5436 const int *attrib_list); 5437 5438 void glXCopyContext(Display *dpy, GLXContext src, 5439 GLXContext dst, arch_ulong mask); 5440 5441 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 5442 GLXContext share_list, Bool direct); 5443 5444 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 5445 Pixmap pixmap); 5446 5447 void glXDestroyContext(Display *dpy, GLXContext ctx); 5448 5449 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 5450 5451 int glXGetConfig(Display *dpy, XVisualInfo *vis, 5452 int attrib, int *value); 5453 5454 GLXContext glXGetCurrentContext(); 5455 5456 GLXDrawable glXGetCurrentDrawable(); 5457 5458 Bool glXIsDirect(Display *dpy, GLXContext ctx); 5459 5460 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 5461 GLXContext ctx); 5462 5463 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 5464 5465 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 5466 5467 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 5468 5469 void glXUseXFont(Font font, int first, int count, int list_base); 5470 5471 void glXWaitGL(); 5472 5473 void glXWaitX(); 5474 5475 5476 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 5477 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 5478 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 5479 5480 char* glXQueryExtensionsString (Display*, int); 5481 void* glXGetProcAddress (const(char)*); 5482 5483 } 5484 } 5485 5486 version(OSX) 5487 mixin DynamicLoad!(GLX, "GL", true) glx; 5488 else 5489 mixin DynamicLoad!(GLX, "GLX", true) glx; 5490 shared static this() { 5491 glx.loadDynamicLibrary(); 5492 } 5493 5494 alias glbindGetProcAddress = glXGetProcAddress; 5495 } 5496 } else version(Windows) { 5497 /* it is done below by interface GL */ 5498 } else 5499 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."); 5500 } 5501 5502 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 5503 alias Resizablity = Resizability; 5504 5505 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 5506 enum Resizability { 5507 fixedSize, /// the window cannot be resized 5508 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. 5509 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. 5510 5511 // FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events 5512 } 5513 5514 5515 /++ 5516 Alignment for $(ScreenPainter.drawText). Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 5517 +/ 5518 enum TextAlignment : uint { 5519 Left = 0, /// 5520 Center = 1, /// 5521 Right = 2, /// 5522 5523 VerticalTop = 0, /// 5524 VerticalCenter = 4, /// 5525 VerticalBottom = 8, /// 5526 } 5527 5528 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 5529 alias Rectangle = arsd.color.Rectangle; 5530 5531 5532 /++ 5533 Keyboard press and release events 5534 +/ 5535 struct KeyEvent { 5536 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 5537 Key key; 5538 ubyte hardwareCode; /// A platform and hardware specific code for the key 5539 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 5540 5541 dchar character; /// 5542 5543 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 5544 5545 SimpleWindow window; /// associated Window 5546 5547 // convert key event to simplified string representation a-la emacs 5548 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 5549 uint dpos = 0; 5550 void put (const(char)[] s...) nothrow @trusted { 5551 static if (growdest) { 5552 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 5553 } else { 5554 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 5555 } 5556 } 5557 5558 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 5559 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 5560 } 5561 5562 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 5563 5564 // put modifiers 5565 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 5566 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 5567 putMod(ModifierState.alt, Key.Alt, "Alt+"); 5568 putMod(ModifierState.windows, Key.Shift, "Windows+"); 5569 putMod(ModifierState.shift, Key.Shift, "Shift+"); 5570 5571 if (this.key) { 5572 foreach (string kn; __traits(allMembers, Key)) { 5573 if (this.key == __traits(getMember, Key, kn)) { 5574 // HACK! 5575 static if (kn == "N0") put("0"); 5576 else static if (kn == "N1") put("1"); 5577 else static if (kn == "N2") put("2"); 5578 else static if (kn == "N3") put("3"); 5579 else static if (kn == "N4") put("4"); 5580 else static if (kn == "N5") put("5"); 5581 else static if (kn == "N6") put("6"); 5582 else static if (kn == "N7") put("7"); 5583 else static if (kn == "N8") put("8"); 5584 else static if (kn == "N9") put("9"); 5585 else put(kn); 5586 return dest[0..dpos]; 5587 } 5588 } 5589 put("Unknown"); 5590 } else { 5591 if (dpos && dest[dpos-1] == '+') --dpos; 5592 } 5593 return dest[0..dpos]; 5594 } 5595 5596 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 5597 5598 /** Parse string into key name with modifiers. It accepts things like: 5599 * 5600 * C-H-1 -- emacs style (ctrl, and windows, and 1) 5601 * 5602 * Ctrl+Win+1 -- windows style 5603 * 5604 * Ctrl-Win-1 -- '-' is a valid delimiter too 5605 * 5606 * Ctrl Win 1 -- and space 5607 * 5608 * and even "Win + 1 + Ctrl". 5609 */ 5610 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 5611 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 5612 5613 // remove trailing spaces 5614 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 5615 5616 // tokens delimited by blank, '+', or '-' 5617 // null on eol 5618 const(char)[] getToken () nothrow @trusted @nogc { 5619 // remove leading spaces and delimiters 5620 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 5621 if (name.length == 0) return null; // oops, no more tokens 5622 // get token 5623 size_t epos = 0; 5624 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 5625 assert(epos > 0 && epos <= name.length); 5626 auto res = name[0..epos]; 5627 name = name[epos..$]; 5628 return res; 5629 } 5630 5631 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 5632 if (s0.length != s1.length) return false; 5633 foreach (immutable ci, char c0; s0) { 5634 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 5635 char c1 = s1[ci]; 5636 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 5637 if (c0 != c1) return false; 5638 } 5639 return true; 5640 } 5641 5642 if (ignoreModsOut !is null) *ignoreModsOut = false; 5643 if (updown !is null) *updown = -1; 5644 KeyEvent res; 5645 res.key = cast(Key)0; // just in case 5646 const(char)[] tk, tkn; // last token 5647 bool allowEmascStyle = true; 5648 bool ignoreModifiers = false; 5649 tokenloop: for (;;) { 5650 tk = tkn; 5651 tkn = getToken(); 5652 //k8: yay, i took "Bloody Mess" trait from Fallout! 5653 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 5654 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 5655 if (allowEmascStyle && tkn.length != 0) { 5656 if (tk.length == 1) { 5657 char mdc = tk[0]; 5658 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 5659 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 5660 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 5661 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 5662 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 5663 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 5664 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 5665 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 5666 } 5667 } 5668 allowEmascStyle = false; 5669 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 5670 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 5671 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 5672 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 5673 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 5674 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 5675 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 5676 if (tk.length == 0) continue; 5677 // try key name 5678 if (res.key == 0) { 5679 // little hack 5680 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 5681 final switch (tk[0]) { 5682 case '0': tk = "N0"; break; 5683 case '1': tk = "N1"; break; 5684 case '2': tk = "N2"; break; 5685 case '3': tk = "N3"; break; 5686 case '4': tk = "N4"; break; 5687 case '5': tk = "N5"; break; 5688 case '6': tk = "N6"; break; 5689 case '7': tk = "N7"; break; 5690 case '8': tk = "N8"; break; 5691 case '9': tk = "N9"; break; 5692 } 5693 } 5694 foreach (string kn; __traits(allMembers, Key)) { 5695 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 5696 } 5697 } 5698 // unknown or duplicate key name, get out of here 5699 break; 5700 } 5701 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 5702 return res; // something 5703 } 5704 5705 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 5706 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 5707 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 5708 if (kk == k) { mask |= mst; kk = cast(Key)0; } 5709 } 5710 bool ignoreMods; 5711 int updown; 5712 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 5713 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 5714 if (this.key != ke.key) { 5715 // things like "ctrl+alt" are complicated 5716 uint tkm = this.modifierState&modmask; 5717 uint kkm = ke.modifierState&modmask; 5718 Key tk = this.key; 5719 // ke 5720 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 5721 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 5722 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 5723 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 5724 // this 5725 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 5726 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 5727 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 5728 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 5729 return (tk == ke.key && tkm == kkm); 5730 } 5731 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 5732 } 5733 } 5734 5735 /// sets the application name. 5736 @property string ApplicationName(string name) { 5737 return _applicationName = name; 5738 } 5739 5740 string _applicationName; 5741 5742 /// ditto 5743 @property string ApplicationName() { 5744 if(_applicationName is null) { 5745 import core.runtime; 5746 return Runtime.args[0]; 5747 } 5748 return _applicationName; 5749 } 5750 5751 5752 /// Type of a [MouseEvent] 5753 enum MouseEventType : int { 5754 motion = 0, /// The mouse moved inside the window 5755 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 5756 buttonReleased = 2, /// A mouse button was released 5757 } 5758 5759 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 5760 /++ 5761 Listen for this on your event listeners if you are interested in mouse action. 5762 5763 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. 5764 5765 Examples: 5766 5767 This will draw boxes on the window with the mouse as you hold the left button. 5768 --- 5769 import arsd.simpledisplay; 5770 5771 void main() { 5772 auto window = new SimpleWindow(); 5773 5774 window.eventLoop(0, 5775 (MouseEvent ev) { 5776 if(ev.modifierState & ModifierState.leftButtonDown) { 5777 auto painter = window.draw(); 5778 painter.fillColor = Color.red; 5779 painter.outlineColor = Color.black; 5780 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 5781 } 5782 } 5783 ); 5784 } 5785 --- 5786 +/ 5787 struct MouseEvent { 5788 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 5789 5790 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. 5791 int y; /// Current Y position of the cursor when the event fired. 5792 5793 int dx; /// Change in X position since last report 5794 int dy; /// Change in Y position since last report 5795 5796 MouseButton button; /// See [MouseButton] 5797 int modifierState; /// See [ModifierState] 5798 5799 /// Returns a linear representation of mouse button, 5800 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 5801 /// 5802 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 5803 @property ubyte buttonLinear() const { 5804 import core.bitop; 5805 if(button == 0) 5806 return 0; 5807 return (bsf(button) + 1) & 0b1111; 5808 } 5809 5810 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 5811 5812 SimpleWindow window; /// The window in which the event happened. 5813 5814 Point globalCoordinates() { 5815 Point p; 5816 if(window is null) 5817 throw new Exception("wtf"); 5818 static if(UsingSimpledisplayX11) { 5819 Window child; 5820 XTranslateCoordinates( 5821 XDisplayConnection.get, 5822 window.impl.window, 5823 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 5824 x, y, &p.x, &p.y, &child); 5825 return p; 5826 } else version(Windows) { 5827 POINT[1] points; 5828 points[0].x = x; 5829 points[0].y = y; 5830 MapWindowPoints( 5831 window.impl.hwnd, 5832 null, 5833 points.ptr, 5834 points.length 5835 ); 5836 p.x = points[0].x; 5837 p.y = points[0].y; 5838 5839 return p; 5840 } else version(OSXCocoa) { 5841 throw new NotYetImplementedException(); 5842 } else static assert(0); 5843 } 5844 5845 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 5846 5847 /** 5848 can contain emacs-like modifier prefix 5849 case-insensitive names: 5850 lmbX/leftX 5851 rmbX/rightX 5852 mmbX/middleX 5853 wheelX 5854 motion (no prefix allowed) 5855 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 5856 */ 5857 static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 5858 if (str.length == 0) return false; // just in case 5859 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 5860 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 5861 auto anchor = str; 5862 uint mods = 0; // uint.max == any 5863 // interesting bits in kmod 5864 uint kmodmask = 5865 ModifierState.shift| 5866 ModifierState.ctrl| 5867 ModifierState.alt| 5868 ModifierState.windows| 5869 ModifierState.leftButtonDown| 5870 ModifierState.middleButtonDown| 5871 ModifierState.rightButtonDown| 5872 0; 5873 uint lastButt = uint.max; // otherwise, bit 31 means "down" 5874 bool wasButtons = false; 5875 while (str.length) { 5876 if (str.ptr[0] <= ' ') { 5877 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 5878 continue; 5879 } 5880 // one-letter modifier? 5881 if (str.length >= 2 && str.ptr[1] == '-') { 5882 switch (str.ptr[0]) { 5883 case '*': // "any" modifier (cannot be undone) 5884 mods = mods.max; 5885 break; 5886 case 'C': case 'c': // emacs "ctrl" 5887 if (mods != mods.max) mods |= ModifierState.ctrl; 5888 break; 5889 case 'M': case 'm': // emacs "meta" 5890 if (mods != mods.max) mods |= ModifierState.alt; 5891 break; 5892 case 'S': case 's': // emacs "shift" 5893 if (mods != mods.max) mods |= ModifierState.shift; 5894 break; 5895 case 'H': case 'h': // emacs "hyper" (aka winkey) 5896 if (mods != mods.max) mods |= ModifierState.windows; 5897 break; 5898 default: 5899 return false; // unknown modifier 5900 } 5901 str = str[2..$]; 5902 continue; 5903 } 5904 // word 5905 char[16] buf = void; // locased 5906 auto wep = 0; 5907 while (str.length) { 5908 immutable char ch = str.ptr[0]; 5909 if (ch <= ' ' || ch == '-') break; 5910 str = str[1..$]; 5911 if (wep > buf.length) return false; // too long 5912 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 5913 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 5914 else return false; // invalid char 5915 } 5916 if (wep == 0) return false; // just in case 5917 uint bnum; 5918 enum UpDown { None = -1, Up, Down, Any } 5919 auto updown = UpDown.None; // 0: up; 1: down 5920 switch (buf[0..wep]) { 5921 // left button 5922 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 5923 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 5924 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 5925 case "lmb": case "left": bnum = 0; break; 5926 // middle button 5927 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 5928 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 5929 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 5930 case "mmb": case "middle": bnum = 1; break; 5931 // right button 5932 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 5933 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 5934 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 5935 case "rmb": case "right": bnum = 2; break; 5936 // wheel 5937 case "wheelup": updown = UpDown.Up; goto case "wheel"; 5938 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 5939 case "wheelany": updown = UpDown.Any; goto case "wheel"; 5940 case "wheel": bnum = 3; break; 5941 // motion 5942 case "motion": bnum = 7; break; 5943 // unknown 5944 default: return false; 5945 } 5946 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 5947 // parse possible "-up" or "-down" 5948 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 5949 wep = 0; 5950 foreach (immutable idx, immutable char ch; str[1..$]) { 5951 if (ch <= ' ' || ch == '-') break; 5952 assert(idx == wep); // for now; trick 5953 if (wep > buf.length) { wep = 0; break; } // too long 5954 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 5955 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 5956 else { wep = 0; break; } // invalid char 5957 } 5958 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 5959 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 5960 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 5961 // remove parsed part 5962 if (updown != UpDown.None) str = str[wep+1..$]; 5963 } 5964 if (updown == UpDown.None) { 5965 updown = UpDown.Down; 5966 } 5967 wasButtons = wasButtons || (bnum <= 2); 5968 //assert(updown != UpDown.None); 5969 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 5970 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 5971 if (lastButt != lastButt.max) { 5972 if ((lastButt&0xff) >= 3) return false; // wheel or motion 5973 if (mods != mods.max) { 5974 uint butbit = 0; 5975 final switch (lastButt&0x03) { 5976 case 0: butbit = ModifierState.leftButtonDown; break; 5977 case 1: butbit = ModifierState.middleButtonDown; break; 5978 case 2: butbit = ModifierState.rightButtonDown; break; 5979 } 5980 if (lastButt&Flag.Down) mods |= butbit; 5981 else if (lastButt&Flag.Up) mods &= ~butbit; 5982 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 5983 } 5984 } 5985 // remember last button 5986 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 5987 } 5988 // no button -- nothing to do 5989 if (lastButt == lastButt.max) return false; 5990 // done parsing, check if something's left 5991 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 5992 // remove action button from mask 5993 if ((lastButt&0xff) < 3) { 5994 final switch (lastButt&0x03) { 5995 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 5996 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 5997 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 5998 } 5999 } 6000 // special case: "Motion" means "ignore buttons" 6001 if ((lastButt&0xff) == 7 && !wasButtons) { 6002 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 6003 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 6004 } 6005 uint kmod = event.modifierState&kmodmask; 6006 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 6007 // check modifier state 6008 if (mods != mods.max) { 6009 if (kmod != mods) return false; 6010 } 6011 // now check type 6012 if ((lastButt&0xff) == 7) { 6013 // motion 6014 if (event.type != MouseEventType.motion) return false; 6015 } else if ((lastButt&0xff) == 3) { 6016 // wheel 6017 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 6018 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 6019 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 6020 return false; 6021 } else { 6022 // buttons 6023 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 6024 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 6025 { 6026 return false; 6027 } 6028 // button number 6029 switch (lastButt&0x03) { 6030 case 0: if (event.button != MouseButton.left) return false; break; 6031 case 1: if (event.button != MouseButton.middle) return false; break; 6032 case 2: if (event.button != MouseButton.right) return false; break; 6033 default: return false; 6034 } 6035 } 6036 return true; 6037 } 6038 } 6039 6040 version(arsd_mevent_strcmp_test) unittest { 6041 MouseEvent event; 6042 event.type = MouseEventType.buttonPressed; 6043 event.button = MouseButton.left; 6044 event.modifierState = ModifierState.ctrl; 6045 assert(event == "C-LMB"); 6046 assert(event != "C-LMBUP"); 6047 assert(event != "C-LMB-UP"); 6048 assert(event != "C-S-LMB"); 6049 assert(event == "*-LMB"); 6050 assert(event != "*-LMB-UP"); 6051 6052 event.type = MouseEventType.buttonReleased; 6053 assert(event != "C-LMB"); 6054 assert(event == "C-LMBUP"); 6055 assert(event == "C-LMB-UP"); 6056 assert(event != "C-S-LMB"); 6057 assert(event != "*-LMB"); 6058 assert(event == "*-LMB-UP"); 6059 6060 event.button = MouseButton.right; 6061 event.modifierState |= ModifierState.shift; 6062 event.type = MouseEventType.buttonPressed; 6063 assert(event != "C-LMB"); 6064 assert(event != "C-LMBUP"); 6065 assert(event != "C-LMB-UP"); 6066 assert(event != "C-S-LMB"); 6067 assert(event != "*-LMB"); 6068 assert(event != "*-LMB-UP"); 6069 6070 assert(event != "C-RMB"); 6071 assert(event != "C-RMBUP"); 6072 assert(event != "C-RMB-UP"); 6073 assert(event == "C-S-RMB"); 6074 assert(event == "*-RMB"); 6075 assert(event != "*-RMB-UP"); 6076 } 6077 6078 /// This gives a few more options to drawing lines and such 6079 struct Pen { 6080 Color color; /// the foreground color 6081 int width = 1; /// width of the line 6082 Style style; /// See [Style] 6083 /+ 6084 // From X.h 6085 6086 #define LineSolid 0 6087 #define LineOnOffDash 1 6088 #define LineDoubleDash 2 6089 LineDou- The full path of the line is drawn, but the 6090 bleDash even dashes are filled differently from the 6091 odd dashes (see fill-style) with CapButt 6092 style used where even and odd dashes meet. 6093 6094 6095 6096 /* capStyle */ 6097 6098 #define CapNotLast 0 6099 #define CapButt 1 6100 #define CapRound 2 6101 #define CapProjecting 3 6102 6103 /* joinStyle */ 6104 6105 #define JoinMiter 0 6106 #define JoinRound 1 6107 #define JoinBevel 2 6108 6109 /* fillStyle */ 6110 6111 #define FillSolid 0 6112 #define FillTiled 1 6113 #define FillStippled 2 6114 #define FillOpaqueStippled 3 6115 6116 6117 +/ 6118 /// Style of lines drawn 6119 enum Style { 6120 Solid, /// a solid line 6121 Dashed, /// a dashed line 6122 Dotted, /// a dotted line 6123 } 6124 } 6125 6126 6127 /++ 6128 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 6129 6130 6131 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 6132 6133 $(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.) 6134 6135 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. 6136 6137 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 6138 6139 $(IMPORTANT `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. 6140 6141 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! 6142 6143 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!) 6144 6145 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 6146 6147 --- 6148 auto image = new Image(256, 256); 6149 scope(exit) destroy(image); 6150 --- 6151 6152 As long as you don't hold on to it outside the scope. 6153 6154 I might change it to be an owned pointer at some point in the future. 6155 6156 ) 6157 6158 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 6159 you can also often get a fair amount of speedup by getting the raw data format and 6160 writing some custom code. 6161 6162 FIXME INSERT EXAMPLES HERE 6163 6164 6165 +/ 6166 final class Image { 6167 /// 6168 this(int width, int height, bool forcexshm=false) { 6169 this.width = width; 6170 this.height = height; 6171 6172 impl.createImage(width, height, forcexshm); 6173 } 6174 6175 /// 6176 this(Size size, bool forcexshm=false) { 6177 this(size.width, size.height, forcexshm); 6178 } 6179 6180 ~this() { 6181 impl.dispose(); 6182 } 6183 6184 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 6185 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 6186 pure const @system nothrow { 6187 /* 6188 To use these to draw a blue rectangle with size WxH at position X,Y... 6189 6190 // make certain that it will fit before we proceed 6191 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! 6192 6193 // gather all the values you'll need up front. These can be kept until the image changes size if you want 6194 // (though calculating them isn't really that expensive). 6195 auto nextLineAdjustment = img.adjustmentForNextLine(); 6196 auto offR = img.redByteOffset(); 6197 auto offB = img.blueByteOffset(); 6198 auto offG = img.greenByteOffset(); 6199 auto bpp = img.bytesPerPixel(); 6200 6201 auto data = img.getDataPointer(); 6202 6203 // figure out the starting byte offset 6204 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 6205 6206 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 6207 6208 // and now our drawing loop for the rectangle 6209 foreach(y; 0 .. H) { 6210 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 6211 foreach(x; 0 .. W) { 6212 // write our color 6213 data[offR] = 0; 6214 data[offG] = 0; 6215 data[offB] = 255; 6216 6217 data += bpp; // moving to the next pixel is just an addition... 6218 } 6219 startOfLine += nextLineAdjustment; 6220 } 6221 6222 6223 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 6224 6225 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 6226 can be made into a bitmask or something so we can write them as *uint... 6227 */ 6228 6229 /// 6230 int offsetForTopLeftPixel() { 6231 version(X11) { 6232 return 0; 6233 } else version(Windows) { 6234 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 6235 } else version(OSXCocoa) { 6236 return 0 ; //throw new NotYetImplementedException(); 6237 } else static assert(0, "fill in this info for other OSes"); 6238 } 6239 6240 /// 6241 int offsetForPixel(int x, int y) { 6242 version(X11) { 6243 auto offset = (y * width + x) * 4; 6244 return offset; 6245 } else version(Windows) { 6246 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 6247 // remember, bmps are upside down 6248 auto offset = itemsPerLine * (height - y - 1) + x * 3; 6249 return offset; 6250 } else version(OSXCocoa) { 6251 return 0 ; //throw new NotYetImplementedException(); 6252 } else static assert(0, "fill in this info for other OSes"); 6253 } 6254 6255 /// 6256 int adjustmentForNextLine() { 6257 version(X11) { 6258 return width * 4; 6259 } else version(Windows) { 6260 // windows bmps are upside down, so the adjustment is actually negative 6261 return -((cast(int) width * 3 + 3) / 4) * 4; 6262 } else version(OSXCocoa) { 6263 return 0 ; //throw new NotYetImplementedException(); 6264 } else static assert(0, "fill in this info for other OSes"); 6265 } 6266 6267 /// once you have the position of a pixel, use these to get to the proper color 6268 int redByteOffset() { 6269 version(X11) { 6270 return 2; 6271 } else version(Windows) { 6272 return 2; 6273 } else version(OSXCocoa) { 6274 return 0 ; //throw new NotYetImplementedException(); 6275 } else static assert(0, "fill in this info for other OSes"); 6276 } 6277 6278 /// 6279 int greenByteOffset() { 6280 version(X11) { 6281 return 1; 6282 } else version(Windows) { 6283 return 1; 6284 } else version(OSXCocoa) { 6285 return 0 ; //throw new NotYetImplementedException(); 6286 } else static assert(0, "fill in this info for other OSes"); 6287 } 6288 6289 /// 6290 int blueByteOffset() { 6291 version(X11) { 6292 return 0; 6293 } else version(Windows) { 6294 return 0; 6295 } else version(OSXCocoa) { 6296 return 0 ; //throw new NotYetImplementedException(); 6297 } else static assert(0, "fill in this info for other OSes"); 6298 } 6299 } 6300 6301 /// 6302 final void putPixel(int x, int y, Color c) { 6303 if(x < 0 || x >= width) 6304 return; 6305 if(y < 0 || y >= height) 6306 return; 6307 6308 impl.setPixel(x, y, c); 6309 } 6310 6311 /// 6312 final Color getPixel(int x, int y) { 6313 if(x < 0 || x >= width) 6314 return Color.transparent; 6315 if(y < 0 || y >= height) 6316 return Color.transparent; 6317 6318 version(OSXCocoa) throw new NotYetImplementedException(); else 6319 return impl.getPixel(x, y); 6320 } 6321 6322 /// 6323 final void opIndexAssign(Color c, int x, int y) { 6324 putPixel(x, y, c); 6325 } 6326 6327 /// 6328 TrueColorImage toTrueColorImage() { 6329 auto tci = new TrueColorImage(width, height); 6330 convertToRgbaBytes(tci.imageData.bytes); 6331 return tci; 6332 } 6333 6334 /// 6335 static Image fromMemoryImage(MemoryImage i) { 6336 auto tci = i.getAsTrueColorImage(); 6337 auto img = new Image(tci.width, tci.height); 6338 img.setRgbaBytes(tci.imageData.bytes); 6339 return img; 6340 } 6341 6342 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 6343 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 6344 /// if you pass null, it will allocate a new one. 6345 ubyte[] getRgbaBytes(ubyte[] where = null) { 6346 if(where is null) 6347 where = new ubyte[this.width*this.height*4]; 6348 convertToRgbaBytes(where); 6349 return where; 6350 } 6351 6352 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 6353 void setRgbaBytes(in ubyte[] from ) { 6354 assert(from.length == this.width * this.height * 4); 6355 setFromRgbaBytes(from); 6356 } 6357 6358 // FIXME: make properly cross platform by getting rgba right 6359 6360 /// warning: this is not portable across platforms because the data format can change 6361 ubyte* getDataPointer() { 6362 return impl.rawData; 6363 } 6364 6365 /// for use with getDataPointer 6366 final int bytesPerLine() const pure @safe nothrow { 6367 version(Windows) 6368 return ((cast(int) width * 3 + 3) / 4) * 4; 6369 else version(X11) 6370 return 4 * width; 6371 else version(OSXCocoa) 6372 return 4 * width; 6373 else static assert(0); 6374 } 6375 6376 /// for use with getDataPointer 6377 final int bytesPerPixel() const pure @safe nothrow { 6378 version(Windows) 6379 return 3; 6380 else version(X11) 6381 return 4; 6382 else version(OSXCocoa) 6383 return 4; 6384 else static assert(0); 6385 } 6386 6387 /// 6388 immutable int width; 6389 6390 /// 6391 immutable int height; 6392 //private: 6393 mixin NativeImageImplementation!() impl; 6394 } 6395 6396 /// A convenience function to pop up a window displaying the image. 6397 /// If you pass a win, it will draw the image in it. Otherwise, it will 6398 /// create a window with the size of the image and run its event loop, closing 6399 /// when a key is pressed. 6400 void displayImage(Image image, SimpleWindow win = null) { 6401 if(win is null) { 6402 win = new SimpleWindow(image); 6403 { 6404 auto p = win.draw; 6405 p.drawImage(Point(0, 0), image); 6406 } 6407 win.eventLoop(0, 6408 (KeyEvent ev) { 6409 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 6410 } ); 6411 } else { 6412 win.image = image; 6413 } 6414 } 6415 6416 enum FontWeight : int { 6417 dontcare = 0, 6418 thin = 100, 6419 extralight = 200, 6420 light = 300, 6421 regular = 400, 6422 medium = 500, 6423 semibold = 600, 6424 bold = 700, 6425 extrabold = 800, 6426 heavy = 900 6427 } 6428 6429 /++ 6430 Represents a font loaded off the operating system or the X server. 6431 6432 6433 While the api here is unified cross platform, the fonts are not necessarily 6434 available, even across machines of the same platform, so be sure to always check 6435 for null (using [isNull]) and have a fallback plan. 6436 6437 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 6438 6439 Worst case, a null font will automatically fall back to the default font loaded 6440 for your system. 6441 +/ 6442 class OperatingSystemFont { 6443 6444 version(X11) { 6445 XFontStruct* font; 6446 XFontSet fontset; 6447 } else version(Windows) { 6448 HFONT font; 6449 int width_; 6450 int height_; 6451 } else version(OSXCocoa) { 6452 // FIXME 6453 } else static assert(0); 6454 6455 /// 6456 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 6457 load(name, size, weight, italic); 6458 } 6459 6460 /// 6461 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 6462 unload(); 6463 version(X11) { 6464 string weightstr; 6465 with(FontWeight) 6466 final switch(weight) { 6467 case dontcare: weightstr = "*"; break; 6468 case thin: weightstr = "extralight"; break; 6469 case extralight: weightstr = "extralight"; break; 6470 case light: weightstr = "light"; break; 6471 case regular: weightstr = "regular"; break; 6472 case medium: weightstr = "medium"; break; 6473 case semibold: weightstr = "demibold"; break; 6474 case bold: weightstr = "bold"; break; 6475 case extrabold: weightstr = "demibold"; break; 6476 case heavy: weightstr = "black"; break; 6477 } 6478 string sizestr; 6479 if(size == 0) 6480 sizestr = "*"; 6481 else if(size < 10) 6482 sizestr = "" ~ cast(char)(size % 10 + '0'); 6483 else 6484 sizestr = "" ~ cast(char)(size / 10 + '0') ~ cast(char)(size % 10 + '0'); 6485 auto xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 6486 6487 //import std.stdio; writeln(xfontstr); 6488 6489 auto display = XDisplayConnection.get; 6490 6491 font = XLoadQueryFont(display, xfontstr.ptr); 6492 if(font is null) 6493 return false; 6494 6495 char** lol; 6496 int lol2; 6497 char* lol3; 6498 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 6499 } else version(Windows) { 6500 WCharzBuffer buffer = WCharzBuffer(name); 6501 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 6502 6503 TEXTMETRIC tm; 6504 auto dc = GetDC(null); 6505 SelectObject(dc, font); 6506 GetTextMetrics(dc, &tm); 6507 ReleaseDC(null, dc); 6508 6509 width_ = tm.tmAveCharWidth; 6510 height_ = tm.tmHeight; 6511 } else version(OSXCocoa) { 6512 // FIXME 6513 } else static assert(0); 6514 6515 return !isNull(); 6516 } 6517 6518 /// 6519 void unload() { 6520 if(isNull()) 6521 return; 6522 6523 version(X11) { 6524 auto display = XDisplayConnection.display; 6525 6526 if(display is null) 6527 return; 6528 6529 if(font) 6530 XFreeFont(display, font); 6531 if(fontset) 6532 XFreeFontSet(display, fontset); 6533 6534 font = null; 6535 fontset = null; 6536 } else version(Windows) { 6537 DeleteObject(font); 6538 font = null; 6539 } else version(OSXCocoa) { 6540 // FIXME 6541 } else static assert(0); 6542 } 6543 6544 // Assuming monospace!!!!! 6545 // added March 26, 2020 6546 int averageWidth() { 6547 version(X11) 6548 return font.max_bounds.width; 6549 else version(Windows) 6550 return width_; 6551 else assert(0); 6552 } 6553 6554 // Assuming monospace!!!!! 6555 // added March 26, 2020 6556 int height() { 6557 version(X11) 6558 return font.max_bounds.ascent + font.max_bounds.descent; 6559 else version(Windows) 6560 return height_; 6561 else assert(0); 6562 } 6563 6564 /// FIXME not implemented 6565 void loadDefault() { 6566 6567 } 6568 6569 /// 6570 bool isNull() { 6571 version(OSXCocoa) throw new NotYetImplementedException(); else 6572 return font is null; 6573 } 6574 6575 /* Metrics */ 6576 /+ 6577 GetABCWidth 6578 GetKerningPairs 6579 6580 if I do it right, I can size it all here, and match 6581 what happens when I draw the full string with the OS functions. 6582 6583 subclasses might do the same thing while getting the glyphs on images 6584 +/ 6585 struct GlyphInfo { 6586 int glyph; 6587 6588 size_t stringIdxStart; 6589 size_t stringIdxEnd; 6590 6591 Rectangle boundingBox; 6592 } 6593 GlyphInfo[] getCharBoxes() { 6594 return null; 6595 6596 } 6597 6598 ~this() { 6599 unload(); 6600 } 6601 } 6602 6603 /** 6604 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 6605 than constructing it directly. Then, it is reference counted so you can pass it 6606 at around and when the last ref goes out of scope, the buffered drawing activities 6607 are all carried out. 6608 6609 6610 Most functions use the outlineColor instead of taking a color themselves. 6611 ScreenPainter is reference counted and draws its buffer to the screen when its 6612 final reference goes out of scope. 6613 */ 6614 struct ScreenPainter { 6615 CapableOfBeingDrawnUpon window; 6616 this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle) { 6617 this.window = window; 6618 if(window.closed) 6619 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 6620 currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 6621 if(window.activeScreenPainter !is null) { 6622 impl = window.activeScreenPainter; 6623 if(impl.referenceCount == 0) { 6624 impl.window = window; 6625 impl.create(handle); 6626 } 6627 impl.referenceCount++; 6628 // writeln("refcount ++ ", impl.referenceCount); 6629 } else { 6630 impl = new ScreenPainterImplementation; 6631 impl.window = window; 6632 impl.create(handle); 6633 impl.referenceCount = 1; 6634 window.activeScreenPainter = impl; 6635 // writeln("constructed"); 6636 } 6637 6638 copyActiveOriginals(); 6639 } 6640 6641 private Pen originalPen; 6642 private Color originalFillColor; 6643 private arsd.color.Rectangle originalClipRectangle; 6644 void copyActiveOriginals() { 6645 if(impl is null) return; 6646 originalPen = impl._activePen; 6647 originalFillColor = impl._fillColor; 6648 originalClipRectangle = impl._clipRectangle; 6649 } 6650 6651 ~this() { 6652 if(impl is null) return; 6653 impl.referenceCount--; 6654 //writeln("refcount -- ", impl.referenceCount); 6655 if(impl.referenceCount == 0) { 6656 //writeln("destructed"); 6657 impl.dispose(); 6658 *window.activeScreenPainter = ScreenPainterImplementation.init; 6659 //import std.stdio; writeln("paint finished"); 6660 } else { 6661 // there is still an active reference, reset stuff so the 6662 // next user doesn't get weirdness via the reference 6663 this.rasterOp = RasterOp.normal; 6664 pen = originalPen; 6665 fillColor = originalFillColor; 6666 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 6667 } 6668 } 6669 6670 this(this) { 6671 if(impl is null) return; 6672 impl.referenceCount++; 6673 //writeln("refcount ++ ", impl.referenceCount); 6674 6675 copyActiveOriginals(); 6676 } 6677 6678 private int _originX; 6679 private int _originY; 6680 @property int originX() { return _originX; } 6681 @property int originY() { return _originY; } 6682 @property int originX(int a) { 6683 //currentClipRectangle.left += a - _originX; 6684 //currentClipRectangle.right += a - _originX; 6685 _originX = a; 6686 return _originX; 6687 } 6688 @property int originY(int a) { 6689 //currentClipRectangle.top += a - _originY; 6690 //currentClipRectangle.bottom += a - _originY; 6691 _originY = a; 6692 return _originY; 6693 } 6694 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 6695 private void transform(ref Point p) { 6696 if(impl is null) return; 6697 p.x += _originX; 6698 p.y += _originY; 6699 } 6700 6701 // this needs to be checked BEFORE the originX/Y transformation 6702 private bool isClipped(Point p) { 6703 return !currentClipRectangle.contains(p); 6704 } 6705 private bool isClipped(Point p, int width, int height) { 6706 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 6707 } 6708 private bool isClipped(Point p, Size s) { 6709 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 6710 } 6711 private bool isClipped(Point p, Point p2) { 6712 // need to ensure the end points are actually included inside, so the +1 does that 6713 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 6714 } 6715 6716 6717 /// Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 6718 void setClipRectangle(Point pt, int width, int height) { 6719 if(impl is null) return; 6720 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 6721 return; // no need to do anything 6722 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 6723 transform(pt); 6724 6725 impl.setClipRectangle(pt.x, pt.y, width, height); 6726 } 6727 6728 /// ditto 6729 void setClipRectangle(arsd.color.Rectangle rect) { 6730 if(impl is null) return; 6731 setClipRectangle(rect.upperLeft, rect.width, rect.height); 6732 } 6733 6734 /// 6735 void setFont(OperatingSystemFont font) { 6736 if(impl is null) return; 6737 impl.setFont(font); 6738 } 6739 6740 /// 6741 int fontHeight() { 6742 if(impl is null) return 0; 6743 return impl.fontHeight(); 6744 } 6745 6746 private Pen activePen; 6747 6748 /// 6749 @property void pen(Pen p) { 6750 if(impl is null) return; 6751 activePen = p; 6752 impl.pen(p); 6753 } 6754 6755 /// 6756 @scriptable 6757 @property void outlineColor(Color c) { 6758 if(impl is null) return; 6759 if(activePen.color == c) 6760 return; 6761 activePen.color = c; 6762 impl.pen(activePen); 6763 } 6764 6765 /// 6766 @scriptable 6767 @property void fillColor(Color c) { 6768 if(impl is null) return; 6769 impl.fillColor(c); 6770 } 6771 6772 /// 6773 @property void rasterOp(RasterOp op) { 6774 if(impl is null) return; 6775 impl.rasterOp(op); 6776 } 6777 6778 6779 void updateDisplay() { 6780 // FIXME this should do what the dtor does 6781 } 6782 6783 /// 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) 6784 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 6785 if(impl is null) return; 6786 if(isClipped(upperLeft, width, height)) return; 6787 transform(upperLeft); 6788 version(Windows) { 6789 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 6790 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 6791 RECT clip = scroll; 6792 RECT uncovered; 6793 HRGN hrgn; 6794 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 6795 throw new Exception("ScrollDC"); 6796 6797 } else version(X11) { 6798 // FIXME: clip stuff outside this rectangle 6799 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 6800 } else version(OSXCocoa) { 6801 throw new NotYetImplementedException(); 6802 } else static assert(0); 6803 } 6804 6805 /// 6806 void clear(Color color = Color.white()) { 6807 if(impl is null) return; 6808 fillColor = color; 6809 outlineColor = color; 6810 drawRectangle(Point(0, 0), window.width, window.height); 6811 } 6812 6813 /// 6814 version(OSXCocoa) {} else // NotYetImplementedException 6815 void drawPixmap(Sprite s, Point upperLeft) { 6816 if(impl is null) return; 6817 if(isClipped(upperLeft, s.width, s.height)) return; 6818 transform(upperLeft); 6819 impl.drawPixmap(s, upperLeft.x, upperLeft.y); 6820 } 6821 6822 /// 6823 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 6824 if(impl is null) return; 6825 //if(isClipped(upperLeft, w, h)) return; // FIXME 6826 transform(upperLeft); 6827 if(w == 0 || w > i.width) 6828 w = i.width; 6829 if(h == 0 || h > i.height) 6830 h = i.height; 6831 if(upperLeftOfImage.x < 0) 6832 upperLeftOfImage.x = 0; 6833 if(upperLeftOfImage.y < 0) 6834 upperLeftOfImage.y = 0; 6835 6836 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 6837 } 6838 6839 /// 6840 Size textSize(in char[] text) { 6841 if(impl is null) return Size(0, 0); 6842 return impl.textSize(text); 6843 } 6844 6845 /// 6846 @scriptable 6847 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 6848 if(impl is null) return; 6849 if(lowerRight.x != 0 || lowerRight.y != 0) { 6850 if(isClipped(upperLeft, lowerRight)) return; 6851 transform(lowerRight); 6852 } else { 6853 if(isClipped(upperLeft, textSize(text))) return; 6854 } 6855 transform(upperLeft); 6856 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 6857 } 6858 6859 /++ 6860 Draws text using a custom font. 6861 6862 This is still MAJOR work in progress. 6863 6864 Creating a [DrawableFont] can be tricky and require additional dependencies. 6865 +/ 6866 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 6867 if(impl is null) return; 6868 if(isClipped(upperLeft, Point(int.max, int.max))) return; 6869 transform(upperLeft); 6870 font.drawString(this, upperLeft, text); 6871 } 6872 6873 static struct TextDrawingContext { 6874 Point boundingBoxUpperLeft; 6875 Point boundingBoxLowerRight; 6876 6877 Point currentLocation; 6878 6879 Point lastDrewUpperLeft; 6880 Point lastDrewLowerRight; 6881 6882 // how do i do right aligned rich text? 6883 // i kinda want to do a pre-made drawing then right align 6884 // draw the whole block. 6885 // 6886 // That's exactly the diff: inline vs block stuff. 6887 6888 // I need to get coordinates of an inline section out too, 6889 // not just a bounding box, but a series of bounding boxes 6890 // should be ok. Consider what's needed to detect a click 6891 // on a link in the middle of a paragraph breaking a line. 6892 // 6893 // Generally, we should be able to get the rectangles of 6894 // any portion we draw. 6895 // 6896 // It also needs to tell what text is left if it overflows 6897 // out of the box, so we can do stuff like float images around 6898 // it. It should not attempt to draw a letter that would be 6899 // clipped. 6900 // 6901 // I might also turn off word wrap stuff. 6902 } 6903 6904 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 6905 if(impl is null) return; 6906 // FIXME 6907 } 6908 6909 /// Drawing an individual pixel is slow. Avoid it if possible. 6910 void drawPixel(Point where) { 6911 if(impl is null) return; 6912 if(isClipped(where)) return; 6913 transform(where); 6914 impl.drawPixel(where.x, where.y); 6915 } 6916 6917 6918 /// Draws a pen using the current pen / outlineColor 6919 @scriptable 6920 void drawLine(Point starting, Point ending) { 6921 if(impl is null) return; 6922 if(isClipped(starting, ending)) return; 6923 transform(starting); 6924 transform(ending); 6925 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 6926 } 6927 6928 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 6929 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 6930 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 6931 @scriptable 6932 void drawRectangle(Point upperLeft, int width, int height) { 6933 if(impl is null) return; 6934 if(isClipped(upperLeft, width, height)) return; 6935 transform(upperLeft); 6936 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 6937 } 6938 6939 /// ditto 6940 void drawRectangle(Point upperLeft, Size size) { 6941 if(impl is null) return; 6942 if(isClipped(upperLeft, size.width, size.height)) return; 6943 transform(upperLeft); 6944 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 6945 } 6946 6947 /// ditto 6948 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 6949 if(impl is null) return; 6950 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 6951 transform(upperLeft); 6952 transform(lowerRightInclusive); 6953 impl.drawRectangle(upperLeft.x, upperLeft.y, 6954 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 6955 } 6956 6957 /// Arguments are the points of the bounding rectangle 6958 void drawEllipse(Point upperLeft, Point lowerRight) { 6959 if(impl is null) return; 6960 if(isClipped(upperLeft, lowerRight)) return; 6961 transform(upperLeft); 6962 transform(lowerRight); 6963 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 6964 } 6965 6966 /++ 6967 start and finish are units of degrees * 64 6968 +/ 6969 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 6970 if(impl is null) return; 6971 // FIXME: not actually implemented 6972 if(isClipped(upperLeft, width, height)) return; 6973 transform(upperLeft); 6974 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 6975 } 6976 6977 //this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius 6978 void drawCircle(Point upperLeft, int diameter) { 6979 drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); 6980 } 6981 6982 /// . 6983 void drawPolygon(Point[] vertexes) { 6984 if(impl is null) return; 6985 assert(vertexes.length); 6986 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 6987 foreach(ref vertex; vertexes) { 6988 if(vertex.x < minX) 6989 minX = vertex.x; 6990 if(vertex.y < minY) 6991 minY = vertex.y; 6992 if(vertex.x > maxX) 6993 maxX = vertex.x; 6994 if(vertex.y > maxY) 6995 maxY = vertex.y; 6996 transform(vertex); 6997 } 6998 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 6999 impl.drawPolygon(vertexes); 7000 } 7001 7002 /// ditto 7003 void drawPolygon(Point[] vertexes...) { 7004 if(impl is null) return; 7005 drawPolygon(vertexes); 7006 } 7007 7008 7009 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 7010 7011 //mixin NativeScreenPainterImplementation!() impl; 7012 7013 7014 // HACK: if I mixin the impl directly, it won't let me override the copy 7015 // constructor! The linker complains about there being multiple definitions. 7016 // I'll make the best of it and reference count it though. 7017 ScreenPainterImplementation* impl; 7018 } 7019 7020 // HACK: I need a pointer to the implementation so it's separate 7021 struct ScreenPainterImplementation { 7022 CapableOfBeingDrawnUpon window; 7023 int referenceCount; 7024 mixin NativeScreenPainterImplementation!(); 7025 } 7026 7027 // FIXME: i haven't actually tested the sprite class on MS Windows 7028 7029 /** 7030 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 7031 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 7032 7033 7034 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 7035 though I'm not sure that's ideal and the implementation might change. 7036 7037 You create one by giving a window and an image. It optimizes for that window, 7038 and copies the image into it to use as the initial picture. Creating a sprite 7039 can be quite slow (especially over a network connection) so you should do it 7040 as little as possible and just hold on to your sprite handles after making them. 7041 simpledisplay does try to do its best though, using the XSHM extension if available, 7042 but you should still write your code as if it will always be slow. 7043 7044 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 7045 a fast operation - much faster than drawing the Image itself every time. 7046 7047 `Sprite` represents a scarce resource which should be freed when you 7048 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 7049 after it has been disposed. If you are unsure about this, don't take chances, 7050 just let the garbage collector do it for you. But ideally, you can manage its 7051 lifetime more efficiently. 7052 7053 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 7054 support alpha blending in its drawing at this time. That might change in the 7055 future, but if you need alpha blending right now, use OpenGL instead. See 7056 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 7057 7058 FIXME: you are supposed to be able to draw on these similarly to on windows. 7059 ScreenPainter needs to be refactored to allow that though. So until that is 7060 done, consider a `Sprite` to have const contents. 7061 */ 7062 version(OSXCocoa) {} else // NotYetImplementedException 7063 class Sprite : CapableOfBeingDrawnUpon { 7064 7065 /// 7066 ScreenPainter draw() { 7067 return ScreenPainter(this, handle); 7068 } 7069 7070 /// Be warned: this can be a very slow operation 7071 /// FIXME NOT IMPLEMENTED 7072 TrueColorImage takeScreenshot() { 7073 return trueColorImageFromNativeHandle(handle, width, height); 7074 } 7075 7076 void delegate() paintingFinishedDg() { return null; } 7077 bool closed() { return false; } 7078 ScreenPainterImplementation* activeScreenPainter_; 7079 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 7080 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 7081 7082 version(Windows) 7083 private ubyte* rawData; 7084 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 7085 7086 this(SimpleWindow win, int width, int height) { 7087 this._width = width; 7088 this._height = height; 7089 7090 version(X11) { 7091 auto display = XDisplayConnection.get(); 7092 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 7093 } else version(Windows) { 7094 BITMAPINFO infoheader; 7095 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 7096 infoheader.bmiHeader.biWidth = width; 7097 infoheader.bmiHeader.biHeight = height; 7098 infoheader.bmiHeader.biPlanes = 1; 7099 infoheader.bmiHeader.biBitCount = 24; 7100 infoheader.bmiHeader.biCompression = BI_RGB; 7101 7102 // FIXME: this should prolly be a device dependent bitmap... 7103 handle = CreateDIBSection( 7104 null, 7105 &infoheader, 7106 DIB_RGB_COLORS, 7107 cast(void**) &rawData, 7108 null, 7109 0); 7110 7111 if(handle is null) 7112 throw new Exception("couldn't create pixmap"); 7113 } 7114 } 7115 7116 /// Makes a sprite based on the image with the initial contents from the Image 7117 this(SimpleWindow win, Image i) { 7118 this(win, i.width, i.height); 7119 7120 version(X11) { 7121 auto display = XDisplayConnection.get(); 7122 if(i.usingXshm) 7123 XShmPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height, false); 7124 else 7125 XPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height); 7126 } else version(Windows) { 7127 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 7128 auto arrLength = itemsPerLine * height; 7129 rawData[0..arrLength] = i.rawData[0..arrLength]; 7130 } else version(OSXCocoa) { 7131 // FIXME: I have no idea if this is even any good 7132 ubyte* rawData; 7133 7134 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 7135 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 7136 colorSpace, 7137 kCGImageAlphaPremultipliedLast 7138 |kCGBitmapByteOrder32Big); 7139 CGColorSpaceRelease(colorSpace); 7140 rawData = CGBitmapContextGetData(context); 7141 7142 auto rdl = (width * height * 4); 7143 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 7144 } else static assert(0); 7145 } 7146 7147 /++ 7148 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 7149 +/ 7150 void drawAt(ScreenPainter painter, Point where) { 7151 painter.drawPixmap(this, where); 7152 } 7153 7154 7155 /// Call this when you're ready to get rid of it 7156 void dispose() { 7157 version(X11) { 7158 if(handle) 7159 XFreePixmap(XDisplayConnection.get(), handle); 7160 handle = None; 7161 } else version(Windows) { 7162 if(handle) 7163 DeleteObject(handle); 7164 handle = null; 7165 } else version(OSXCocoa) { 7166 if(context) 7167 CGContextRelease(context); 7168 context = null; 7169 } else static assert(0); 7170 7171 } 7172 7173 ~this() { 7174 dispose(); 7175 } 7176 7177 /// 7178 final @property int width() { return _width; } 7179 7180 /// 7181 final @property int height() { return _height; } 7182 7183 private: 7184 7185 int _width; 7186 int _height; 7187 version(X11) 7188 Pixmap handle; 7189 else version(Windows) 7190 HBITMAP handle; 7191 else version(OSXCocoa) 7192 CGContextRef context; 7193 else static assert(0); 7194 } 7195 7196 /// 7197 interface CapableOfBeingDrawnUpon { 7198 /// 7199 ScreenPainter draw(); 7200 /// 7201 int width(); 7202 /// 7203 int height(); 7204 protected ScreenPainterImplementation* activeScreenPainter(); 7205 protected void activeScreenPainter(ScreenPainterImplementation*); 7206 bool closed(); 7207 7208 void delegate() paintingFinishedDg(); 7209 7210 /// Be warned: this can be a very slow operation 7211 TrueColorImage takeScreenshot(); 7212 } 7213 7214 /// 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() 7215 void flushGui() { 7216 version(X11) { 7217 auto dpy = XDisplayConnection.get(); 7218 XLockDisplay(dpy); 7219 scope(exit) XUnlockDisplay(dpy); 7220 XFlush(dpy); 7221 } 7222 } 7223 7224 /++ 7225 Runs the given code in the GUI thread when its event loop 7226 is available, blocking until it completes. This allows you 7227 to create and manipulate windows from another thread without 7228 invoking undefined behavior. 7229 7230 If this is the gui thread, it runs the code immediately. 7231 7232 If no gui thread exists yet, the current thread is assumed 7233 to be it. Attempting to create windows or run the event loop 7234 in any other thread will cause an assertion failure. 7235 7236 7237 $(TIP 7238 Did you know you can use UFCS on delegate literals? 7239 7240 () { 7241 // code here 7242 }.runInGuiThread; 7243 ) 7244 7245 History: 7246 Added April 10, 2020 (v7.2.0) 7247 +/ 7248 void runInGuiThread(scope void delegate() dg) @trusted { 7249 claimGuiThread(); 7250 7251 if(thisIsGuiThread) { 7252 dg(); 7253 return; 7254 } 7255 7256 import core.sync.semaphore; 7257 static Semaphore sc; 7258 if(sc is null) 7259 sc = new Semaphore(); 7260 7261 static RunQueueMember* rqm; 7262 if(rqm is null) 7263 rqm = new RunQueueMember; 7264 rqm.dg = cast(typeof(rqm.dg)) dg; 7265 rqm.signal = sc; 7266 rqm.thrown = null; 7267 7268 synchronized(runInGuiThreadLock) { 7269 runInGuiThreadQueue ~= rqm; 7270 } 7271 7272 if(!SimpleWindow.eventWakeUp()) 7273 throw new Error("runInGuiThread impossible; eventWakeUp failed"); 7274 7275 rqm.signal.wait(); 7276 7277 if(rqm.thrown) 7278 throw rqm.thrown; 7279 } 7280 7281 private void claimGuiThread() { 7282 import core.atomic; 7283 if(cas(&guiThreadExists, false, true)) 7284 thisIsGuiThread = true; 7285 } 7286 7287 private struct RunQueueMember { 7288 void delegate() dg; 7289 import core.sync.semaphore; 7290 Semaphore signal; 7291 Throwable thrown; 7292 } 7293 7294 private __gshared RunQueueMember*[] runInGuiThreadQueue; 7295 private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE 7296 private bool thisIsGuiThread = false; 7297 private shared bool guiThreadExists = false; 7298 7299 /// Used internal to dispatch events to various classes. 7300 interface CapableOfHandlingNativeEvent { 7301 NativeEventHandler getNativeEventHandler(); 7302 7303 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 7304 7305 version(X11) { 7306 // if this is impossible, you are allowed to just throw from it 7307 // Note: if you call it from another object, set a flag cuz the manger will call you again 7308 void recreateAfterDisconnect(); 7309 // discard any *connection specific* state, but keep enough that you 7310 // can be recreated if possible. discardConnectionState() is always called immediately 7311 // before recreateAfterDisconnect(), so you can set a flag there to decide if 7312 // you need initialization order 7313 void discardConnectionState(); 7314 } 7315 } 7316 7317 version(X11) 7318 /++ 7319 State of keys on mouse events, especially motion. 7320 7321 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 7322 +/ 7323 enum ModifierState : uint { 7324 shift = 1, /// 7325 capsLock = 2, /// 7326 ctrl = 4, /// 7327 alt = 8, /// Not always available on Windows 7328 windows = 64, /// ditto 7329 numLock = 16, /// 7330 7331 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 7332 middleButtonDown = 512, /// ditto 7333 rightButtonDown = 1024, /// ditto 7334 } 7335 else version(Windows) 7336 /// ditto 7337 enum ModifierState : uint { 7338 shift = 4, /// 7339 ctrl = 8, /// 7340 7341 // i'm not sure if the next two are available 7342 alt = 256, /// not always available on Windows 7343 windows = 512, /// ditto 7344 7345 capsLock = 1024, /// 7346 numLock = 2048, /// 7347 7348 leftButtonDown = 1, /// not available on key events 7349 middleButtonDown = 16, /// ditto 7350 rightButtonDown = 2, /// ditto 7351 7352 backButtonDown = 0x20, /// not available on X 7353 forwardButtonDown = 0x40, /// ditto 7354 } 7355 else version(OSXCocoa) 7356 // FIXME FIXME NotYetImplementedException 7357 enum ModifierState : uint { 7358 shift = 1, /// 7359 capsLock = 2, /// 7360 ctrl = 4, /// 7361 alt = 8, /// Not always available on Windows 7362 windows = 64, /// ditto 7363 numLock = 16, /// 7364 7365 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 7366 middleButtonDown = 512, /// ditto 7367 rightButtonDown = 1024, /// ditto 7368 } 7369 7370 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them 7371 enum MouseButton : int { 7372 none = 0, 7373 left = 1, /// 7374 right = 2, /// 7375 middle = 4, /// 7376 wheelUp = 8, /// 7377 wheelDown = 16, /// 7378 backButton = 32, /// often found on the thumb and used for back in browsers 7379 forwardButton = 64, /// often found on the thumb and used for forward in browsers 7380 } 7381 7382 version(X11) { 7383 // FIXME: match ASCII whenever we can. Most of it is already there, 7384 // but there's a few exceptions and mismatches with Windows 7385 7386 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 7387 enum Key { 7388 Escape = 0xff1b, /// 7389 F1 = 0xffbe, /// 7390 F2 = 0xffbf, /// 7391 F3 = 0xffc0, /// 7392 F4 = 0xffc1, /// 7393 F5 = 0xffc2, /// 7394 F6 = 0xffc3, /// 7395 F7 = 0xffc4, /// 7396 F8 = 0xffc5, /// 7397 F9 = 0xffc6, /// 7398 F10 = 0xffc7, /// 7399 F11 = 0xffc8, /// 7400 F12 = 0xffc9, /// 7401 PrintScreen = 0xff61, /// 7402 ScrollLock = 0xff14, /// 7403 Pause = 0xff13, /// 7404 Grave = 0x60, /// The $(BACKTICK) ~ key 7405 // number keys across the top of the keyboard 7406 N1 = 0x31, /// Number key atop the keyboard 7407 N2 = 0x32, /// 7408 N3 = 0x33, /// 7409 N4 = 0x34, /// 7410 N5 = 0x35, /// 7411 N6 = 0x36, /// 7412 N7 = 0x37, /// 7413 N8 = 0x38, /// 7414 N9 = 0x39, /// 7415 N0 = 0x30, /// 7416 Dash = 0x2d, /// 7417 Equals = 0x3d, /// 7418 Backslash = 0x5c, /// The \ | key 7419 Backspace = 0xff08, /// 7420 Insert = 0xff63, /// 7421 Home = 0xff50, /// 7422 PageUp = 0xff55, /// 7423 Delete = 0xffff, /// 7424 End = 0xff57, /// 7425 PageDown = 0xff56, /// 7426 Up = 0xff52, /// 7427 Down = 0xff54, /// 7428 Left = 0xff51, /// 7429 Right = 0xff53, /// 7430 7431 Tab = 0xff09, /// 7432 Q = 0x71, /// 7433 W = 0x77, /// 7434 E = 0x65, /// 7435 R = 0x72, /// 7436 T = 0x74, /// 7437 Y = 0x79, /// 7438 U = 0x75, /// 7439 I = 0x69, /// 7440 O = 0x6f, /// 7441 P = 0x70, /// 7442 LeftBracket = 0x5b, /// the [ { key 7443 RightBracket = 0x5d, /// the ] } key 7444 CapsLock = 0xffe5, /// 7445 A = 0x61, /// 7446 S = 0x73, /// 7447 D = 0x64, /// 7448 F = 0x66, /// 7449 G = 0x67, /// 7450 H = 0x68, /// 7451 J = 0x6a, /// 7452 K = 0x6b, /// 7453 L = 0x6c, /// 7454 Semicolon = 0x3b, /// 7455 Apostrophe = 0x27, /// 7456 Enter = 0xff0d, /// 7457 Shift = 0xffe1, /// 7458 Z = 0x7a, /// 7459 X = 0x78, /// 7460 C = 0x63, /// 7461 V = 0x76, /// 7462 B = 0x62, /// 7463 N = 0x6e, /// 7464 M = 0x6d, /// 7465 Comma = 0x2c, /// 7466 Period = 0x2e, /// 7467 Slash = 0x2f, /// the / ? key 7468 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 7469 Ctrl = 0xffe3, /// 7470 Windows = 0xffeb, /// 7471 Alt = 0xffe9, /// 7472 Space = 0x20, /// 7473 Alt_r = 0xffea, /// ditto of shift_r 7474 Windows_r = 0xffec, /// 7475 Menu = 0xff67, /// 7476 Ctrl_r = 0xffe4, /// 7477 7478 NumLock = 0xff7f, /// 7479 Divide = 0xffaf, /// The / key on the number pad 7480 Multiply = 0xffaa, /// The * key on the number pad 7481 Minus = 0xffad, /// The - key on the number pad 7482 Plus = 0xffab, /// The + key on the number pad 7483 PadEnter = 0xff8d, /// Numberpad enter key 7484 Pad1 = 0xff9c, /// Numberpad keys 7485 Pad2 = 0xff99, /// 7486 Pad3 = 0xff9b, /// 7487 Pad4 = 0xff96, /// 7488 Pad5 = 0xff9d, /// 7489 Pad6 = 0xff98, /// 7490 Pad7 = 0xff95, /// 7491 Pad8 = 0xff97, /// 7492 Pad9 = 0xff9a, /// 7493 Pad0 = 0xff9e, /// 7494 PadDot = 0xff9f, /// 7495 } 7496 } else version(Windows) { 7497 // the character here is for en-us layouts and for illustration only 7498 // if you actually want to get characters, wait for character events 7499 // (the argument to your event handler is simply a dchar) 7500 // those will be converted by the OS for the right locale. 7501 7502 enum Key { 7503 Escape = 0x1b, 7504 F1 = 0x70, 7505 F2 = 0x71, 7506 F3 = 0x72, 7507 F4 = 0x73, 7508 F5 = 0x74, 7509 F6 = 0x75, 7510 F7 = 0x76, 7511 F8 = 0x77, 7512 F9 = 0x78, 7513 F10 = 0x79, 7514 F11 = 0x7a, 7515 F12 = 0x7b, 7516 PrintScreen = 0x2c, 7517 ScrollLock = 0x91, 7518 Pause = 0x13, 7519 Grave = 0xc0, 7520 // number keys across the top of the keyboard 7521 N1 = 0x31, 7522 N2 = 0x32, 7523 N3 = 0x33, 7524 N4 = 0x34, 7525 N5 = 0x35, 7526 N6 = 0x36, 7527 N7 = 0x37, 7528 N8 = 0x38, 7529 N9 = 0x39, 7530 N0 = 0x30, 7531 Dash = 0xbd, 7532 Equals = 0xbb, 7533 Backslash = 0xdc, 7534 Backspace = 0x08, 7535 Insert = 0x2d, 7536 Home = 0x24, 7537 PageUp = 0x21, 7538 Delete = 0x2e, 7539 End = 0x23, 7540 PageDown = 0x22, 7541 Up = 0x26, 7542 Down = 0x28, 7543 Left = 0x25, 7544 Right = 0x27, 7545 7546 Tab = 0x09, 7547 Q = 0x51, 7548 W = 0x57, 7549 E = 0x45, 7550 R = 0x52, 7551 T = 0x54, 7552 Y = 0x59, 7553 U = 0x55, 7554 I = 0x49, 7555 O = 0x4f, 7556 P = 0x50, 7557 LeftBracket = 0xdb, 7558 RightBracket = 0xdd, 7559 CapsLock = 0x14, 7560 A = 0x41, 7561 S = 0x53, 7562 D = 0x44, 7563 F = 0x46, 7564 G = 0x47, 7565 H = 0x48, 7566 J = 0x4a, 7567 K = 0x4b, 7568 L = 0x4c, 7569 Semicolon = 0xba, 7570 Apostrophe = 0xde, 7571 Enter = 0x0d, 7572 Shift = 0x10, 7573 Z = 0x5a, 7574 X = 0x58, 7575 C = 0x43, 7576 V = 0x56, 7577 B = 0x42, 7578 N = 0x4e, 7579 M = 0x4d, 7580 Comma = 0xbc, 7581 Period = 0xbe, 7582 Slash = 0xbf, 7583 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 7584 Ctrl = 0x11, 7585 Windows = 0x5b, 7586 Alt = -5, // FIXME 7587 Space = 0x20, 7588 Alt_r = 0xffea, // ditto of shift_r 7589 Windows_r = 0x5c, // ditto of shift_r 7590 Menu = 0x5d, 7591 Ctrl_r = 0xa3, // ditto of shift_r 7592 7593 NumLock = 0x90, 7594 Divide = 0x6f, 7595 Multiply = 0x6a, 7596 Minus = 0x6d, 7597 Plus = 0x6b, 7598 PadEnter = -8, // FIXME 7599 Pad1 = 0x61, 7600 Pad2 = 0x62, 7601 Pad3 = 0x63, 7602 Pad4 = 0x64, 7603 Pad5 = 0x65, 7604 Pad6 = 0x66, 7605 Pad7 = 0x67, 7606 Pad8 = 0x68, 7607 Pad9 = 0x69, 7608 Pad0 = 0x60, 7609 PadDot = 0x6e, 7610 } 7611 7612 // I'm keeping this around for reference purposes 7613 // ideally all these buttons will be listed for all platforms, 7614 // but now now I'm just focusing on my US keyboard 7615 version(none) 7616 enum Key { 7617 LBUTTON = 0x01, 7618 RBUTTON = 0x02, 7619 CANCEL = 0x03, 7620 MBUTTON = 0x04, 7621 //static if (_WIN32_WINNT > = 0x500) { 7622 XBUTTON1 = 0x05, 7623 XBUTTON2 = 0x06, 7624 //} 7625 BACK = 0x08, 7626 TAB = 0x09, 7627 CLEAR = 0x0C, 7628 RETURN = 0x0D, 7629 SHIFT = 0x10, 7630 CONTROL = 0x11, 7631 MENU = 0x12, 7632 PAUSE = 0x13, 7633 CAPITAL = 0x14, 7634 KANA = 0x15, 7635 HANGEUL = 0x15, 7636 HANGUL = 0x15, 7637 JUNJA = 0x17, 7638 FINAL = 0x18, 7639 HANJA = 0x19, 7640 KANJI = 0x19, 7641 ESCAPE = 0x1B, 7642 CONVERT = 0x1C, 7643 NONCONVERT = 0x1D, 7644 ACCEPT = 0x1E, 7645 MODECHANGE = 0x1F, 7646 SPACE = 0x20, 7647 PRIOR = 0x21, 7648 NEXT = 0x22, 7649 END = 0x23, 7650 HOME = 0x24, 7651 LEFT = 0x25, 7652 UP = 0x26, 7653 RIGHT = 0x27, 7654 DOWN = 0x28, 7655 SELECT = 0x29, 7656 PRINT = 0x2A, 7657 EXECUTE = 0x2B, 7658 SNAPSHOT = 0x2C, 7659 INSERT = 0x2D, 7660 DELETE = 0x2E, 7661 HELP = 0x2F, 7662 LWIN = 0x5B, 7663 RWIN = 0x5C, 7664 APPS = 0x5D, 7665 SLEEP = 0x5F, 7666 NUMPAD0 = 0x60, 7667 NUMPAD1 = 0x61, 7668 NUMPAD2 = 0x62, 7669 NUMPAD3 = 0x63, 7670 NUMPAD4 = 0x64, 7671 NUMPAD5 = 0x65, 7672 NUMPAD6 = 0x66, 7673 NUMPAD7 = 0x67, 7674 NUMPAD8 = 0x68, 7675 NUMPAD9 = 0x69, 7676 MULTIPLY = 0x6A, 7677 ADD = 0x6B, 7678 SEPARATOR = 0x6C, 7679 SUBTRACT = 0x6D, 7680 DECIMAL = 0x6E, 7681 DIVIDE = 0x6F, 7682 F1 = 0x70, 7683 F2 = 0x71, 7684 F3 = 0x72, 7685 F4 = 0x73, 7686 F5 = 0x74, 7687 F6 = 0x75, 7688 F7 = 0x76, 7689 F8 = 0x77, 7690 F9 = 0x78, 7691 F10 = 0x79, 7692 F11 = 0x7A, 7693 F12 = 0x7B, 7694 F13 = 0x7C, 7695 F14 = 0x7D, 7696 F15 = 0x7E, 7697 F16 = 0x7F, 7698 F17 = 0x80, 7699 F18 = 0x81, 7700 F19 = 0x82, 7701 F20 = 0x83, 7702 F21 = 0x84, 7703 F22 = 0x85, 7704 F23 = 0x86, 7705 F24 = 0x87, 7706 NUMLOCK = 0x90, 7707 SCROLL = 0x91, 7708 LSHIFT = 0xA0, 7709 RSHIFT = 0xA1, 7710 LCONTROL = 0xA2, 7711 RCONTROL = 0xA3, 7712 LMENU = 0xA4, 7713 RMENU = 0xA5, 7714 //static if (_WIN32_WINNT > = 0x500) { 7715 BROWSER_BACK = 0xA6, 7716 BROWSER_FORWARD = 0xA7, 7717 BROWSER_REFRESH = 0xA8, 7718 BROWSER_STOP = 0xA9, 7719 BROWSER_SEARCH = 0xAA, 7720 BROWSER_FAVORITES = 0xAB, 7721 BROWSER_HOME = 0xAC, 7722 VOLUME_MUTE = 0xAD, 7723 VOLUME_DOWN = 0xAE, 7724 VOLUME_UP = 0xAF, 7725 MEDIA_NEXT_TRACK = 0xB0, 7726 MEDIA_PREV_TRACK = 0xB1, 7727 MEDIA_STOP = 0xB2, 7728 MEDIA_PLAY_PAUSE = 0xB3, 7729 LAUNCH_MAIL = 0xB4, 7730 LAUNCH_MEDIA_SELECT = 0xB5, 7731 LAUNCH_APP1 = 0xB6, 7732 LAUNCH_APP2 = 0xB7, 7733 //} 7734 OEM_1 = 0xBA, 7735 //static if (_WIN32_WINNT > = 0x500) { 7736 OEM_PLUS = 0xBB, 7737 OEM_COMMA = 0xBC, 7738 OEM_MINUS = 0xBD, 7739 OEM_PERIOD = 0xBE, 7740 //} 7741 OEM_2 = 0xBF, 7742 OEM_3 = 0xC0, 7743 OEM_4 = 0xDB, 7744 OEM_5 = 0xDC, 7745 OEM_6 = 0xDD, 7746 OEM_7 = 0xDE, 7747 OEM_8 = 0xDF, 7748 //static if (_WIN32_WINNT > = 0x500) { 7749 OEM_102 = 0xE2, 7750 //} 7751 PROCESSKEY = 0xE5, 7752 //static if (_WIN32_WINNT > = 0x500) { 7753 PACKET = 0xE7, 7754 //} 7755 ATTN = 0xF6, 7756 CRSEL = 0xF7, 7757 EXSEL = 0xF8, 7758 EREOF = 0xF9, 7759 PLAY = 0xFA, 7760 ZOOM = 0xFB, 7761 NONAME = 0xFC, 7762 PA1 = 0xFD, 7763 OEM_CLEAR = 0xFE, 7764 } 7765 7766 } else version(OSXCocoa) { 7767 // FIXME 7768 enum Key { 7769 Escape = 0x1b, 7770 F1 = 0x70, 7771 F2 = 0x71, 7772 F3 = 0x72, 7773 F4 = 0x73, 7774 F5 = 0x74, 7775 F6 = 0x75, 7776 F7 = 0x76, 7777 F8 = 0x77, 7778 F9 = 0x78, 7779 F10 = 0x79, 7780 F11 = 0x7a, 7781 F12 = 0x7b, 7782 PrintScreen = 0x2c, 7783 ScrollLock = -2, // FIXME 7784 Pause = -3, // FIXME 7785 Grave = 0xc0, 7786 // number keys across the top of the keyboard 7787 N1 = 0x31, 7788 N2 = 0x32, 7789 N3 = 0x33, 7790 N4 = 0x34, 7791 N5 = 0x35, 7792 N6 = 0x36, 7793 N7 = 0x37, 7794 N8 = 0x38, 7795 N9 = 0x39, 7796 N0 = 0x30, 7797 Dash = 0xbd, 7798 Equals = 0xbb, 7799 Backslash = 0xdc, 7800 Backspace = 0x08, 7801 Insert = 0x2d, 7802 Home = 0x24, 7803 PageUp = 0x21, 7804 Delete = 0x2e, 7805 End = 0x23, 7806 PageDown = 0x22, 7807 Up = 0x26, 7808 Down = 0x28, 7809 Left = 0x25, 7810 Right = 0x27, 7811 7812 Tab = 0x09, 7813 Q = 0x51, 7814 W = 0x57, 7815 E = 0x45, 7816 R = 0x52, 7817 T = 0x54, 7818 Y = 0x59, 7819 U = 0x55, 7820 I = 0x49, 7821 O = 0x4f, 7822 P = 0x50, 7823 LeftBracket = 0xdb, 7824 RightBracket = 0xdd, 7825 CapsLock = 0x14, 7826 A = 0x41, 7827 S = 0x53, 7828 D = 0x44, 7829 F = 0x46, 7830 G = 0x47, 7831 H = 0x48, 7832 J = 0x4a, 7833 K = 0x4b, 7834 L = 0x4c, 7835 Semicolon = 0xba, 7836 Apostrophe = 0xde, 7837 Enter = 0x0d, 7838 Shift = 0x10, 7839 Z = 0x5a, 7840 X = 0x58, 7841 C = 0x43, 7842 V = 0x56, 7843 B = 0x42, 7844 N = 0x4e, 7845 M = 0x4d, 7846 Comma = 0xbc, 7847 Period = 0xbe, 7848 Slash = 0xbf, 7849 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 7850 Ctrl = 0x11, 7851 Windows = 0x5b, 7852 Alt = -5, // FIXME 7853 Space = 0x20, 7854 Alt_r = 0xffea, // ditto of shift_r 7855 Windows_r = -6, // FIXME 7856 Menu = 0x5d, 7857 Ctrl_r = -7, // FIXME 7858 7859 NumLock = 0x90, 7860 Divide = 0x6f, 7861 Multiply = 0x6a, 7862 Minus = 0x6d, 7863 Plus = 0x6b, 7864 PadEnter = -8, // FIXME 7865 // FIXME for the rest of these: 7866 Pad1 = 0xff9c, 7867 Pad2 = 0xff99, 7868 Pad3 = 0xff9b, 7869 Pad4 = 0xff96, 7870 Pad5 = 0xff9d, 7871 Pad6 = 0xff98, 7872 Pad7 = 0xff95, 7873 Pad8 = 0xff97, 7874 Pad9 = 0xff9a, 7875 Pad0 = 0xff9e, 7876 PadDot = 0xff9f, 7877 } 7878 7879 } 7880 7881 /* Additional utilities */ 7882 7883 7884 Color fromHsl(real h, real s, real l) { 7885 return arsd.color.fromHsl([h,s,l]); 7886 } 7887 7888 7889 7890 /* ********** What follows is the system-specific implementations *********/ 7891 version(Windows) { 7892 7893 7894 // helpers for making HICONs from MemoryImages 7895 class WindowsIcon { 7896 struct Win32Icon(int colorCount) { 7897 align(1): 7898 uint biSize; 7899 int biWidth; 7900 int biHeight; 7901 ushort biPlanes; 7902 ushort biBitCount; 7903 uint biCompression; 7904 uint biSizeImage; 7905 int biXPelsPerMeter; 7906 int biYPelsPerMeter; 7907 uint biClrUsed; 7908 uint biClrImportant; 7909 RGBQUAD[colorCount] biColors; 7910 /* Pixels: 7911 Uint8 pixels[] 7912 */ 7913 /* Mask: 7914 Uint8 mask[] 7915 */ 7916 7917 ubyte[4096] data; 7918 7919 void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 7920 width = mi.width; 7921 height = mi.height; 7922 7923 auto indexedImage = cast(IndexedImage) mi; 7924 if(indexedImage is null) 7925 indexedImage = quantize(mi.getAsTrueColorImage()); 7926 7927 assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy 7928 assert(height %4 == 0); 7929 7930 int icon_plen = height*((width+3)&~3); 7931 int icon_mlen = height*((((width+7)/8)+3)&~3); 7932 icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 7933 7934 biSize = 40; 7935 biWidth = width; 7936 biHeight = height*2; 7937 biPlanes = 1; 7938 biBitCount = 8; 7939 biSizeImage = icon_plen+icon_mlen; 7940 7941 int offset = 0; 7942 int andOff = icon_plen * 8; // the and offset is in bits 7943 for(int y = height - 1; y >= 0; y--) { 7944 int off2 = y * width; 7945 foreach(x; 0 .. width) { 7946 const b = indexedImage.data[off2 + x]; 7947 data[offset] = b; 7948 offset++; 7949 7950 const andBit = andOff % 8; 7951 const andIdx = andOff / 8; 7952 assert(b < indexedImage.palette.length); 7953 // this is anded to the destination, since and 0 means erase, 7954 // we want that to be opaque, and 1 for transparent 7955 auto transparent = (indexedImage.palette[b].a <= 127); 7956 data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0); 7957 7958 andOff++; 7959 } 7960 7961 andOff += andOff % 32; 7962 } 7963 7964 foreach(idx, entry; indexedImage.palette) { 7965 if(entry.a > 127) { 7966 biColors[idx].rgbBlue = entry.b; 7967 biColors[idx].rgbGreen = entry.g; 7968 biColors[idx].rgbRed = entry.r; 7969 } else { 7970 biColors[idx].rgbBlue = 255; 7971 biColors[idx].rgbGreen = 255; 7972 biColors[idx].rgbRed = 255; 7973 } 7974 } 7975 7976 /* 7977 data[0..icon_plen] = getFlippedUnfilteredDatastream(png); 7978 data[icon_plen..icon_plen+icon_mlen] = getANDMask(png); 7979 //icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0); 7980 auto pngMap = fetchPaletteWin32(png); 7981 biColors[0..pngMap.length] = pngMap[]; 7982 */ 7983 } 7984 } 7985 7986 7987 Win32Icon!(256) icon_win32; 7988 7989 7990 this(MemoryImage mi) { 7991 int icon_len, width, height; 7992 7993 icon_win32.fromMemoryImage(mi, icon_len, width, height); 7994 7995 /* 7996 PNG* png = readPnpngData); 7997 PNGHeader pngh = getHeader(png); 7998 void* icon_win32; 7999 if(pngh.depth == 4) { 8000 auto i = new Win32Icon!(16); 8001 i.fromPNG(png, pngh, icon_len, width, height); 8002 icon_win32 = i; 8003 } 8004 else if(pngh.depth == 8) { 8005 auto i = new Win32Icon!(256); 8006 i.fromPNG(png, pngh, icon_len, width, height); 8007 icon_win32 = i; 8008 } else assert(0); 8009 */ 8010 8011 hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0); 8012 8013 if(hIcon is null) throw new Exception("CreateIconFromResourceEx"); 8014 } 8015 8016 ~this() { 8017 DestroyIcon(hIcon); 8018 } 8019 8020 HICON hIcon; 8021 } 8022 8023 8024 8025 8026 8027 8028 alias int delegate(HWND, UINT, WPARAM, LPARAM) NativeEventHandler; 8029 alias HWND NativeWindowHandle; 8030 8031 extern(Windows) 8032 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 8033 try { 8034 if(SimpleWindow.handleNativeGlobalEvent !is null) { 8035 // it returns zero if the message is handled, so we won't do anything more there 8036 // do I like that though? 8037 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam); 8038 if(ret == 0) 8039 return ret; 8040 } 8041 8042 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 8043 if(window.getNativeEventHandler !is null) { 8044 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam); 8045 if(ret == 0) 8046 return ret; 8047 } 8048 if(auto w = cast(SimpleWindow) (*window)) 8049 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 8050 else 8051 return DefWindowProc(hWnd, iMessage, wParam, lParam); 8052 } else { 8053 return DefWindowProc(hWnd, iMessage, wParam, lParam); 8054 } 8055 } catch (Exception e) { 8056 assert(false, "Exception caught in WndProc " ~ e.toString()); 8057 } 8058 } 8059 8060 mixin template NativeScreenPainterImplementation() { 8061 HDC hdc; 8062 HWND hwnd; 8063 //HDC windowHdc; 8064 HBITMAP oldBmp; 8065 8066 void create(NativeWindowHandle window) { 8067 hwnd = window; 8068 8069 if(auto sw = cast(SimpleWindow) this.window) { 8070 // drawing on a window, double buffer 8071 auto windowHdc = GetDC(hwnd); 8072 8073 auto buffer = sw.impl.buffer; 8074 hdc = CreateCompatibleDC(windowHdc); 8075 8076 ReleaseDC(hwnd, windowHdc); 8077 8078 oldBmp = SelectObject(hdc, buffer); 8079 } else { 8080 // drawing on something else, draw directly 8081 hdc = CreateCompatibleDC(null); 8082 SelectObject(hdc, window); 8083 8084 } 8085 8086 // X doesn't draw a text background, so neither should we 8087 SetBkMode(hdc, TRANSPARENT); 8088 8089 8090 static bool triedDefaultGuiFont = false; 8091 if(!triedDefaultGuiFont) { 8092 NONCLIENTMETRICS params; 8093 params.cbSize = params.sizeof; 8094 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 8095 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 8096 } 8097 triedDefaultGuiFont = true; 8098 } 8099 8100 if(defaultGuiFont) { 8101 SelectObject(hdc, defaultGuiFont); 8102 // DeleteObject(defaultGuiFont); 8103 } 8104 } 8105 8106 static HFONT defaultGuiFont; 8107 8108 void setFont(OperatingSystemFont font) { 8109 if(font && font.font) { 8110 if(SelectObject(hdc, font.font) == HGDI_ERROR) { 8111 // error... how to handle tho? 8112 } 8113 } 8114 else if(defaultGuiFont) 8115 SelectObject(hdc, defaultGuiFont); 8116 } 8117 8118 arsd.color.Rectangle _clipRectangle; 8119 8120 void setClipRectangle(int x, int y, int width, int height) { 8121 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 8122 8123 if(width == 0 || height == 0) { 8124 SelectClipRgn(hdc, null); 8125 } else { 8126 auto region = CreateRectRgn(x, y, x + width, y + height); 8127 SelectClipRgn(hdc, region); 8128 DeleteObject(region); 8129 } 8130 } 8131 8132 8133 // just because we can on Windows... 8134 //void create(Image image); 8135 8136 void dispose() { 8137 // FIXME: this.window.width/height is probably wrong 8138 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 8139 // ReleaseDC(hwnd, windowHdc); 8140 8141 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 8142 if(cast(SimpleWindow) this.window) 8143 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 8144 8145 if(originalPen !is null) 8146 SelectObject(hdc, originalPen); 8147 if(currentPen !is null) 8148 DeleteObject(currentPen); 8149 if(originalBrush !is null) 8150 SelectObject(hdc, originalBrush); 8151 if(currentBrush !is null) 8152 DeleteObject(currentBrush); 8153 8154 SelectObject(hdc, oldBmp); 8155 8156 DeleteDC(hdc); 8157 8158 if(window.paintingFinishedDg !is null) 8159 window.paintingFinishedDg(); 8160 } 8161 8162 HPEN originalPen; 8163 HPEN currentPen; 8164 8165 Pen _activePen; 8166 8167 @property void pen(Pen p) { 8168 _activePen = p; 8169 8170 HPEN pen; 8171 if(p.color.a == 0) { 8172 pen = GetStockObject(NULL_PEN); 8173 } else { 8174 int style = PS_SOLID; 8175 final switch(p.style) { 8176 case Pen.Style.Solid: 8177 style = PS_SOLID; 8178 break; 8179 case Pen.Style.Dashed: 8180 style = PS_DASH; 8181 break; 8182 case Pen.Style.Dotted: 8183 style = PS_DOT; 8184 break; 8185 } 8186 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 8187 } 8188 auto orig = SelectObject(hdc, pen); 8189 if(originalPen is null) 8190 originalPen = orig; 8191 8192 if(currentPen !is null) 8193 DeleteObject(currentPen); 8194 8195 currentPen = pen; 8196 8197 // the outline is like a foreground since it's done that way on X 8198 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 8199 8200 } 8201 8202 @property void rasterOp(RasterOp op) { 8203 int mode; 8204 final switch(op) { 8205 case RasterOp.normal: 8206 mode = R2_COPYPEN; 8207 break; 8208 case RasterOp.xor: 8209 mode = R2_XORPEN; 8210 break; 8211 } 8212 SetROP2(hdc, mode); 8213 } 8214 8215 HBRUSH originalBrush; 8216 HBRUSH currentBrush; 8217 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 8218 @property void fillColor(Color c) { 8219 if(c == _fillColor) 8220 return; 8221 _fillColor = c; 8222 HBRUSH brush; 8223 if(c.a == 0) { 8224 brush = GetStockObject(HOLLOW_BRUSH); 8225 } else { 8226 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 8227 } 8228 auto orig = SelectObject(hdc, brush); 8229 if(originalBrush is null) 8230 originalBrush = orig; 8231 8232 if(currentBrush !is null) 8233 DeleteObject(currentBrush); 8234 8235 currentBrush = brush; 8236 8237 // background color is NOT set because X doesn't draw text backgrounds 8238 // SetBkColor(hdc, RGB(255, 255, 255)); 8239 } 8240 8241 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 8242 BITMAP bm; 8243 8244 HDC hdcMem = CreateCompatibleDC(hdc); 8245 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 8246 8247 GetObject(i.handle, bm.sizeof, &bm); 8248 8249 // or should I AlphaBlend!??!?! 8250 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 8251 8252 SelectObject(hdcMem, hbmOld); 8253 DeleteDC(hdcMem); 8254 } 8255 8256 void drawPixmap(Sprite s, int x, int y) { 8257 BITMAP bm; 8258 8259 HDC hdcMem = CreateCompatibleDC(hdc); 8260 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 8261 8262 GetObject(s.handle, bm.sizeof, &bm); 8263 8264 // or should I AlphaBlend!??!?! 8265 BitBlt(hdc, x, y, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 8266 8267 SelectObject(hdcMem, hbmOld); 8268 DeleteDC(hdcMem); 8269 } 8270 8271 Size textSize(scope const(char)[] text) { 8272 bool dummyX; 8273 if(text.length == 0) { 8274 text = " "; 8275 dummyX = true; 8276 } 8277 RECT rect; 8278 WCharzBuffer buffer = WCharzBuffer(text); 8279 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT); 8280 return Size(dummyX ? 0 : rect.right, rect.bottom); 8281 } 8282 8283 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 8284 if(text.length && text[$-1] == '\n') 8285 text = text[0 .. $-1]; // tailing newlines are weird on windows... 8286 8287 WCharzBuffer buffer = WCharzBuffer(text); 8288 if(x2 == 0 && y2 == 0) 8289 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 8290 else { 8291 RECT rect; 8292 rect.left = x; 8293 rect.top = y; 8294 rect.right = x2; 8295 rect.bottom = y2; 8296 8297 uint mode = DT_LEFT; 8298 if(alignment & TextAlignment.Right) 8299 mode = DT_RIGHT; 8300 else if(alignment & TextAlignment.Center) 8301 mode = DT_CENTER; 8302 8303 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 8304 if(alignment & TextAlignment.VerticalCenter) 8305 mode |= DT_VCENTER | DT_SINGLELINE; 8306 8307 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode); 8308 } 8309 8310 /* 8311 uint mode; 8312 8313 if(alignment & TextAlignment.Center) 8314 mode = TA_CENTER; 8315 8316 SetTextAlign(hdc, mode); 8317 */ 8318 } 8319 8320 int fontHeight() { 8321 TEXTMETRIC metric; 8322 if(GetTextMetricsW(hdc, &metric)) { 8323 return metric.tmHeight; 8324 } 8325 8326 return 16; // idk just guessing here, maybe we should throw 8327 } 8328 8329 void drawPixel(int x, int y) { 8330 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 8331 } 8332 8333 // The basic shapes, outlined 8334 8335 void drawLine(int x1, int y1, int x2, int y2) { 8336 MoveToEx(hdc, x1, y1, null); 8337 LineTo(hdc, x2, y2); 8338 } 8339 8340 void drawRectangle(int x, int y, int width, int height) { 8341 gdi.Rectangle(hdc, x, y, x + width, y + height); 8342 } 8343 8344 /// Arguments are the points of the bounding rectangle 8345 void drawEllipse(int x1, int y1, int x2, int y2) { 8346 Ellipse(hdc, x1, y1, x2, y2); 8347 } 8348 8349 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 8350 if((start % (360*64)) == (finish % (360*64))) 8351 drawEllipse(x1, y1, x1 + width, y1 + height); 8352 else { 8353 import core.stdc.math; 8354 float startAngle = start * 64 * 180 / 3.14159265; 8355 float endAngle = finish * 64 * 180 / 3.14159265; 8356 Arc(hdc, x1, y1, x1 + width, y1 + height, 8357 cast(int)(cos(startAngle) * width / 2 + x1), 8358 cast(int)(sin(startAngle) * height / 2 + y1), 8359 cast(int)(cos(endAngle) * width / 2 + x1), 8360 cast(int)(sin(endAngle) * height / 2 + y1), 8361 ); 8362 } 8363 } 8364 8365 void drawPolygon(Point[] vertexes) { 8366 POINT[] points; 8367 points.length = vertexes.length; 8368 8369 foreach(i, p; vertexes) { 8370 points[i].x = p.x; 8371 points[i].y = p.y; 8372 } 8373 8374 Polygon(hdc, points.ptr, cast(int) points.length); 8375 } 8376 } 8377 8378 8379 // Mix this into the SimpleWindow class 8380 mixin template NativeSimpleWindowImplementation() { 8381 int curHidden = 0; // counter 8382 __gshared static bool[string] knownWinClasses; 8383 static bool altPressed = false; 8384 8385 HANDLE oldCursor; 8386 8387 void hideCursor () { 8388 if(curHidden == 0) 8389 oldCursor = SetCursor(null); 8390 ++curHidden; 8391 } 8392 8393 void showCursor () { 8394 --curHidden; 8395 if(curHidden == 0) { 8396 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 8397 } 8398 } 8399 8400 8401 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 8402 8403 void setMinSize (int minwidth, int minheight) { 8404 minWidth = minwidth; 8405 minHeight = minheight; 8406 } 8407 void setMaxSize (int maxwidth, int maxheight) { 8408 maxWidth = maxwidth; 8409 maxHeight = maxheight; 8410 } 8411 8412 // FIXME i'm not sure that Windows has this functionality 8413 // though it is nonessential anyway. 8414 void setResizeGranularity (int granx, int grany) {} 8415 8416 ScreenPainter getPainter() { 8417 return ScreenPainter(this, hwnd); 8418 } 8419 8420 HBITMAP buffer; 8421 8422 void setTitle(string title) { 8423 WCharzBuffer bfr = WCharzBuffer(title); 8424 SetWindowTextW(hwnd, bfr.ptr); 8425 } 8426 8427 string getTitle() { 8428 auto len = GetWindowTextLengthW(hwnd); 8429 if (!len) 8430 return null; 8431 wchar[256] tmpBuffer; 8432 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 8433 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 8434 auto str = buffer[0 .. len2]; 8435 return makeUtf8StringFromWindowsString(str); 8436 } 8437 8438 void move(int x, int y) { 8439 RECT rect; 8440 GetWindowRect(hwnd, &rect); 8441 // move it while maintaining the same size... 8442 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 8443 } 8444 8445 void resize(int w, int h) { 8446 RECT rect; 8447 GetWindowRect(hwnd, &rect); 8448 8449 RECT client; 8450 GetClientRect(hwnd, &client); 8451 8452 rect.right = rect.right - client.right + w; 8453 rect.bottom = rect.bottom - client.bottom + h; 8454 8455 // same position, new size for the client rectangle 8456 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 8457 8458 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 8459 } 8460 8461 void moveResize (int x, int y, int w, int h) { 8462 // what's given is the client rectangle, we need to adjust 8463 8464 RECT rect; 8465 rect.left = x; 8466 rect.top = y; 8467 rect.right = w + x; 8468 rect.bottom = h + y; 8469 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 8470 throw new Exception("AdjustWindowRect"); 8471 8472 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 8473 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 8474 if (windowResized !is null) windowResized(w, h); 8475 } 8476 8477 version(without_opengl) {} else { 8478 HGLRC ghRC; 8479 HDC ghDC; 8480 } 8481 8482 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 8483 string cnamec; 8484 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 8485 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 8486 cnamec = "DSimpleWindow"; 8487 } else { 8488 cnamec = sdpyWindowClass; 8489 } 8490 8491 WCharzBuffer cn = WCharzBuffer(cnamec); 8492 8493 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 8494 8495 if(cnamec !in knownWinClasses) { 8496 WNDCLASSEX wc; 8497 8498 // FIXME: I might be able to use cbWndExtra to hold the pointer back 8499 // to the object. Maybe. 8500 wc.cbSize = wc.sizeof; 8501 wc.cbClsExtra = 0; 8502 wc.cbWndExtra = 0; 8503 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 8504 wc.hCursor = LoadCursorW(null, IDC_ARROW); 8505 wc.hIcon = LoadIcon(hInstance, null); 8506 wc.hInstance = hInstance; 8507 wc.lpfnWndProc = &WndProc; 8508 wc.lpszClassName = cn.ptr; 8509 wc.hIconSm = null; 8510 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 8511 if(!RegisterClassExW(&wc)) 8512 throw new WindowsApiException("RegisterClassExW"); 8513 knownWinClasses[cnamec] = true; 8514 } 8515 8516 int style; 8517 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 8518 8519 // FIXME: windowType and customizationFlags 8520 final switch(windowType) { 8521 case WindowTypes.normal: 8522 style = WS_OVERLAPPEDWINDOW; 8523 break; 8524 case WindowTypes.undecorated: 8525 style = WS_POPUP | WS_SYSMENU; 8526 break; 8527 case WindowTypes.eventOnly: 8528 _hidden = true; 8529 break; 8530 case WindowTypes.dropdownMenu: 8531 case WindowTypes.popupMenu: 8532 case WindowTypes.notification: 8533 style = WS_POPUP; 8534 flags |= WS_EX_NOACTIVATE; 8535 break; 8536 case WindowTypes.nestedChild: 8537 style = WS_CHILD; 8538 break; 8539 } 8540 8541 if ((customizationFlags & WindowFlags.extraComposite) != 0) 8542 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 8543 8544 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 8545 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 8546 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 8547 8548 if ((customizationFlags & WindowFlags.extraComposite) != 0) 8549 setOpacity(255); 8550 8551 SimpleWindow.nativeMapping[hwnd] = this; 8552 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 8553 8554 if(windowType == WindowTypes.eventOnly) 8555 return; 8556 8557 HDC hdc = GetDC(hwnd); 8558 8559 8560 version(without_opengl) {} 8561 else { 8562 if(opengl == OpenGlOptions.yes) { 8563 if(!openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 8564 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 8565 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 8566 ghDC = hdc; 8567 PIXELFORMATDESCRIPTOR pfd; 8568 8569 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 8570 pfd.nVersion = 1; 8571 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 8572 pfd.dwLayerMask = PFD_MAIN_PLANE; 8573 pfd.iPixelType = PFD_TYPE_RGBA; 8574 pfd.cColorBits = 24; 8575 pfd.cDepthBits = 24; 8576 pfd.cAccumBits = 0; 8577 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 8578 8579 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 8580 8581 if (pixelformat == 0) 8582 throw new WindowsApiException("ChoosePixelFormat"); 8583 8584 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 8585 throw new WindowsApiException("SetPixelFormat"); 8586 8587 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 8588 // windoze is idiotic: we have to have OpenGL context to get function addresses 8589 // so we will create fake context to get that stupid address 8590 auto tmpcc = wglCreateContext(ghDC); 8591 if (tmpcc !is null) { 8592 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 8593 wglMakeCurrent(ghDC, tmpcc); 8594 wglInitOtherFunctions(); 8595 } 8596 } 8597 8598 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 8599 int[9] contextAttribs = [ 8600 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 8601 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 8602 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 8603 // for modern context, set "forward compatibility" flag too 8604 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 8605 0/*None*/, 8606 ]; 8607 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 8608 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 8609 // activate fallback mode 8610 // 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; 8611 ghRC = wglCreateContext(ghDC); 8612 } 8613 if (ghRC is null) 8614 throw new WindowsApiException("wglCreateContextAttribsARB"); 8615 } else { 8616 // try to do at least something 8617 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 8618 sdpyOpenGLContextVersion = 0; 8619 ghRC = wglCreateContext(ghDC); 8620 } 8621 if (ghRC is null) 8622 throw new WindowsApiException("wglCreateContext"); 8623 } 8624 } 8625 } 8626 8627 if(opengl == OpenGlOptions.no) { 8628 buffer = CreateCompatibleBitmap(hdc, width, height); 8629 8630 auto hdcBmp = CreateCompatibleDC(hdc); 8631 // make sure it's filled with a blank slate 8632 auto oldBmp = SelectObject(hdcBmp, buffer); 8633 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 8634 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 8635 gdi.Rectangle(hdcBmp, 0, 0, width, height); 8636 SelectObject(hdcBmp, oldBmp); 8637 SelectObject(hdcBmp, oldBrush); 8638 SelectObject(hdcBmp, oldPen); 8639 DeleteDC(hdcBmp); 8640 8641 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 8642 } 8643 8644 // We want the window's client area to match the image size 8645 RECT rcClient, rcWindow; 8646 POINT ptDiff; 8647 GetClientRect(hwnd, &rcClient); 8648 GetWindowRect(hwnd, &rcWindow); 8649 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 8650 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 8651 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 8652 8653 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 8654 ShowWindow(hwnd, SW_SHOWNORMAL); 8655 } else { 8656 _hidden = true; 8657 } 8658 this._visibleForTheFirstTimeCalled = false; // hack! 8659 } 8660 8661 8662 void dispose() { 8663 if(buffer) 8664 DeleteObject(buffer); 8665 } 8666 8667 void closeWindow() { 8668 DestroyWindow(hwnd); 8669 } 8670 8671 bool setOpacity(ubyte alpha) { 8672 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 8673 } 8674 8675 HANDLE currentCursor; 8676 8677 // returns zero if it recognized the event 8678 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 8679 MouseEvent mouse; 8680 8681 void mouseEvent(bool isScreen, ulong mods) { 8682 auto x = LOWORD(lParam); 8683 auto y = HIWORD(lParam); 8684 if(isScreen) { 8685 POINT p; 8686 p.x = x; 8687 p.y = y; 8688 ScreenToClient(hwnd, &p); 8689 x = cast(ushort) p.x; 8690 y = cast(ushort) p.y; 8691 } 8692 mouse.x = x + offsetX; 8693 mouse.y = y + offsetY; 8694 8695 wind.mdx(mouse); 8696 mouse.modifierState = cast(int) mods; 8697 mouse.window = wind; 8698 8699 if(wind.handleMouseEvent) 8700 wind.handleMouseEvent(mouse); 8701 } 8702 8703 switch(msg) { 8704 case WM_GETMINMAXINFO: 8705 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 8706 8707 if(wind.minWidth > 0) { 8708 RECT rect; 8709 rect.left = 100; 8710 rect.top = 100; 8711 rect.right = wind.minWidth + 100; 8712 rect.bottom = wind.minHeight + 100; 8713 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 8714 throw new WindowsApiException("AdjustWindowRect"); 8715 8716 mmi.ptMinTrackSize.x = rect.right - rect.left; 8717 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 8718 } 8719 8720 if(wind.maxWidth < int.max) { 8721 RECT rect; 8722 rect.left = 100; 8723 rect.top = 100; 8724 rect.right = wind.maxWidth + 100; 8725 rect.bottom = wind.maxHeight + 100; 8726 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 8727 throw new WindowsApiException("AdjustWindowRect"); 8728 8729 mmi.ptMaxTrackSize.x = rect.right - rect.left; 8730 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 8731 } 8732 break; 8733 case WM_CHAR: 8734 wchar c = cast(wchar) wParam; 8735 if(wind.handleCharEvent) 8736 wind.handleCharEvent(cast(dchar) c); 8737 break; 8738 case WM_SETFOCUS: 8739 case WM_KILLFOCUS: 8740 wind._focused = (msg == WM_SETFOCUS); 8741 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 8742 if(wind.onFocusChange) 8743 wind.onFocusChange(msg == WM_SETFOCUS); 8744 break; 8745 case WM_SYSKEYDOWN: 8746 case WM_SYSKEYUP: 8747 case WM_KEYDOWN: 8748 case WM_KEYUP: 8749 KeyEvent ev; 8750 ev.key = cast(Key) wParam; 8751 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 8752 if ((msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP) && wParam == 0x12) ev.key = Key.Alt; // windows does it this way 8753 8754 ev.hardwareCode = (lParam & 0xff0000) >> 16; 8755 8756 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 8757 ev.modifierState |= ModifierState.shift; 8758 //k8: this doesn't work; thanks for nothing, windows 8759 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 8760 ev.modifierState |= ModifierState.alt;*/ 8761 if ((msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP) && wParam == 0x12) altPressed = (msg == WM_SYSKEYDOWN); 8762 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 8763 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 8764 ev.modifierState |= ModifierState.ctrl; 8765 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 8766 ev.modifierState |= ModifierState.windows; 8767 if(GetKeyState(Key.NumLock)) 8768 ev.modifierState |= ModifierState.numLock; 8769 if(GetKeyState(Key.CapsLock)) 8770 ev.modifierState |= ModifierState.capsLock; 8771 8772 /+ 8773 // we always want to send the character too, so let's convert it 8774 ubyte[256] state; 8775 wchar[16] buffer; 8776 GetKeyboardState(state.ptr); 8777 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 8778 8779 foreach(dchar d; buffer) { 8780 ev.character = d; 8781 break; 8782 } 8783 +/ 8784 8785 ev.window = wind; 8786 if(wind.handleKeyEvent) 8787 wind.handleKeyEvent(ev); 8788 break; 8789 case 0x020a /*WM_MOUSEWHEEL*/: 8790 // send click 8791 mouse.type = cast(MouseEventType) 1; 8792 mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp); 8793 mouseEvent(true, LOWORD(wParam)); 8794 8795 // also send release 8796 mouse.type = cast(MouseEventType) 2; 8797 mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp); 8798 mouseEvent(true, LOWORD(wParam)); 8799 break; 8800 case WM_MOUSEMOVE: 8801 mouse.type = cast(MouseEventType) 0; 8802 mouseEvent(false, wParam); 8803 break; 8804 case WM_LBUTTONDOWN: 8805 case WM_LBUTTONDBLCLK: 8806 mouse.type = cast(MouseEventType) 1; 8807 mouse.button = MouseButton.left; 8808 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 8809 mouseEvent(false, wParam); 8810 break; 8811 case WM_LBUTTONUP: 8812 mouse.type = cast(MouseEventType) 2; 8813 mouse.button = MouseButton.left; 8814 mouseEvent(false, wParam); 8815 break; 8816 case WM_RBUTTONDOWN: 8817 case WM_RBUTTONDBLCLK: 8818 mouse.type = cast(MouseEventType) 1; 8819 mouse.button = MouseButton.right; 8820 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 8821 mouseEvent(false, wParam); 8822 break; 8823 case WM_RBUTTONUP: 8824 mouse.type = cast(MouseEventType) 2; 8825 mouse.button = MouseButton.right; 8826 mouseEvent(false, wParam); 8827 break; 8828 case WM_MBUTTONDOWN: 8829 case WM_MBUTTONDBLCLK: 8830 mouse.type = cast(MouseEventType) 1; 8831 mouse.button = MouseButton.middle; 8832 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 8833 mouseEvent(false, wParam); 8834 break; 8835 case WM_MBUTTONUP: 8836 mouse.type = cast(MouseEventType) 2; 8837 mouse.button = MouseButton.middle; 8838 mouseEvent(false, wParam); 8839 break; 8840 case WM_XBUTTONDOWN: 8841 case WM_XBUTTONDBLCLK: 8842 mouse.type = cast(MouseEventType) 1; 8843 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 8844 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 8845 mouseEvent(false, wParam); 8846 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 8847 case WM_XBUTTONUP: 8848 mouse.type = cast(MouseEventType) 2; 8849 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 8850 mouseEvent(false, wParam); 8851 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 8852 8853 default: return 1; 8854 } 8855 return 0; 8856 } 8857 8858 HWND hwnd; 8859 int oldWidth; 8860 int oldHeight; 8861 bool inSizeMove; 8862 8863 // the extern(Windows) wndproc should just forward to this 8864 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 8865 assert(hwnd is this.hwnd); 8866 8867 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 8868 switch(msg) { 8869 case WM_SETCURSOR: 8870 if(cast(HWND) wParam !is hwnd) 8871 return 0; // further processing elsewhere 8872 8873 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 8874 SetCursor(this.curHidden > 0 ? null : currentCursor); 8875 return 1; 8876 } else { 8877 return DefWindowProc(hwnd, msg, wParam, lParam); 8878 } 8879 //break; 8880 8881 case WM_CLOSE: 8882 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 8883 break; 8884 case WM_DESTROY: 8885 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 8886 SimpleWindow.nativeMapping.remove(hwnd); 8887 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 8888 8889 bool anyImportant = false; 8890 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 8891 if(w.beingOpenKeepsAppOpen) { 8892 anyImportant = true; 8893 break; 8894 } 8895 if(!anyImportant) { 8896 PostQuitMessage(0); 8897 } 8898 break; 8899 case WM_SIZE: 8900 if(wParam == 1 /* SIZE_MINIMIZED */) 8901 break; 8902 _width = LOWORD(lParam); 8903 _height = HIWORD(lParam); 8904 8905 // I want to avoid tearing in the windows (my code is inefficient 8906 // so this is a hack around that) so while sizing, we don't trigger, 8907 // but we do want to trigger on events like mazimize. 8908 if(!inSizeMove) 8909 goto size_changed; 8910 break; 8911 // I don't like the tearing I get when redrawing on WM_SIZE 8912 // (I know there's other ways to fix that but I don't like that behavior anyway) 8913 // so instead it is going to redraw only at the end of a size. 8914 case 0x0231: /* WM_ENTERSIZEMOVE */ 8915 oldWidth = this.width; 8916 oldHeight = this.height; 8917 inSizeMove = true; 8918 break; 8919 case 0x0232: /* WM_EXITSIZEMOVE */ 8920 inSizeMove = false; 8921 // nothing relevant changed, don't bother redrawing 8922 if(oldWidth == width && oldHeight == height) 8923 break; 8924 8925 size_changed: 8926 8927 // note: OpenGL windows don't use a backing bmp, so no need to change them 8928 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 8929 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 8930 // gotta get the double buffer bmp to match the window 8931 // FIXME: could this be more efficient? It isn't really necessary to make 8932 // a new buffer if we're sizing down at least. 8933 auto hdc = GetDC(hwnd); 8934 auto oldBuffer = buffer; 8935 buffer = CreateCompatibleBitmap(hdc, width, height); 8936 8937 auto hdcBmp = CreateCompatibleDC(hdc); 8938 auto oldBmp = SelectObject(hdcBmp, buffer); 8939 8940 auto hdcOldBmp = CreateCompatibleDC(hdc); 8941 auto oldOldBmp = SelectObject(hdcOldBmp, oldBmp); 8942 8943 BitBlt(hdcBmp, 0, 0, width, height, hdcOldBmp, oldWidth, oldHeight, SRCCOPY); 8944 8945 SelectObject(hdcOldBmp, oldOldBmp); 8946 DeleteDC(hdcOldBmp); 8947 8948 SelectObject(hdcBmp, oldBmp); 8949 DeleteDC(hdcBmp); 8950 8951 ReleaseDC(hwnd, hdc); 8952 8953 DeleteObject(oldBuffer); 8954 } 8955 8956 version(without_opengl) {} else 8957 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 8958 glViewport(0, 0, width, height); 8959 } 8960 8961 if(windowResized !is null) 8962 windowResized(width, height); 8963 break; 8964 case WM_ERASEBKGND: 8965 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 8966 if (!this._visibleForTheFirstTimeCalled) { 8967 this._visibleForTheFirstTimeCalled = true; 8968 if (this.visibleForTheFirstTime !is null) { 8969 version(without_opengl) {} else { 8970 if(openglMode == OpenGlOptions.yes) { 8971 this.setAsCurrentOpenGlContextNT(); 8972 glViewport(0, 0, width, height); 8973 } 8974 } 8975 this.visibleForTheFirstTime(); 8976 } 8977 } 8978 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 8979 version(without_opengl) {} else { 8980 if (openglMode == OpenGlOptions.yes) return 1; 8981 } 8982 // call windows default handler, so it can paint standard controls 8983 goto default; 8984 case WM_CTLCOLORBTN: 8985 case WM_CTLCOLORSTATIC: 8986 SetBkMode(cast(HDC) wParam, TRANSPARENT); 8987 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 8988 GetSysColorBrush(COLOR_3DFACE); 8989 //break; 8990 case WM_SHOWWINDOW: 8991 this._visible = (wParam != 0); 8992 if (!this._visibleForTheFirstTimeCalled && this._visible) { 8993 this._visibleForTheFirstTimeCalled = true; 8994 if (this.visibleForTheFirstTime !is null) { 8995 version(without_opengl) {} else { 8996 if(openglMode == OpenGlOptions.yes) { 8997 this.setAsCurrentOpenGlContextNT(); 8998 glViewport(0, 0, width, height); 8999 } 9000 } 9001 this.visibleForTheFirstTime(); 9002 } 9003 } 9004 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 9005 break; 9006 case WM_PAINT: { 9007 if (!this._visibleForTheFirstTimeCalled) { 9008 this._visibleForTheFirstTimeCalled = true; 9009 if (this.visibleForTheFirstTime !is null) { 9010 version(without_opengl) {} else { 9011 if(openglMode == OpenGlOptions.yes) { 9012 this.setAsCurrentOpenGlContextNT(); 9013 glViewport(0, 0, width, height); 9014 } 9015 } 9016 this.visibleForTheFirstTime(); 9017 } 9018 } 9019 9020 BITMAP bm; 9021 PAINTSTRUCT ps; 9022 9023 HDC hdc = BeginPaint(hwnd, &ps); 9024 9025 if(openglMode == OpenGlOptions.no) { 9026 9027 HDC hdcMem = CreateCompatibleDC(hdc); 9028 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 9029 9030 GetObject(buffer, bm.sizeof, &bm); 9031 9032 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 9033 if(resizability == Resizability.automaticallyScaleIfPossible) 9034 StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 9035 else 9036 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 9037 9038 SelectObject(hdcMem, hbmOld); 9039 DeleteDC(hdcMem); 9040 EndPaint(hwnd, &ps); 9041 } else { 9042 EndPaint(hwnd, &ps); 9043 version(without_opengl) {} else 9044 redrawOpenGlSceneNow(); 9045 } 9046 } break; 9047 default: 9048 return DefWindowProc(hwnd, msg, wParam, lParam); 9049 } 9050 return 0; 9051 9052 } 9053 } 9054 9055 mixin template NativeImageImplementation() { 9056 HBITMAP handle; 9057 ubyte* rawData; 9058 9059 final: 9060 9061 Color getPixel(int x, int y) { 9062 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 9063 // remember, bmps are upside down 9064 auto offset = itemsPerLine * (height - y - 1) + x * 3; 9065 9066 Color c; 9067 c.a = 255; 9068 c.b = rawData[offset + 0]; 9069 c.g = rawData[offset + 1]; 9070 c.r = rawData[offset + 2]; 9071 return c; 9072 } 9073 9074 void setPixel(int x, int y, Color c) { 9075 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 9076 // remember, bmps are upside down 9077 auto offset = itemsPerLine * (height - y - 1) + x * 3; 9078 9079 rawData[offset + 0] = c.b; 9080 rawData[offset + 1] = c.g; 9081 rawData[offset + 2] = c.r; 9082 } 9083 9084 void convertToRgbaBytes(ubyte[] where) { 9085 assert(where.length == this.width * this.height * 4); 9086 9087 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 9088 int idx = 0; 9089 int offset = itemsPerLine * (height - 1); 9090 // remember, bmps are upside down 9091 for(int y = height - 1; y >= 0; y--) { 9092 auto offsetStart = offset; 9093 for(int x = 0; x < width; x++) { 9094 where[idx + 0] = rawData[offset + 2]; // r 9095 where[idx + 1] = rawData[offset + 1]; // g 9096 where[idx + 2] = rawData[offset + 0]; // b 9097 where[idx + 3] = 255; // a 9098 idx += 4; 9099 offset += 3; 9100 } 9101 9102 offset = offsetStart - itemsPerLine; 9103 } 9104 } 9105 9106 void setFromRgbaBytes(in ubyte[] what) { 9107 assert(what.length == this.width * this.height * 4); 9108 9109 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 9110 int idx = 0; 9111 int offset = itemsPerLine * (height - 1); 9112 // remember, bmps are upside down 9113 for(int y = height - 1; y >= 0; y--) { 9114 auto offsetStart = offset; 9115 for(int x = 0; x < width; x++) { 9116 rawData[offset + 2] = what[idx + 0]; // r 9117 rawData[offset + 1] = what[idx + 1]; // g 9118 rawData[offset + 0] = what[idx + 2]; // b 9119 //where[idx + 3] = 255; // a 9120 idx += 4; 9121 offset += 3; 9122 } 9123 9124 offset = offsetStart - itemsPerLine; 9125 } 9126 } 9127 9128 9129 void createImage(int width, int height, bool forcexshm=false) { 9130 BITMAPINFO infoheader; 9131 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 9132 infoheader.bmiHeader.biWidth = width; 9133 infoheader.bmiHeader.biHeight = height; 9134 infoheader.bmiHeader.biPlanes = 1; 9135 infoheader.bmiHeader.biBitCount = 24; 9136 infoheader.bmiHeader.biCompression = BI_RGB; 9137 9138 handle = CreateDIBSection( 9139 null, 9140 &infoheader, 9141 DIB_RGB_COLORS, 9142 cast(void**) &rawData, 9143 null, 9144 0); 9145 if(handle is null) 9146 throw new WindowsApiException("create image failed"); 9147 9148 } 9149 9150 void dispose() { 9151 DeleteObject(handle); 9152 } 9153 } 9154 9155 enum KEY_ESCAPE = 27; 9156 } 9157 version(X11) { 9158 /// This is the default font used. You might change this before doing anything else with 9159 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 9160 /// for cross-platform compatibility. 9161 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 9162 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 9163 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 9164 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 9165 9166 alias int delegate(XEvent) NativeEventHandler; 9167 alias Window NativeWindowHandle; 9168 9169 enum KEY_ESCAPE = 9; 9170 9171 mixin template NativeScreenPainterImplementation() { 9172 Display* display; 9173 Drawable d; 9174 Drawable destiny; 9175 9176 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 9177 GC gc; 9178 9179 __gshared bool fontAttempted; 9180 9181 __gshared XFontStruct* defaultfont; 9182 __gshared XFontSet defaultfontset; 9183 9184 XFontStruct* font; 9185 XFontSet fontset; 9186 9187 void create(NativeWindowHandle window) { 9188 this.display = XDisplayConnection.get(); 9189 9190 Drawable buffer = None; 9191 if(auto sw = cast(SimpleWindow) this.window) { 9192 buffer = sw.impl.buffer; 9193 this.destiny = cast(Drawable) window; 9194 } else { 9195 buffer = cast(Drawable) window; 9196 this.destiny = None; 9197 } 9198 9199 this.d = cast(Drawable) buffer; 9200 9201 auto dgc = DefaultGC(display, DefaultScreen(display)); 9202 9203 this.gc = XCreateGC(display, d, 0, null); 9204 9205 XCopyGC(display, dgc, 0xffffffff, this.gc); 9206 9207 if(!fontAttempted) { 9208 font = XLoadQueryFont(display, xfontstr.ptr); 9209 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 9210 if(font is null) 9211 font = XLoadQueryFont(display, "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*".ptr); 9212 9213 char** lol; 9214 int lol2; 9215 char* lol3; 9216 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 9217 9218 fontAttempted = true; 9219 9220 defaultfont = font; 9221 defaultfontset = fontset; 9222 } 9223 9224 font = defaultfont; 9225 fontset = defaultfontset; 9226 9227 if(font) { 9228 XSetFont(display, gc, font.fid); 9229 } 9230 } 9231 9232 arsd.color.Rectangle _clipRectangle; 9233 void setClipRectangle(int x, int y, int width, int height) { 9234 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 9235 if(width == 0 || height == 0) 9236 XSetClipMask(display, gc, None); 9237 else { 9238 XRectangle[1] rects; 9239 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 9240 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 9241 } 9242 } 9243 9244 9245 void setFont(OperatingSystemFont font) { 9246 if(font && font.font) { 9247 this.font = font.font; 9248 this.fontset = font.fontset; 9249 XSetFont(display, gc, font.font.fid); 9250 } else { 9251 this.font = defaultfont; 9252 this.fontset = defaultfontset; 9253 } 9254 9255 } 9256 9257 void dispose() { 9258 this.rasterOp = RasterOp.normal; 9259 9260 // FIXME: this.window.width/height is probably wrong 9261 9262 // src x,y then dest x, y 9263 if(destiny != None) { 9264 XSetClipMask(display, gc, None); 9265 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 9266 } 9267 9268 XFreeGC(display, gc); 9269 9270 version(none) // we don't want to free it because we can use it later 9271 if(font) 9272 XFreeFont(display, font); 9273 version(none) // we don't want to free it because we can use it later 9274 if(fontset) 9275 XFreeFontSet(display, fontset); 9276 XFlush(display); 9277 9278 if(window.paintingFinishedDg !is null) 9279 window.paintingFinishedDg(); 9280 } 9281 9282 bool backgroundIsNotTransparent = true; 9283 bool foregroundIsNotTransparent = true; 9284 9285 bool _penInitialized = false; 9286 Pen _activePen; 9287 9288 Color _outlineColor; 9289 Color _fillColor; 9290 9291 @property void pen(Pen p) { 9292 if(_penInitialized && p == _activePen) { 9293 return; 9294 } 9295 _penInitialized = true; 9296 _activePen = p; 9297 _outlineColor = p.color; 9298 9299 int style; 9300 9301 byte dashLength; 9302 9303 final switch(p.style) { 9304 case Pen.Style.Solid: 9305 style = 0 /*LineSolid*/; 9306 break; 9307 case Pen.Style.Dashed: 9308 style = 1 /*LineOnOffDash*/; 9309 dashLength = 4; 9310 break; 9311 case Pen.Style.Dotted: 9312 style = 1 /*LineOnOffDash*/; 9313 dashLength = 1; 9314 break; 9315 } 9316 9317 XSetLineAttributes(display, gc, p.width, style, 0, 0); 9318 if(dashLength) 9319 XSetDashes(display, gc, 0, &dashLength, 1); 9320 9321 if(p.color.a == 0) { 9322 foregroundIsNotTransparent = false; 9323 return; 9324 } 9325 9326 foregroundIsNotTransparent = true; 9327 9328 XSetForeground(display, gc, colorToX(p.color, display)); 9329 } 9330 9331 RasterOp _currentRasterOp; 9332 bool _currentRasterOpInitialized = false; 9333 @property void rasterOp(RasterOp op) { 9334 if(_currentRasterOpInitialized && _currentRasterOp == op) 9335 return; 9336 _currentRasterOp = op; 9337 _currentRasterOpInitialized = true; 9338 int mode; 9339 final switch(op) { 9340 case RasterOp.normal: 9341 mode = GXcopy; 9342 break; 9343 case RasterOp.xor: 9344 mode = GXxor; 9345 break; 9346 } 9347 XSetFunction(display, gc, mode); 9348 } 9349 9350 9351 bool _fillColorInitialized = false; 9352 9353 @property void fillColor(Color c) { 9354 if(_fillColorInitialized && _fillColor == c) 9355 return; // already good, no need to waste time calling it 9356 _fillColor = c; 9357 _fillColorInitialized = true; 9358 if(c.a == 0) { 9359 backgroundIsNotTransparent = false; 9360 return; 9361 } 9362 9363 backgroundIsNotTransparent = true; 9364 9365 XSetBackground(display, gc, colorToX(c, display)); 9366 9367 } 9368 9369 void swapColors() { 9370 auto tmp = _fillColor; 9371 fillColor = _outlineColor; 9372 auto newPen = _activePen; 9373 newPen.color = tmp; 9374 pen(newPen); 9375 } 9376 9377 uint colorToX(Color c, Display* display) { 9378 auto visual = DefaultVisual(display, DefaultScreen(display)); 9379 import core.bitop; 9380 uint color = 0; 9381 { 9382 auto startBit = bsf(visual.red_mask); 9383 auto lastBit = bsr(visual.red_mask); 9384 auto r = cast(uint) c.r; 9385 r >>= 7 - (lastBit - startBit); 9386 r <<= startBit; 9387 color |= r; 9388 } 9389 { 9390 auto startBit = bsf(visual.green_mask); 9391 auto lastBit = bsr(visual.green_mask); 9392 auto g = cast(uint) c.g; 9393 g >>= 7 - (lastBit - startBit); 9394 g <<= startBit; 9395 color |= g; 9396 } 9397 { 9398 auto startBit = bsf(visual.blue_mask); 9399 auto lastBit = bsr(visual.blue_mask); 9400 auto b = cast(uint) c.b; 9401 b >>= 7 - (lastBit - startBit); 9402 b <<= startBit; 9403 color |= b; 9404 } 9405 9406 9407 9408 return color; 9409 } 9410 9411 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 9412 // source x, source y 9413 if(ix >= i.width) return; 9414 if(iy >= i.height) return; 9415 if(ix + w > i.width) w = i.width - ix; 9416 if(iy + h > i.height) h = i.height - iy; 9417 if(i.usingXshm) 9418 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 9419 else 9420 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 9421 } 9422 9423 void drawPixmap(Sprite s, int x, int y) { 9424 XCopyArea(display, s.handle, d, gc, 0, 0, s.width, s.height, x, y); 9425 } 9426 9427 int fontHeight() { 9428 if(font) 9429 return font.max_bounds.ascent + font.max_bounds.descent; 9430 return 12; // pretty common default... 9431 } 9432 9433 Size textSize(in char[] text) { 9434 auto maxWidth = 0; 9435 auto lineHeight = fontHeight; 9436 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 9437 foreach(line; text.split('\n')) { 9438 int textWidth; 9439 if(font) 9440 // FIXME: unicode 9441 textWidth = XTextWidth( font, line.ptr, cast(int) line.length); 9442 else 9443 textWidth = fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 9444 9445 if(textWidth > maxWidth) 9446 maxWidth = textWidth; 9447 h += lineHeight + 4; 9448 } 9449 return Size(maxWidth, h); 9450 } 9451 9452 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 9453 // FIXME: we should actually draw unicode.. but until then, I'm going to strip out multibyte chars 9454 const(char)[] text; 9455 if(fontset) 9456 text = originalText; 9457 else { 9458 text.reserve(originalText.length); 9459 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 9460 // then strip the rest so there isn't garbage 9461 foreach(dchar ch; originalText) 9462 if(ch < 256) 9463 text ~= cast(ubyte) ch; 9464 else 9465 text ~= 191; // FIXME: using a random character to fill the space 9466 } 9467 if(text.length == 0) 9468 return; 9469 9470 9471 int textHeight = 12; 9472 9473 // FIXME: should we clip it to the bounding box? 9474 9475 if(font) { 9476 textHeight = font.max_bounds.ascent + font.max_bounds.descent; 9477 } 9478 9479 auto lines = text.split('\n'); 9480 9481 auto lineHeight = textHeight; 9482 textHeight *= lines.length; 9483 9484 int cy = y; 9485 9486 if(alignment & TextAlignment.VerticalBottom) { 9487 assert(y2); 9488 auto h = y2 - y; 9489 if(h > textHeight) { 9490 cy += h - textHeight; 9491 cy -= lineHeight / 2; 9492 } 9493 } else if(alignment & TextAlignment.VerticalCenter) { 9494 assert(y2); 9495 auto h = y2 - y; 9496 if(textHeight < h) { 9497 cy += (h - textHeight) / 2; 9498 //cy -= lineHeight / 4; 9499 } 9500 } 9501 9502 foreach(line; text.split('\n')) { 9503 int textWidth; 9504 if(font) 9505 // FIXME: unicode 9506 textWidth = XTextWidth( font, line.ptr, cast(int) line.length); 9507 else 9508 textWidth = 12 * cast(int) line.length; 9509 9510 int px = x, py = cy; 9511 9512 if(alignment & TextAlignment.Center) { 9513 assert(x2); 9514 auto w = x2 - x; 9515 if(w > textWidth) 9516 px += (w - textWidth) / 2; 9517 } else if(alignment & TextAlignment.Right) { 9518 assert(x2); 9519 auto pos = x2 - textWidth; 9520 if(pos > x) 9521 px = pos; 9522 } 9523 9524 if(fontset) 9525 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 9526 9527 else 9528 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 9529 cy += lineHeight + 4; 9530 } 9531 } 9532 9533 void drawPixel(int x, int y) { 9534 XDrawPoint(display, d, gc, x, y); 9535 } 9536 9537 // The basic shapes, outlined 9538 9539 void drawLine(int x1, int y1, int x2, int y2) { 9540 if(foregroundIsNotTransparent) 9541 XDrawLine(display, d, gc, x1, y1, x2, y2); 9542 } 9543 9544 void drawRectangle(int x, int y, int width, int height) { 9545 if(backgroundIsNotTransparent) { 9546 swapColors(); 9547 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 9548 swapColors(); 9549 } 9550 if(foregroundIsNotTransparent) 9551 XDrawRectangle(display, d, gc, x, y, width - 1, height - 1); 9552 } 9553 9554 /// Arguments are the points of the bounding rectangle 9555 void drawEllipse(int x1, int y1, int x2, int y2) { 9556 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 9557 } 9558 9559 // NOTE: start and finish are in units of degrees * 64 9560 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 9561 if(backgroundIsNotTransparent) { 9562 swapColors(); 9563 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 9564 swapColors(); 9565 } 9566 if(foregroundIsNotTransparent) 9567 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 9568 } 9569 9570 void drawPolygon(Point[] vertexes) { 9571 XPoint[16] pointsBuffer; 9572 XPoint[] points; 9573 if(vertexes.length <= pointsBuffer.length) 9574 points = pointsBuffer[0 .. vertexes.length]; 9575 else 9576 points.length = vertexes.length; 9577 9578 foreach(i, p; vertexes) { 9579 points[i].x = cast(short) p.x; 9580 points[i].y = cast(short) p.y; 9581 } 9582 9583 if(backgroundIsNotTransparent) { 9584 swapColors(); 9585 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 9586 swapColors(); 9587 } 9588 if(foregroundIsNotTransparent) { 9589 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 9590 } 9591 } 9592 } 9593 9594 class XDisconnectException : Exception { 9595 bool userRequested; 9596 this(bool userRequested = true) { 9597 this.userRequested = userRequested; 9598 super("X disconnected"); 9599 } 9600 } 9601 9602 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a Display* 9603 class XDisplayConnection { 9604 private __gshared Display* display; 9605 private __gshared XIM xim; 9606 private __gshared char* displayName; 9607 9608 private __gshared int connectionSequence_; 9609 9610 /// use this for lazy caching when reconnection 9611 static int connectionSequenceNumber() { return connectionSequence_; } 9612 9613 /// Attempts recreation of state, may require application assistance 9614 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 9615 /// then call this, and if successful, reenter the loop. 9616 static void discardAndRecreate(string newDisplayString = null) { 9617 if(insideXEventLoop) 9618 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 9619 9620 // 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 9621 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 9622 9623 foreach(handle; chnenhm) { 9624 handle.discardConnectionState(); 9625 } 9626 9627 discardState(); 9628 9629 if(newDisplayString !is null) 9630 setDisplayName(newDisplayString); 9631 9632 auto display = get(); 9633 9634 foreach(handle; chnenhm) { 9635 handle.recreateAfterDisconnect(); 9636 } 9637 } 9638 9639 private __gshared EventMask rootEventMask; 9640 9641 /++ 9642 Requests the specified input from the root window on the connection, in addition to any other request. 9643 9644 9645 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. 9646 9647 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 9648 +/ 9649 static void addRootInput(EventMask mask) { 9650 auto old = rootEventMask; 9651 rootEventMask |= mask; 9652 get(); // to ensure display connected 9653 if(display !is null && rootEventMask != old) 9654 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 9655 } 9656 9657 static void discardState() { 9658 freeImages(); 9659 9660 foreach(atomPtr; interredAtoms) 9661 *atomPtr = 0; 9662 interredAtoms = null; 9663 interredAtoms.assumeSafeAppend(); 9664 9665 ScreenPainterImplementation.fontAttempted = false; 9666 ScreenPainterImplementation.defaultfont = null; 9667 ScreenPainterImplementation.defaultfontset = null; 9668 9669 Image.impl.xshmQueryCompleted = false; 9670 Image.impl._xshmAvailable = false; 9671 9672 SimpleWindow.nativeMapping = null; 9673 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 9674 // GlobalHotkeyManager 9675 9676 display = null; 9677 xim = null; 9678 } 9679 9680 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 9681 private static void createXIM () { 9682 import core.stdc.locale : setlocale, LC_ALL; 9683 import core.stdc.stdio : stderr, fprintf; 9684 import core.stdc.stdlib : free; 9685 import core.stdc.string : strdup; 9686 9687 static immutable string[3] mtry = [ null, "@im=local", "@im=" ]; 9688 9689 auto olocale = strdup(setlocale(LC_ALL, null)); 9690 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 9691 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 9692 9693 //fprintf(stderr, "opening IM...\n"); 9694 foreach (string s; mtry) { 9695 if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 9696 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 9697 } 9698 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 9699 } 9700 9701 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 9702 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 9703 static struct ImgList { 9704 size_t img; // class; hide it from GC 9705 ImgList* next; 9706 } 9707 9708 static __gshared ImgList* imglist = null; 9709 static __gshared bool imglistLocked = false; // true: don't register and unregister images 9710 9711 static void registerImage (Image img) { 9712 if (!imglistLocked && img !is null) { 9713 import core.stdc.stdlib : malloc; 9714 auto it = cast(ImgList*)malloc(ImgList.sizeof); 9715 assert(it !is null); // do proper checks 9716 it.img = cast(size_t)cast(void*)img; 9717 it.next = imglist; 9718 imglist = it; 9719 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 9720 } 9721 } 9722 9723 static void unregisterImage (Image img) { 9724 if (!imglistLocked && img !is null) { 9725 import core.stdc.stdlib : free; 9726 ImgList* prev = null; 9727 ImgList* cur = imglist; 9728 while (cur !is null) { 9729 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 9730 prev = cur; 9731 cur = cur.next; 9732 } 9733 if (cur !is null) { 9734 if (prev is null) imglist = cur.next; else prev.next = cur.next; 9735 free(cur); 9736 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 9737 } else { 9738 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 9739 } 9740 } 9741 } 9742 9743 static void freeImages () { // needed for discardAndRecreate 9744 imglistLocked = true; 9745 scope(exit) imglistLocked = false; 9746 ImgList* cur = imglist; 9747 ImgList* next = null; 9748 while (cur !is null) { 9749 import core.stdc.stdlib : free; 9750 next = cur.next; 9751 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 9752 (cast(Image)cast(void*)cur.img).dispose(); 9753 free(cur); 9754 cur = next; 9755 } 9756 imglist = null; 9757 } 9758 9759 /// can be used to override normal handling of display name 9760 /// from environment and/or command line 9761 static setDisplayName(string newDisplayName) { 9762 displayName = cast(char*) (newDisplayName ~ '\0'); 9763 } 9764 9765 /// resets to the default display string 9766 static resetDisplayName() { 9767 displayName = null; 9768 } 9769 9770 /// 9771 static Display* get() { 9772 if(display is null) { 9773 if(!librariesSuccessfullyLoaded) 9774 throw new Exception("Unable to load X11 client libraries"); 9775 display = XOpenDisplay(displayName); 9776 connectionSequence_++; 9777 if(display is null) 9778 throw new Exception("Unable to open X display"); 9779 XSetIOErrorHandler(&x11ioerrCB); 9780 Bool sup; 9781 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 9782 createXIM(); 9783 version(with_eventloop) { 9784 import arsd.eventloop; 9785 addFileEventListeners(display.fd, &eventListener, null, null); 9786 } 9787 } 9788 9789 return display; 9790 } 9791 9792 extern(C) 9793 static int x11ioerrCB(Display* dpy) { 9794 throw new XDisconnectException(false); 9795 } 9796 9797 version(with_eventloop) { 9798 import arsd.eventloop; 9799 static void eventListener(OsFileHandle fd) { 9800 //this.mtLock(); 9801 //scope(exit) this.mtUnlock(); 9802 while(XPending(display)) 9803 doXNextEvent(display); 9804 } 9805 } 9806 9807 // close connection on program exit -- we need this to properly free all images 9808 shared static ~this () { close(); } 9809 9810 /// 9811 static void close() { 9812 if(display is null) 9813 return; 9814 9815 version(with_eventloop) { 9816 import arsd.eventloop; 9817 removeFileEventListeners(display.fd); 9818 } 9819 9820 // now remove all registered images to prevent shared memory leaks 9821 freeImages(); 9822 9823 XCloseDisplay(display); 9824 display = null; 9825 } 9826 } 9827 9828 mixin template NativeImageImplementation() { 9829 XImage* handle; 9830 ubyte* rawData; 9831 9832 XShmSegmentInfo shminfo; 9833 9834 __gshared bool xshmQueryCompleted; 9835 __gshared bool _xshmAvailable; 9836 public static @property bool xshmAvailable() { 9837 if(!xshmQueryCompleted) { 9838 int i1, i2, i3; 9839 xshmQueryCompleted = true; 9840 9841 auto str = XDisplayConnection.get().display_name; 9842 // only if we are actually on the same machine does this 9843 // have any hope, and the query extension only asks if 9844 // the server can in theory, not in practice. 9845 if(str is null || (str[0] != ':' && str[0] != '/')) 9846 _xshmAvailable = false; 9847 else 9848 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 9849 } 9850 return _xshmAvailable; 9851 } 9852 9853 bool usingXshm; 9854 final: 9855 9856 void createImage(int width, int height, bool forcexshm=false) { 9857 auto display = XDisplayConnection.get(); 9858 assert(display !is null); 9859 auto screen = DefaultScreen(display); 9860 9861 // it will only use shared memory for somewhat largish images, 9862 // since otherwise we risk wasting shared memory handles on a lot of little ones 9863 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 9864 usingXshm = true; 9865 handle = XShmCreateImage( 9866 display, 9867 DefaultVisual(display, screen), 9868 24, 9869 ImageFormat.ZPixmap, 9870 null, 9871 &shminfo, 9872 width, height); 9873 assert(handle !is null); 9874 9875 assert(handle.bytes_per_line == 4 * width); 9876 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 9877 //import std.conv; import core.stdc.errno; 9878 assert(shminfo.shmid >= 0);//, to!string(errno)); 9879 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 9880 assert(rawData != cast(ubyte*) -1); 9881 shminfo.readOnly = 0; 9882 XShmAttach(display, &shminfo); 9883 XDisplayConnection.registerImage(this); 9884 } else { 9885 if (forcexshm) throw new Exception("can't create XShm Image"); 9886 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 9887 import core.stdc.stdlib : malloc; 9888 rawData = cast(ubyte*) malloc(width * height * 4); 9889 9890 handle = XCreateImage( 9891 display, 9892 DefaultVisual(display, screen), 9893 24, // bpp 9894 ImageFormat.ZPixmap, 9895 0, // offset 9896 rawData, 9897 width, height, 9898 8 /* FIXME */, 4 * width); // padding, bytes per line 9899 } 9900 } 9901 9902 void dispose() { 9903 // note: this calls free(rawData) for us 9904 if(handle) { 9905 if (usingXshm) { 9906 XDisplayConnection.unregisterImage(this); 9907 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 9908 } 9909 XDestroyImage(handle); 9910 if(usingXshm) { 9911 shmdt(shminfo.shmaddr); 9912 shmctl(shminfo.shmid, IPC_RMID, null); 9913 } 9914 handle = null; 9915 } 9916 } 9917 9918 Color getPixel(int x, int y) { 9919 auto offset = (y * width + x) * 4; 9920 Color c; 9921 c.a = 255; 9922 c.b = rawData[offset + 0]; 9923 c.g = rawData[offset + 1]; 9924 c.r = rawData[offset + 2]; 9925 return c; 9926 } 9927 9928 void setPixel(int x, int y, Color c) { 9929 auto offset = (y * width + x) * 4; 9930 rawData[offset + 0] = c.b; 9931 rawData[offset + 1] = c.g; 9932 rawData[offset + 2] = c.r; 9933 } 9934 9935 void convertToRgbaBytes(ubyte[] where) { 9936 assert(where.length == this.width * this.height * 4); 9937 9938 // if rawData had a length.... 9939 //assert(rawData.length == where.length); 9940 for(int idx = 0; idx < where.length; idx += 4) { 9941 where[idx + 0] = rawData[idx + 2]; // r 9942 where[idx + 1] = rawData[idx + 1]; // g 9943 where[idx + 2] = rawData[idx + 0]; // b 9944 where[idx + 3] = 255; // a 9945 } 9946 } 9947 9948 void setFromRgbaBytes(in ubyte[] where) { 9949 assert(where.length == this.width * this.height * 4); 9950 9951 // if rawData had a length.... 9952 //assert(rawData.length == where.length); 9953 for(int idx = 0; idx < where.length; idx += 4) { 9954 rawData[idx + 2] = where[idx + 0]; // r 9955 rawData[idx + 1] = where[idx + 1]; // g 9956 rawData[idx + 0] = where[idx + 2]; // b 9957 //rawData[idx + 3] = 255; // a 9958 } 9959 } 9960 9961 } 9962 9963 mixin template NativeSimpleWindowImplementation() { 9964 GC gc; 9965 Window window; 9966 Display* display; 9967 9968 Pixmap buffer; 9969 int bufferw, bufferh; // size of the buffer; can be bigger than window 9970 XIC xic; // input context 9971 int curHidden = 0; // counter 9972 Cursor blankCurPtr = 0; 9973 int cursorSequenceNumber = 0; 9974 int warpEventCount = 0; // number of mouse movement events to eat 9975 9976 void delegate(XEvent) setSelectionHandler; 9977 void delegate(in char[]) getSelectionHandler; 9978 9979 version(without_opengl) {} else 9980 GLXContext glc; 9981 9982 private void fixFixedSize(bool forced=false) (int width, int height) { 9983 if (forced || this.resizability == Resizability.fixedSize) { 9984 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 9985 XSizeHints sh; 9986 static if (!forced) { 9987 c_long spr; 9988 XGetWMNormalHints(display, window, &sh, &spr); 9989 sh.flags |= PMaxSize | PMinSize; 9990 } else { 9991 sh.flags = PMaxSize | PMinSize; 9992 } 9993 sh.min_width = width; 9994 sh.min_height = height; 9995 sh.max_width = width; 9996 sh.max_height = height; 9997 XSetWMNormalHints(display, window, &sh); 9998 //XFlush(display); 9999 } 10000 } 10001 10002 ScreenPainter getPainter() { 10003 return ScreenPainter(this, window); 10004 } 10005 10006 void move(int x, int y) { 10007 XMoveWindow(display, window, x, y); 10008 } 10009 10010 void resize(int w, int h) { 10011 if (w < 1) w = 1; 10012 if (h < 1) h = 1; 10013 XResizeWindow(display, window, w, h); 10014 10015 // calling this now to avoid waiting for the server to 10016 // acknowledge the resize; draws without returning to the 10017 // event loop will thus actually work. the server's event 10018 // btw might overrule this and resize it again 10019 recordX11Resize(display, this, w, h); 10020 10021 // FIXME: do we need to set this as the opengl context to do the glViewport change? 10022 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 10023 } 10024 10025 void moveResize (int x, int y, int w, int h) { 10026 if (w < 1) w = 1; 10027 if (h < 1) h = 1; 10028 XMoveResizeWindow(display, window, x, y, w, h); 10029 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 10030 } 10031 10032 void hideCursor () { 10033 if (curHidden++ == 0) { 10034 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 10035 static const(char)[1] cmbmp = 0; 10036 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 10037 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 10038 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 10039 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 10040 XFreePixmap(display, pm); 10041 } 10042 XDefineCursor(display, window, blankCurPtr); 10043 } 10044 } 10045 10046 void showCursor () { 10047 if (--curHidden == 0) XUndefineCursor(display, window); 10048 } 10049 10050 void warpMouse (int x, int y) { 10051 // here i will send dummy "ignore next mouse motion" event, 10052 // 'cause `XWarpPointer()` sends synthesised mouse motion, 10053 // and we don't need to report it to the user (as warping is 10054 // used when the user needs movement deltas). 10055 //XClientMessageEvent xclient; 10056 XEvent e; 10057 e.xclient.type = EventType.ClientMessage; 10058 e.xclient.window = window; 10059 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 10060 e.xclient.format = 32; 10061 e.xclient.data.l[0] = 0; 10062 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 10063 //{ 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]); } 10064 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 10065 // now warp pointer... 10066 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 10067 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 10068 // ...and flush 10069 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 10070 XFlush(display); 10071 } 10072 10073 void sendDummyEvent () { 10074 // here i will send dummy event to ping event queue 10075 XEvent e; 10076 e.xclient.type = EventType.ClientMessage; 10077 e.xclient.window = window; 10078 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 10079 e.xclient.format = 32; 10080 e.xclient.data.l[0] = 0; 10081 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 10082 XFlush(display); 10083 } 10084 10085 void setTitle(string title) { 10086 if (title.ptr is null) title = ""; 10087 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 10088 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 10089 XTextProperty windowName; 10090 windowName.value = title.ptr; 10091 windowName.encoding = XA_UTF8; //XA_STRING; 10092 windowName.format = 8; 10093 windowName.nitems = cast(uint)title.length; 10094 XSetWMName(display, window, &windowName); 10095 char[1024] namebuf = 0; 10096 auto maxlen = namebuf.length-1; 10097 if (maxlen > title.length) maxlen = title.length; 10098 namebuf[0..maxlen] = title[0..maxlen]; 10099 XStoreName(display, window, namebuf.ptr); 10100 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 10101 flushGui(); // without this OpenGL windows has a LONG delay before changing title 10102 } 10103 10104 string[] getTitles() { 10105 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 10106 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 10107 XTextProperty textProp; 10108 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 10109 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 10110 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 10111 } else 10112 return []; 10113 } else 10114 return null; 10115 } 10116 10117 string getTitle() { 10118 auto titles = getTitles(); 10119 return titles.length ? titles[0] : null; 10120 } 10121 10122 void setMinSize (int minwidth, int minheight) { 10123 import core.stdc.config : c_long; 10124 if (minwidth < 1) minwidth = 1; 10125 if (minheight < 1) minheight = 1; 10126 XSizeHints sh; 10127 c_long spr; 10128 XGetWMNormalHints(display, window, &sh, &spr); 10129 sh.min_width = minwidth; 10130 sh.min_height = minheight; 10131 sh.flags |= PMinSize; 10132 XSetWMNormalHints(display, window, &sh); 10133 flushGui(); 10134 } 10135 10136 void setMaxSize (int maxwidth, int maxheight) { 10137 import core.stdc.config : c_long; 10138 if (maxwidth < 1) maxwidth = 1; 10139 if (maxheight < 1) maxheight = 1; 10140 XSizeHints sh; 10141 c_long spr; 10142 XGetWMNormalHints(display, window, &sh, &spr); 10143 sh.max_width = maxwidth; 10144 sh.max_height = maxheight; 10145 sh.flags |= PMaxSize; 10146 XSetWMNormalHints(display, window, &sh); 10147 flushGui(); 10148 } 10149 10150 void setResizeGranularity (int granx, int grany) { 10151 import core.stdc.config : c_long; 10152 if (granx < 1) granx = 1; 10153 if (grany < 1) grany = 1; 10154 XSizeHints sh; 10155 c_long spr; 10156 XGetWMNormalHints(display, window, &sh, &spr); 10157 sh.width_inc = granx; 10158 sh.height_inc = grany; 10159 sh.flags |= PResizeInc; 10160 XSetWMNormalHints(display, window, &sh); 10161 flushGui(); 10162 } 10163 10164 void setOpacity (uint opacity) { 10165 if (opacity == uint.max) 10166 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 10167 else 10168 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 10169 XA_CARDINAL, 32, PropModeReplace, &opacity, 1); 10170 } 10171 10172 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 10173 version(without_opengl) {} else if(opengl == OpenGlOptions.yes && !openGlLibrariesSuccessfullyLoaded) throw new Exception("OpenGL libraries did not load"); 10174 display = XDisplayConnection.get(); 10175 auto screen = DefaultScreen(display); 10176 10177 version(without_opengl) {} 10178 else { 10179 if(opengl == OpenGlOptions.yes) { 10180 GLXFBConfig fbconf = null; 10181 XVisualInfo* vi = null; 10182 bool useLegacy = false; 10183 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 10184 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 10185 int[23] visualAttribs = [ 10186 GLX_X_RENDERABLE , 1/*True*/, 10187 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 10188 GLX_RENDER_TYPE , GLX_RGBA_BIT, 10189 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 10190 GLX_RED_SIZE , 8, 10191 GLX_GREEN_SIZE , 8, 10192 GLX_BLUE_SIZE , 8, 10193 GLX_ALPHA_SIZE , 8, 10194 GLX_DEPTH_SIZE , 24, 10195 GLX_STENCIL_SIZE , 8, 10196 GLX_DOUBLEBUFFER , 1/*True*/, 10197 0/*None*/, 10198 ]; 10199 int fbcount; 10200 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 10201 if (fbcount == 0) { 10202 useLegacy = true; // try to do at least something 10203 } else { 10204 // pick the FB config/visual with the most samples per pixel 10205 int bestidx = -1, bestns = -1; 10206 foreach (int fbi; 0..fbcount) { 10207 int sb, samples; 10208 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 10209 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 10210 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 10211 } 10212 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 10213 fbconf = fbc[bestidx]; 10214 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 10215 XFree(fbc); 10216 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 10217 } 10218 } 10219 if (vi is null || useLegacy) { 10220 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 10221 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 10222 useLegacy = true; 10223 } 10224 if (vi is null) throw new Exception("no open gl visual found"); 10225 10226 XSetWindowAttributes swa; 10227 auto root = RootWindow(display, screen); 10228 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 10229 10230 window = XCreateWindow(display, parent is null ? root : parent.impl.window, 10231 0, 0, width, height, 10232 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap, &swa); 10233 10234 // now try to use `glXCreateContextAttribsARB()` if it's here 10235 if (!useLegacy) { 10236 // request fairly advanced context, even with stencil buffer! 10237 int[9] contextAttribs = [ 10238 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 10239 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 10240 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 10241 // for modern context, set "forward compatibility" flag too 10242 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 10243 0/*None*/, 10244 ]; 10245 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 10246 if (glc is null && sdpyOpenGLContextAllowFallback) { 10247 sdpyOpenGLContextVersion = 0; 10248 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 10249 } 10250 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 10251 } else { 10252 // fallback to old GLX call 10253 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 10254 sdpyOpenGLContextVersion = 0; 10255 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 10256 } 10257 } 10258 // sync to ensure any errors generated are processed 10259 XSync(display, 0/*False*/); 10260 //{ import core.stdc.stdio; printf("ogl is here\n"); } 10261 if(glc is null) 10262 throw new Exception("glc"); 10263 } 10264 } 10265 10266 if(opengl == OpenGlOptions.no) { 10267 10268 bool overrideRedirect = false; 10269 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification) 10270 overrideRedirect = true; 10271 10272 XSetWindowAttributes swa; 10273 swa.background_pixel = WhitePixel(display, screen); 10274 swa.border_pixel = BlackPixel(display, screen); 10275 swa.override_redirect = overrideRedirect; 10276 auto root = RootWindow(display, screen); 10277 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 10278 10279 window = XCreateWindow(display, parent is null ? root : parent.impl.window, 10280 0, 0, width, height, 10281 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap | CWBackPixel | CWBorderPixel | CWOverrideRedirect, &swa); 10282 10283 10284 10285 /* 10286 window = XCreateSimpleWindow( 10287 display, 10288 parent is null ? RootWindow(display, screen) : parent.impl.window, 10289 0, 0, // x, y 10290 width, height, 10291 1, // border width 10292 BlackPixel(display, screen), // border 10293 WhitePixel(display, screen)); // background 10294 */ 10295 10296 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 10297 bufferw = width; 10298 bufferh = height; 10299 10300 gc = DefaultGC(display, screen); 10301 10302 // clear out the buffer to get us started... 10303 XSetForeground(display, gc, WhitePixel(display, screen)); 10304 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 10305 XSetForeground(display, gc, BlackPixel(display, screen)); 10306 } 10307 10308 // input context 10309 //TODO: create this only for top-level windows, and reuse that? 10310 if (XDisplayConnection.xim !is null) { 10311 xic = XCreateIC(XDisplayConnection.xim, 10312 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 10313 /*XNClientWindow*/"clientWindow".ptr, window, 10314 /*XNFocusWindow*/"focusWindow".ptr, window, 10315 null); 10316 if (xic is null) { 10317 import core.stdc.stdio : stderr, fprintf; 10318 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 10319 } 10320 } 10321 10322 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 10323 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 10324 // window class 10325 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 10326 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 10327 XClassHint klass; 10328 XWMHints wh; 10329 XSizeHints size; 10330 klass.res_name = sdpyWindowClassStr; 10331 klass.res_class = sdpyWindowClassStr; 10332 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 10333 } 10334 10335 setTitle(title); 10336 SimpleWindow.nativeMapping[window] = this; 10337 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 10338 10339 // This gives our window a close button 10340 if (windowType != WindowTypes.eventOnly) { 10341 Atom atom = XInternAtom(display, "WM_DELETE_WINDOW".ptr, true); // FIXME: does this need to be freed? 10342 XSetWMProtocols(display, window, &atom, 1); 10343 } 10344 10345 // FIXME: windowType and customizationFlags 10346 Atom[8] wsatoms; // here, due to goto 10347 int wmsacount = 0; // here, due to goto 10348 10349 try 10350 final switch(windowType) { 10351 case WindowTypes.normal: 10352 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 10353 break; 10354 case WindowTypes.undecorated: 10355 motifHideDecorations(); 10356 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 10357 break; 10358 case WindowTypes.eventOnly: 10359 _hidden = true; 10360 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 10361 goto hiddenWindow; 10362 //break; 10363 case WindowTypes.nestedChild: 10364 10365 break; 10366 10367 case WindowTypes.dropdownMenu: 10368 motifHideDecorations(); 10369 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 10370 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 10371 break; 10372 case WindowTypes.popupMenu: 10373 motifHideDecorations(); 10374 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 10375 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 10376 break; 10377 case WindowTypes.notification: 10378 motifHideDecorations(); 10379 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 10380 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 10381 break; 10382 /+ 10383 case WindowTypes.menu: 10384 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 10385 motifHideDecorations(); 10386 break; 10387 case WindowTypes.desktop: 10388 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 10389 break; 10390 case WindowTypes.dock: 10391 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 10392 break; 10393 case WindowTypes.toolbar: 10394 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 10395 break; 10396 case WindowTypes.menu: 10397 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 10398 break; 10399 case WindowTypes.utility: 10400 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 10401 break; 10402 case WindowTypes.splash: 10403 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 10404 break; 10405 case WindowTypes.dialog: 10406 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 10407 break; 10408 case WindowTypes.tooltip: 10409 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 10410 break; 10411 case WindowTypes.notification: 10412 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 10413 break; 10414 case WindowTypes.combo: 10415 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 10416 break; 10417 case WindowTypes.dnd: 10418 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 10419 break; 10420 +/ 10421 } 10422 catch(Exception e) { 10423 // XInternAtom failed, prolly a WM 10424 // that doesn't support these things 10425 } 10426 10427 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 10428 // the two following flags may be ignored by WM 10429 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 10430 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 10431 10432 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 10433 10434 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 10435 10436 // What would be ideal here is if they only were 10437 // selected if there was actually an event handler 10438 // for them... 10439 10440 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 10441 10442 hiddenWindow: 10443 10444 // set the pid property for lookup later by window managers 10445 // a standard convenience 10446 import core.sys.posix.unistd; 10447 arch_ulong pid = getpid(); 10448 10449 XChangeProperty( 10450 display, 10451 impl.window, 10452 GetAtom!("_NET_WM_PID", true)(display), 10453 XA_CARDINAL, 10454 32 /* bits */, 10455 0 /*PropModeReplace*/, 10456 &pid, 10457 1); 10458 10459 10460 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 10461 XMapWindow(display, window); 10462 } else { 10463 _hidden = true; 10464 } 10465 } 10466 10467 void selectDefaultInput(bool forceIncludeMouseMotion) { 10468 auto mask = EventMask.ExposureMask | 10469 EventMask.KeyPressMask | 10470 EventMask.KeyReleaseMask | 10471 EventMask.PropertyChangeMask | 10472 EventMask.FocusChangeMask | 10473 EventMask.StructureNotifyMask | 10474 EventMask.VisibilityChangeMask 10475 | EventMask.ButtonPressMask 10476 | EventMask.ButtonReleaseMask 10477 ; 10478 10479 // xshm is our shortcut for local connections 10480 if(Image.impl.xshmAvailable || forceIncludeMouseMotion) 10481 mask |= EventMask.PointerMotionMask; 10482 else 10483 mask |= EventMask.ButtonMotionMask; 10484 10485 XSelectInput(display, window, mask); 10486 } 10487 10488 10489 void setNetWMWindowType(Atom type) { 10490 Atom[2] atoms; 10491 10492 atoms[0] = type; 10493 // generic fallback 10494 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 10495 10496 XChangeProperty( 10497 display, 10498 impl.window, 10499 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 10500 XA_ATOM, 10501 32 /* bits */, 10502 0 /*PropModeReplace*/, 10503 atoms.ptr, 10504 cast(int) atoms.length); 10505 } 10506 10507 void motifHideDecorations() { 10508 MwmHints hints; 10509 hints.flags = MWM_HINTS_DECORATIONS; 10510 10511 XChangeProperty( 10512 display, 10513 impl.window, 10514 GetAtom!"_MOTIF_WM_HINTS"(display), 10515 GetAtom!"_MOTIF_WM_HINTS"(display), 10516 32 /* bits */, 10517 0 /*PropModeReplace*/, 10518 &hints, 10519 hints.sizeof / 4); 10520 } 10521 10522 /*k8: unused 10523 void createOpenGlContext() { 10524 10525 } 10526 */ 10527 10528 void closeWindow() { 10529 if (customEventFDRead != -1) { 10530 import core.sys.posix.unistd : close; 10531 auto same = customEventFDRead == customEventFDWrite; 10532 10533 close(customEventFDRead); 10534 if(!same) 10535 close(customEventFDWrite); 10536 customEventFDRead = -1; 10537 customEventFDWrite = -1; 10538 } 10539 if(buffer) 10540 XFreePixmap(display, buffer); 10541 bufferw = bufferh = 0; 10542 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 10543 XDestroyWindow(display, window); 10544 XFlush(display); 10545 } 10546 10547 void dispose() { 10548 } 10549 10550 bool destroyed = false; 10551 } 10552 10553 bool insideXEventLoop; 10554 } 10555 10556 version(X11) { 10557 10558 int mouseDoubleClickTimeout = 350; /// double click timeout. X only, you probably shouldn't change this. 10559 10560 void recordX11Resize(Display* display, SimpleWindow win, int width, int height) { 10561 if(width != win.width || height != win.height) { 10562 win._width = width; 10563 win._height = height; 10564 10565 if(win.openglMode == OpenGlOptions.no) { 10566 // FIXME: could this be more efficient? 10567 10568 if (win.bufferw < width || win.bufferh < height) { 10569 //{ 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); } 10570 // grow the internal buffer to match the window... 10571 auto newPixmap = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 10572 { 10573 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 10574 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 10575 scope(exit) XFreeGC(win.display, xgc); 10576 XSetClipMask(win.display, xgc, None); 10577 XSetForeground(win.display, xgc, 0); 10578 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, width, height); 10579 } 10580 XCopyArea(display, 10581 cast(Drawable) win.buffer, 10582 cast(Drawable) newPixmap, 10583 win.gc, 0, 0, 10584 win.bufferw < width ? win.bufferw : win.width, 10585 win.bufferh < height ? win.bufferh : win.height, 10586 0, 0); 10587 10588 XFreePixmap(display, win.buffer); 10589 win.buffer = newPixmap; 10590 win.bufferw = width; 10591 win.bufferh = height; 10592 } 10593 10594 // clear unused parts of the buffer 10595 if (win.bufferw > width || win.bufferh > height) { 10596 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 10597 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 10598 scope(exit) XFreeGC(win.display, xgc); 10599 XSetClipMask(win.display, xgc, None); 10600 XSetForeground(win.display, xgc, 0); 10601 immutable int maxw = (win.bufferw > width ? win.bufferw : width); 10602 immutable int maxh = (win.bufferh > height ? win.bufferh : height); 10603 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, width, 0, maxw, maxh); // let X11 do clipping 10604 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, height, maxw, maxh); // let X11 do clipping 10605 } 10606 10607 } 10608 10609 version(without_opengl) {} else 10610 if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) { 10611 glViewport(0, 0, width, height); 10612 } 10613 10614 win.fixFixedSize(width, height); //k8: this does nothing on my FluxBox; wtf?! 10615 10616 if(win.windowResized !is null) { 10617 XUnlockDisplay(display); 10618 scope(exit) XLockDisplay(display); 10619 win.windowResized(width, height); 10620 } 10621 } 10622 } 10623 10624 10625 /// Platform-specific, you might use it when doing a custom event loop 10626 bool doXNextEvent(Display* display) { 10627 bool done; 10628 XEvent e; 10629 XNextEvent(display, &e); 10630 version(sddddd) { 10631 import std.stdio, std.conv : to; 10632 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 10633 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 10634 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 10635 } 10636 } 10637 10638 // filter out compose events 10639 if (XFilterEvent(&e, None)) { 10640 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 10641 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 10642 return false; 10643 } 10644 // process keyboard mapping changes 10645 if (e.type == EventType.KeymapNotify) { 10646 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 10647 XRefreshKeyboardMapping(&e.xmapping); 10648 return false; 10649 } 10650 10651 version(with_eventloop) 10652 import arsd.eventloop; 10653 10654 if(SimpleWindow.handleNativeGlobalEvent !is null) { 10655 // see windows impl's comments 10656 XUnlockDisplay(display); 10657 scope(exit) XLockDisplay(display); 10658 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 10659 if(ret == 0) 10660 return done; 10661 } 10662 10663 10664 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 10665 if(win.getNativeEventHandler !is null) { 10666 XUnlockDisplay(display); 10667 scope(exit) XLockDisplay(display); 10668 auto ret = win.getNativeEventHandler()(e); 10669 if(ret == 0) 10670 return done; 10671 } 10672 } 10673 10674 switch(e.type) { 10675 case EventType.SelectionClear: 10676 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) 10677 { /* FIXME??????? */ } 10678 break; 10679 case EventType.SelectionRequest: 10680 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 10681 if(win.setSelectionHandler !is null) { 10682 XUnlockDisplay(display); 10683 scope(exit) XLockDisplay(display); 10684 win.setSelectionHandler(e); 10685 } 10686 break; 10687 case EventType.PropertyNotify: 10688 10689 break; 10690 case EventType.SelectionNotify: 10691 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 10692 if(win.getSelectionHandler !is null) { 10693 // FIXME: maybe we should call a different handler for PRIMARY vs CLIPBOARD 10694 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 10695 XUnlockDisplay(display); 10696 scope(exit) XLockDisplay(display); 10697 win.getSelectionHandler(null); 10698 } else { 10699 Atom target; 10700 int format; 10701 arch_ulong bytesafter, length; 10702 void* value; 10703 XGetWindowProperty( 10704 e.xselection.display, 10705 e.xselection.requestor, 10706 e.xselection.property, 10707 0, 10708 100000 /* length */, 10709 //false, /* don't erase it */ 10710 true, /* do erase it lol */ 10711 0 /*AnyPropertyType*/, 10712 &target, &format, &length, &bytesafter, &value); 10713 10714 // FIXME: I don't have to copy it now since it is in char[] instead of string 10715 10716 { 10717 XUnlockDisplay(display); 10718 scope(exit) XLockDisplay(display); 10719 10720 if(target == XA_ATOM) { 10721 // initial request, see what they are able to work with and request the best one 10722 // we can handle, if available 10723 10724 Atom[] answer = (cast(Atom*) value)[0 .. length]; 10725 Atom best = None; 10726 foreach(option; answer) { 10727 if(option == GetAtom!"UTF8_STRING"(display)) { 10728 best = option; 10729 break; 10730 } else if(option == XA_STRING) { 10731 best = option; 10732 } 10733 } 10734 10735 //writeln("got ", answer); 10736 10737 if(best != None) { 10738 // actually request the best format 10739 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 10740 } 10741 } else if(target == GetAtom!"UTF8_STRING"(display) || target == XA_STRING) { 10742 win.getSelectionHandler((cast(char[]) value[0 .. length]).idup); 10743 } else if(target == GetAtom!"INCR"(display)) { 10744 // incremental 10745 10746 //sdpyGettingPaste = true; // FIXME: should prolly be separate for the different selections 10747 10748 // FIXME: handle other events while it goes so app doesn't lock up with big pastes 10749 // can also be optimized if it chunks to the api somehow 10750 10751 char[] s; 10752 10753 do { 10754 10755 XEvent subevent; 10756 do { 10757 XMaskEvent(display, EventMask.PropertyChangeMask, &subevent); 10758 } while(subevent.type != EventType.PropertyNotify || subevent.xproperty.atom != e.xselection.property || subevent.xproperty.state != PropertyNotification.PropertyNewValue); 10759 10760 void* subvalue; 10761 XGetWindowProperty( 10762 e.xselection.display, 10763 e.xselection.requestor, 10764 e.xselection.property, 10765 0, 10766 100000 /* length */, 10767 true, /* erase it to signal we got it and want more */ 10768 0 /*AnyPropertyType*/, 10769 &target, &format, &length, &bytesafter, &subvalue); 10770 10771 s ~= (cast(char*) subvalue)[0 .. length]; 10772 10773 XFree(subvalue); 10774 } while(length > 0); 10775 10776 win.getSelectionHandler(s); 10777 } else { 10778 // unsupported type 10779 } 10780 } 10781 XFree(value); 10782 /* 10783 XDeleteProperty( 10784 e.xselection.display, 10785 e.xselection.requestor, 10786 e.xselection.property); 10787 */ 10788 } 10789 } 10790 break; 10791 case EventType.ConfigureNotify: 10792 auto event = e.xconfigure; 10793 if(auto win = event.window in SimpleWindow.nativeMapping) { 10794 //version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); } 10795 10796 recordX11Resize(display, *win, event.width, event.height); 10797 } 10798 break; 10799 case EventType.Expose: 10800 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 10801 // if it is closing from a popup menu, it can get 10802 // an Expose event right by the end and trigger a 10803 // BadDrawable error ... we'll just check 10804 // closed to handle that. 10805 if((*win).closed) break; 10806 if((*win).openglMode == OpenGlOptions.no) { 10807 bool doCopy = true; 10808 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 10809 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); 10810 } else { 10811 // need to redraw the scene somehow 10812 XUnlockDisplay(display); 10813 scope(exit) XLockDisplay(display); 10814 version(without_opengl) {} else 10815 win.redrawOpenGlSceneNow(); 10816 } 10817 } 10818 break; 10819 case EventType.FocusIn: 10820 case EventType.FocusOut: 10821 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 10822 if (win.xic !is null) { 10823 //{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); } 10824 if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic); 10825 } 10826 10827 win._focused = e.type == EventType.FocusIn; 10828 10829 if(win.demandingAttention) 10830 demandAttention(*win, false); 10831 10832 if(win.onFocusChange) { 10833 XUnlockDisplay(display); 10834 scope(exit) XLockDisplay(display); 10835 win.onFocusChange(e.type == EventType.FocusIn); 10836 } 10837 } 10838 break; 10839 case EventType.VisibilityNotify: 10840 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 10841 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 10842 if (win.visibilityChanged !is null) { 10843 XUnlockDisplay(display); 10844 scope(exit) XLockDisplay(display); 10845 win.visibilityChanged(false); 10846 } 10847 } else { 10848 if (win.visibilityChanged !is null) { 10849 XUnlockDisplay(display); 10850 scope(exit) XLockDisplay(display); 10851 win.visibilityChanged(true); 10852 } 10853 } 10854 } 10855 break; 10856 case EventType.ClientMessage: 10857 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 10858 // "ignore next mouse motion" event, increment ignore counter for teh window 10859 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 10860 ++(*win).warpEventCount; 10861 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 10862 } else { 10863 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 10864 } 10865 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 10866 // user clicked the close button on the window manager 10867 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 10868 XUnlockDisplay(display); 10869 scope(exit) XLockDisplay(display); 10870 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 10871 } 10872 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 10873 foreach(nai; NotificationAreaIcon.activeIcons) 10874 nai.newManager(); 10875 } 10876 break; 10877 case EventType.MapNotify: 10878 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 10879 (*win)._visible = true; 10880 if (!(*win)._visibleForTheFirstTimeCalled) { 10881 (*win)._visibleForTheFirstTimeCalled = true; 10882 if ((*win).visibleForTheFirstTime !is null) { 10883 XUnlockDisplay(display); 10884 scope(exit) XLockDisplay(display); 10885 version(without_opengl) {} else { 10886 if((*win).openglMode == OpenGlOptions.yes) { 10887 (*win).setAsCurrentOpenGlContextNT(); 10888 glViewport(0, 0, (*win).width, (*win).height); 10889 } 10890 } 10891 (*win).visibleForTheFirstTime(); 10892 } 10893 } 10894 if ((*win).visibilityChanged !is null) { 10895 XUnlockDisplay(display); 10896 scope(exit) XLockDisplay(display); 10897 (*win).visibilityChanged(true); 10898 } 10899 } 10900 break; 10901 case EventType.UnmapNotify: 10902 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 10903 win._visible = false; 10904 if (win.visibilityChanged !is null) { 10905 XUnlockDisplay(display); 10906 scope(exit) XLockDisplay(display); 10907 win.visibilityChanged(false); 10908 } 10909 } 10910 break; 10911 case EventType.DestroyNotify: 10912 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 10913 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 10914 win._closed = true; // just in case 10915 win.destroyed = true; 10916 if (win.xic !is null) { 10917 XDestroyIC(win.xic); 10918 win.xic = null; // just in calse 10919 } 10920 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 10921 bool anyImportant = false; 10922 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 10923 if(w.beingOpenKeepsAppOpen) { 10924 anyImportant = true; 10925 break; 10926 } 10927 if(!anyImportant) 10928 done = true; 10929 } 10930 auto window = e.xdestroywindow.window; 10931 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 10932 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 10933 10934 version(with_eventloop) { 10935 if(done) exit(); 10936 } 10937 break; 10938 10939 case EventType.MotionNotify: 10940 MouseEvent mouse; 10941 auto event = e.xmotion; 10942 10943 mouse.type = MouseEventType.motion; 10944 mouse.x = event.x; 10945 mouse.y = event.y; 10946 mouse.modifierState = event.state; 10947 10948 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 10949 mouse.window = *win; 10950 if (win.warpEventCount > 0) { 10951 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 10952 --(*win).warpEventCount; 10953 (*win).mdx(mouse); // so deltas will be correctly updated 10954 } else { 10955 win.warpEventCount = 0; // just in case 10956 (*win).mdx(mouse); 10957 if((*win).handleMouseEvent) { 10958 XUnlockDisplay(display); 10959 scope(exit) XLockDisplay(display); 10960 (*win).handleMouseEvent(mouse); 10961 } 10962 } 10963 } 10964 10965 version(with_eventloop) 10966 send(mouse); 10967 break; 10968 case EventType.ButtonPress: 10969 case EventType.ButtonRelease: 10970 MouseEvent mouse; 10971 auto event = e.xbutton; 10972 10973 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 10974 mouse.x = event.x; 10975 mouse.y = event.y; 10976 10977 static Time lastMouseDownTime = 0; 10978 10979 mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 10980 if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time; 10981 10982 switch(event.button) { 10983 case 1: mouse.button = MouseButton.left; break; // left 10984 case 2: mouse.button = MouseButton.middle; break; // middle 10985 case 3: mouse.button = MouseButton.right; break; // right 10986 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 10987 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 10988 case 6: break; // idk 10989 case 7: break; // idk 10990 case 8: mouse.button = MouseButton.backButton; break; 10991 case 9: mouse.button = MouseButton.forwardButton; break; 10992 default: 10993 } 10994 10995 // FIXME: double check this 10996 mouse.modifierState = event.state; 10997 10998 //mouse.modifierState = event.detail; 10999 11000 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 11001 mouse.window = *win; 11002 (*win).mdx(mouse); 11003 if((*win).handleMouseEvent) { 11004 XUnlockDisplay(display); 11005 scope(exit) XLockDisplay(display); 11006 (*win).handleMouseEvent(mouse); 11007 } 11008 } 11009 version(with_eventloop) 11010 send(mouse); 11011 break; 11012 11013 case EventType.KeyPress: 11014 case EventType.KeyRelease: 11015 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 11016 KeyEvent ke; 11017 ke.pressed = e.type == EventType.KeyPress; 11018 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 11019 11020 auto sym = XKeycodeToKeysym( 11021 XDisplayConnection.get(), 11022 e.xkey.keycode, 11023 0); 11024 11025 ke.key = cast(Key) sym;//e.xkey.keycode; 11026 11027 ke.modifierState = e.xkey.state; 11028 11029 // import std.stdio; writefln("%x", sym); 11030 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 11031 int charbuflen = 0; // return value of XwcLookupString 11032 if (ke.pressed) { 11033 auto win = e.xkey.window in SimpleWindow.nativeMapping; 11034 if (win !is null && win.xic !is null) { 11035 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 11036 Status status; 11037 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 11038 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 11039 } else { 11040 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 11041 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 11042 char[16] buffer; 11043 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 11044 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 11045 } 11046 } 11047 11048 // if there's no char, subst one 11049 if (charbuflen == 0) { 11050 switch (sym) { 11051 case 0xff09: charbuf[charbuflen++] = '\t'; break; 11052 case 0xff8d: // keypad enter 11053 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 11054 default : // ignore 11055 } 11056 } 11057 11058 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 11059 ke.window = *win; 11060 if (win.handleKeyEvent) { 11061 XUnlockDisplay(display); 11062 scope(exit) XLockDisplay(display); 11063 win.handleKeyEvent(ke); 11064 } 11065 11066 // char events are separate since they are on Windows too 11067 // also, xcompose can generate long char sequences 11068 // don't send char events if Meta and/or Hyper is pressed 11069 // TODO: ctrl+char should only send control chars; not yet 11070 if ((e.xkey.state&ModifierState.ctrl) != 0) { 11071 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 11072 } 11073 if (ke.pressed && charbuflen > 0 && (e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0) { 11074 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 11075 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 11076 if (win.handleCharEvent) { 11077 XUnlockDisplay(display); 11078 scope(exit) XLockDisplay(display); 11079 win.handleCharEvent(ch); 11080 } 11081 } 11082 } 11083 } 11084 11085 version(with_eventloop) 11086 send(ke); 11087 break; 11088 default: 11089 } 11090 11091 return done; 11092 } 11093 } 11094 11095 /* *************************************** */ 11096 /* Done with simpledisplay stuff */ 11097 /* *************************************** */ 11098 11099 // Necessary C library bindings follow 11100 version(Windows) {} else 11101 version(X11) { 11102 11103 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 11104 11105 // X11 bindings needed here 11106 /* 11107 A little of this is from the bindings project on 11108 D Source and some of it is copy/paste from the C 11109 header. 11110 11111 The DSource listing consistently used D's long 11112 where C used long. That's wrong - C long is 32 bit, so 11113 it should be int in D. I changed that here. 11114 11115 Note: 11116 This isn't complete, just took what I needed for myself. 11117 */ 11118 11119 import core.stdc.stddef : wchar_t; 11120 11121 interface XLib { 11122 extern(C) nothrow @nogc { 11123 char* XResourceManagerString(Display*); 11124 void XrmInitialize(); 11125 XrmDatabase XrmGetStringDatabase(char* data); 11126 bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 11127 11128 Cursor XCreateFontCursor(Display*, uint shape); 11129 int XDefineCursor(Display* display, Window w, Cursor cursor); 11130 int XUndefineCursor(Display* display, Window w); 11131 11132 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 11133 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 11134 int XFreeCursor(Display* display, Cursor cursor); 11135 11136 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 11137 11138 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 11139 11140 char *XKeysymToString(KeySym keysym); 11141 KeySym XKeycodeToKeysym( 11142 Display* /* display */, 11143 KeyCode /* keycode */, 11144 int /* index */ 11145 ); 11146 11147 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 11148 11149 int XFree(void*); 11150 int XDeleteProperty(Display *display, Window w, Atom property); 11151 11152 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements); 11153 11154 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 11155 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 11156 *actual_type_return, int *actual_format_return, arch_ulong 11157 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 11158 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 11159 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 11160 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 11161 11162 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 11163 11164 Window XGetSelectionOwner(Display *display, Atom selection); 11165 11166 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 11167 11168 Display* XOpenDisplay(const char*); 11169 int XCloseDisplay(Display*); 11170 11171 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 11172 11173 Bool XSupportsLocale(); 11174 char* XSetLocaleModifiers(const(char)* modifier_list); 11175 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 11176 Status XCloseOM(XOM om); 11177 11178 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 11179 Status XCloseIM(XIM im); 11180 11181 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 11182 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 11183 Display* XDisplayOfIM(XIM im); 11184 char* XLocaleOfIM(XIM im); 11185 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 11186 void XDestroyIC(XIC ic); 11187 void XSetICFocus(XIC ic); 11188 void XUnsetICFocus(XIC ic); 11189 //wchar_t* XwcResetIC(XIC ic); 11190 char* XmbResetIC(XIC ic); 11191 char* Xutf8ResetIC(XIC ic); 11192 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 11193 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 11194 XIM XIMOfIC(XIC ic); 11195 11196 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 11197 11198 11199 XFontStruct *XLoadQueryFont(Display *display, in char *name); 11200 int XFreeFont(Display *display, XFontStruct *font_struct); 11201 int XSetFont(Display* display, GC gc, Font font); 11202 int XTextWidth(XFontStruct*, in char*, int); 11203 11204 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 11205 int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n); 11206 11207 Window XCreateSimpleWindow( 11208 Display* /* display */, 11209 Window /* parent */, 11210 int /* x */, 11211 int /* y */, 11212 uint /* width */, 11213 uint /* height */, 11214 uint /* border_width */, 11215 uint /* border */, 11216 uint /* background */ 11217 ); 11218 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); 11219 11220 int XReparentWindow(Display*, Window, Window, int, int); 11221 int XClearWindow(Display*, Window); 11222 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 11223 int XMoveWindow(Display*, Window, int, int); 11224 int XResizeWindow(Display *display, Window w, uint width, uint height); 11225 11226 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 11227 11228 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 11229 11230 XImage *XCreateImage( 11231 Display* /* display */, 11232 Visual* /* visual */, 11233 uint /* depth */, 11234 int /* format */, 11235 int /* offset */, 11236 ubyte* /* data */, 11237 uint /* width */, 11238 uint /* height */, 11239 int /* bitmap_pad */, 11240 int /* bytes_per_line */ 11241 ); 11242 11243 Status XInitImage (XImage* image); 11244 11245 Atom XInternAtom( 11246 Display* /* display */, 11247 const char* /* atom_name */, 11248 Bool /* only_if_exists */ 11249 ); 11250 11251 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 11252 char* XGetAtomName(Display*, Atom); 11253 Status XGetAtomNames(Display*, Atom*, int count, char**); 11254 11255 int XPutImage( 11256 Display* /* display */, 11257 Drawable /* d */, 11258 GC /* gc */, 11259 XImage* /* image */, 11260 int /* src_x */, 11261 int /* src_y */, 11262 int /* dest_x */, 11263 int /* dest_y */, 11264 uint /* width */, 11265 uint /* height */ 11266 ); 11267 11268 int XDestroyWindow( 11269 Display* /* display */, 11270 Window /* w */ 11271 ); 11272 11273 int XDestroyImage(XImage*); 11274 11275 int XSelectInput( 11276 Display* /* display */, 11277 Window /* w */, 11278 EventMask /* event_mask */ 11279 ); 11280 11281 int XMapWindow( 11282 Display* /* display */, 11283 Window /* w */ 11284 ); 11285 11286 Status XIconifyWindow(Display*, Window, int); 11287 int XMapRaised(Display*, Window); 11288 int XMapSubwindows(Display*, Window); 11289 11290 int XNextEvent( 11291 Display* /* display */, 11292 XEvent* /* event_return */ 11293 ); 11294 11295 int XMaskEvent(Display*, arch_long, XEvent*); 11296 11297 Bool XFilterEvent(XEvent *event, Window window); 11298 int XRefreshKeyboardMapping(XMappingEvent *event_map); 11299 11300 Status XSetWMProtocols( 11301 Display* /* display */, 11302 Window /* w */, 11303 Atom* /* protocols */, 11304 int /* count */ 11305 ); 11306 11307 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 11308 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 11309 11310 11311 Status XInitThreads(); 11312 void XLockDisplay (Display* display); 11313 void XUnlockDisplay (Display* display); 11314 11315 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 11316 11317 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 11318 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 11319 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 11320 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 11321 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 11322 11323 11324 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 11325 int XDrawString(Display*, Drawable, GC, int, int, in char*, int); 11326 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 11327 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 11328 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 11329 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 11330 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 11331 int XDrawPoint(Display*, Drawable, GC, int, int); 11332 int XSetForeground(Display*, GC, uint); 11333 int XSetBackground(Display*, GC, uint); 11334 11335 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 11336 void XFreeFontSet(Display*, XFontSet); 11337 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int); 11338 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 11339 11340 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 11341 int XSetFunction(Display*, GC, int); 11342 11343 GC XCreateGC(Display*, Drawable, uint, void*); 11344 int XCopyGC(Display*, GC, uint, GC); 11345 int XFreeGC(Display*, GC); 11346 11347 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 11348 bool XCheckMaskEvent(Display*, int, XEvent*); 11349 11350 int XPending(Display*); 11351 int XEventsQueued(Display* display, int mode); 11352 11353 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 11354 int XFreePixmap(Display*, Pixmap); 11355 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 11356 int XFlush(Display*); 11357 int XBell(Display*, int); 11358 int XSync(Display*, bool); 11359 11360 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 11361 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 11362 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 11363 11364 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 11365 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 11366 11367 Status XAllocColor(Display*, Colormap, XColor*); 11368 11369 int XWithdrawWindow(Display*, Window, int); 11370 int XUnmapWindow(Display*, Window); 11371 int XLowerWindow(Display*, Window); 11372 int XRaiseWindow(Display*, Window); 11373 11374 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); 11375 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); 11376 11377 int XGetInputFocus(Display*, Window*, int*); 11378 int XSetInputFocus(Display*, Window, int, Time); 11379 11380 XErrorHandler XSetErrorHandler(XErrorHandler); 11381 11382 int XGetErrorText(Display*, int, char*, int); 11383 11384 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 11385 11386 11387 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); 11388 int XUngrabPointer(Display *display, Time time); 11389 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 11390 11391 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 11392 11393 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 11394 int XSetClipMask(Display*, GC, Pixmap); 11395 int XSetClipOrigin(Display*, GC, int, int); 11396 11397 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 11398 11399 void XSetWMName(Display*, Window, XTextProperty*); 11400 Status XGetWMName(Display*, Window, XTextProperty*); 11401 int XStoreName(Display* display, Window w, const(char)* window_name); 11402 11403 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 11404 11405 } 11406 } 11407 11408 interface Xext { 11409 extern(C) nothrow @nogc { 11410 Status XShmAttach(Display*, XShmSegmentInfo*); 11411 Status XShmDetach(Display*, XShmSegmentInfo*); 11412 Status XShmPutImage( 11413 Display* /* dpy */, 11414 Drawable /* d */, 11415 GC /* gc */, 11416 XImage* /* image */, 11417 int /* src_x */, 11418 int /* src_y */, 11419 int /* dst_x */, 11420 int /* dst_y */, 11421 uint /* src_width */, 11422 uint /* src_height */, 11423 Bool /* send_event */ 11424 ); 11425 11426 Status XShmQueryExtension(Display*); 11427 11428 XImage *XShmCreateImage( 11429 Display* /* dpy */, 11430 Visual* /* visual */, 11431 uint /* depth */, 11432 int /* format */, 11433 char* /* data */, 11434 XShmSegmentInfo* /* shminfo */, 11435 uint /* width */, 11436 uint /* height */ 11437 ); 11438 11439 Pixmap XShmCreatePixmap( 11440 Display* /* dpy */, 11441 Drawable /* d */, 11442 char* /* data */, 11443 XShmSegmentInfo* /* shminfo */, 11444 uint /* width */, 11445 uint /* height */, 11446 uint /* depth */ 11447 ); 11448 11449 } 11450 } 11451 11452 // this requires -lXpm 11453 //int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 11454 11455 11456 mixin DynamicLoad!(XLib, "X11") xlib; 11457 mixin DynamicLoad!(Xext, "Xext") xext; 11458 shared static this() { 11459 xlib.loadDynamicLibrary(); 11460 xext.loadDynamicLibrary(); 11461 } 11462 11463 11464 extern(C) nothrow @nogc { 11465 11466 alias XrmDatabase = void*; 11467 struct XrmValue { 11468 uint size; 11469 void* addr; 11470 } 11471 11472 struct XVisualInfo { 11473 Visual* visual; 11474 VisualID visualid; 11475 int screen; 11476 uint depth; 11477 int c_class; 11478 c_ulong red_mask; 11479 c_ulong green_mask; 11480 c_ulong blue_mask; 11481 int colormap_size; 11482 int bits_per_rgb; 11483 } 11484 11485 enum VisualNoMask= 0x0; 11486 enum VisualIDMask= 0x1; 11487 enum VisualScreenMask=0x2; 11488 enum VisualDepthMask= 0x4; 11489 enum VisualClassMask= 0x8; 11490 enum VisualRedMaskMask=0x10; 11491 enum VisualGreenMaskMask=0x20; 11492 enum VisualBlueMaskMask=0x40; 11493 enum VisualColormapSizeMask=0x80; 11494 enum VisualBitsPerRGBMask=0x100; 11495 enum VisualAllMask= 0x1FF; 11496 11497 11498 // XIM and other crap 11499 struct _XOM {} 11500 struct _XIM {} 11501 struct _XIC {} 11502 alias XOM = _XOM*; 11503 alias XIM = _XIM*; 11504 alias XIC = _XIC*; 11505 11506 alias XIMStyle = arch_ulong; 11507 enum : arch_ulong { 11508 XIMPreeditArea = 0x0001, 11509 XIMPreeditCallbacks = 0x0002, 11510 XIMPreeditPosition = 0x0004, 11511 XIMPreeditNothing = 0x0008, 11512 XIMPreeditNone = 0x0010, 11513 XIMStatusArea = 0x0100, 11514 XIMStatusCallbacks = 0x0200, 11515 XIMStatusNothing = 0x0400, 11516 XIMStatusNone = 0x0800, 11517 } 11518 11519 11520 /* X Shared Memory Extension functions */ 11521 //pragma(lib, "Xshm"); 11522 alias arch_ulong ShmSeg; 11523 struct XShmSegmentInfo { 11524 ShmSeg shmseg; 11525 int shmid; 11526 ubyte* shmaddr; 11527 Bool readOnly; 11528 } 11529 11530 // and the necessary OS functions 11531 int shmget(int, size_t, int); 11532 void* shmat(int, in void*, int); 11533 int shmdt(in void*); 11534 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 11535 11536 enum IPC_PRIVATE = 0; 11537 enum IPC_CREAT = 512; 11538 enum IPC_RMID = 0; 11539 11540 /* MIT-SHM end */ 11541 11542 11543 enum MappingType:int { 11544 MappingModifier =0, 11545 MappingKeyboard =1, 11546 MappingPointer =2 11547 } 11548 11549 /* ImageFormat -- PutImage, GetImage */ 11550 enum ImageFormat:int { 11551 XYBitmap =0, /* depth 1, XYFormat */ 11552 XYPixmap =1, /* depth == drawable depth */ 11553 ZPixmap =2 /* depth == drawable depth */ 11554 } 11555 11556 enum ModifierName:int { 11557 ShiftMapIndex =0, 11558 LockMapIndex =1, 11559 ControlMapIndex =2, 11560 Mod1MapIndex =3, 11561 Mod2MapIndex =4, 11562 Mod3MapIndex =5, 11563 Mod4MapIndex =6, 11564 Mod5MapIndex =7 11565 } 11566 11567 enum ButtonMask:int { 11568 Button1Mask =1<<8, 11569 Button2Mask =1<<9, 11570 Button3Mask =1<<10, 11571 Button4Mask =1<<11, 11572 Button5Mask =1<<12, 11573 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 11574 } 11575 11576 enum KeyOrButtonMask:uint { 11577 ShiftMask =1<<0, 11578 LockMask =1<<1, 11579 ControlMask =1<<2, 11580 Mod1Mask =1<<3, 11581 Mod2Mask =1<<4, 11582 Mod3Mask =1<<5, 11583 Mod4Mask =1<<6, 11584 Mod5Mask =1<<7, 11585 Button1Mask =1<<8, 11586 Button2Mask =1<<9, 11587 Button3Mask =1<<10, 11588 Button4Mask =1<<11, 11589 Button5Mask =1<<12, 11590 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 11591 } 11592 11593 enum ButtonName:int { 11594 Button1 =1, 11595 Button2 =2, 11596 Button3 =3, 11597 Button4 =4, 11598 Button5 =5 11599 } 11600 11601 /* Notify modes */ 11602 enum NotifyModes:int 11603 { 11604 NotifyNormal =0, 11605 NotifyGrab =1, 11606 NotifyUngrab =2, 11607 NotifyWhileGrabbed =3 11608 } 11609 const int NotifyHint =1; /* for MotionNotify events */ 11610 11611 /* Notify detail */ 11612 enum NotifyDetail:int 11613 { 11614 NotifyAncestor =0, 11615 NotifyVirtual =1, 11616 NotifyInferior =2, 11617 NotifyNonlinear =3, 11618 NotifyNonlinearVirtual =4, 11619 NotifyPointer =5, 11620 NotifyPointerRoot =6, 11621 NotifyDetailNone =7 11622 } 11623 11624 /* Visibility notify */ 11625 11626 enum VisibilityNotify:int 11627 { 11628 VisibilityUnobscured =0, 11629 VisibilityPartiallyObscured =1, 11630 VisibilityFullyObscured =2 11631 } 11632 11633 11634 enum WindowStackingMethod:int 11635 { 11636 Above =0, 11637 Below =1, 11638 TopIf =2, 11639 BottomIf =3, 11640 Opposite =4 11641 } 11642 11643 /* Circulation request */ 11644 enum CirculationRequest:int 11645 { 11646 PlaceOnTop =0, 11647 PlaceOnBottom =1 11648 } 11649 11650 enum PropertyNotification:int 11651 { 11652 PropertyNewValue =0, 11653 PropertyDelete =1 11654 } 11655 11656 enum ColorMapNotification:int 11657 { 11658 ColormapUninstalled =0, 11659 ColormapInstalled =1 11660 } 11661 11662 11663 struct _XPrivate {} 11664 struct _XrmHashBucketRec {} 11665 11666 alias void* XPointer; 11667 alias void* XExtData; 11668 11669 version( X86_64 ) { 11670 alias ulong XID; 11671 alias ulong arch_ulong; 11672 alias long arch_long; 11673 } else { 11674 alias uint XID; 11675 alias uint arch_ulong; 11676 alias int arch_long; 11677 } 11678 11679 alias XID Window; 11680 alias XID Drawable; 11681 alias XID Pixmap; 11682 11683 alias arch_ulong Atom; 11684 alias int Bool; 11685 alias Display XDisplay; 11686 11687 alias int ByteOrder; 11688 alias arch_ulong Time; 11689 alias void ScreenFormat; 11690 11691 struct XImage { 11692 int width, height; /* size of image */ 11693 int xoffset; /* number of pixels offset in X direction */ 11694 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 11695 void *data; /* pointer to image data */ 11696 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 11697 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 11698 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 11699 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 11700 int depth; /* depth of image */ 11701 int bytes_per_line; /* accelarator to next line */ 11702 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 11703 arch_ulong red_mask; /* bits in z arrangment */ 11704 arch_ulong green_mask; 11705 arch_ulong blue_mask; 11706 XPointer obdata; /* hook for the object routines to hang on */ 11707 static struct F { /* image manipulation routines */ 11708 XImage* function( 11709 XDisplay* /* display */, 11710 Visual* /* visual */, 11711 uint /* depth */, 11712 int /* format */, 11713 int /* offset */, 11714 ubyte* /* data */, 11715 uint /* width */, 11716 uint /* height */, 11717 int /* bitmap_pad */, 11718 int /* bytes_per_line */) create_image; 11719 int function(XImage *) destroy_image; 11720 arch_ulong function(XImage *, int, int) get_pixel; 11721 int function(XImage *, int, int, arch_ulong) put_pixel; 11722 XImage* function(XImage *, int, int, uint, uint) sub_image; 11723 int function(XImage *, arch_long) add_pixel; 11724 } 11725 F f; 11726 } 11727 version(X86_64) static assert(XImage.sizeof == 136); 11728 else version(X86) static assert(XImage.sizeof == 88); 11729 11730 struct XCharStruct { 11731 short lbearing; /* origin to left edge of raster */ 11732 short rbearing; /* origin to right edge of raster */ 11733 short width; /* advance to next char's origin */ 11734 short ascent; /* baseline to top edge of raster */ 11735 short descent; /* baseline to bottom edge of raster */ 11736 ushort attributes; /* per char flags (not predefined) */ 11737 } 11738 11739 /* 11740 * To allow arbitrary information with fonts, there are additional properties 11741 * returned. 11742 */ 11743 struct XFontProp { 11744 Atom name; 11745 arch_ulong card32; 11746 } 11747 11748 alias Atom Font; 11749 11750 struct XFontStruct { 11751 XExtData *ext_data; /* Hook for extension to hang data */ 11752 Font fid; /* Font ID for this font */ 11753 uint direction; /* Direction the font is painted */ 11754 uint min_char_or_byte2; /* First character */ 11755 uint max_char_or_byte2; /* Last character */ 11756 uint min_byte1; /* First row that exists (for two-byte fonts) */ 11757 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 11758 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 11759 uint default_char; /* Char to print for undefined character */ 11760 int n_properties; /* How many properties there are */ 11761 XFontProp *properties; /* Pointer to array of additional properties*/ 11762 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 11763 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 11764 XCharStruct *per_char; /* first_char to last_char information */ 11765 int ascent; /* Max extent above baseline for spacing */ 11766 int descent; /* Max descent below baseline for spacing */ 11767 } 11768 11769 11770 /* 11771 * Definitions of specific events. 11772 */ 11773 struct XKeyEvent 11774 { 11775 int type; /* of event */ 11776 arch_ulong serial; /* # of last request processed by server */ 11777 Bool send_event; /* true if this came from a SendEvent request */ 11778 Display *display; /* Display the event was read from */ 11779 Window window; /* "event" window it is reported relative to */ 11780 Window root; /* root window that the event occurred on */ 11781 Window subwindow; /* child window */ 11782 Time time; /* milliseconds */ 11783 int x, y; /* pointer x, y coordinates in event window */ 11784 int x_root, y_root; /* coordinates relative to root */ 11785 KeyOrButtonMask state; /* key or button mask */ 11786 uint keycode; /* detail */ 11787 Bool same_screen; /* same screen flag */ 11788 } 11789 version(X86_64) static assert(XKeyEvent.sizeof == 96); 11790 alias XKeyEvent XKeyPressedEvent; 11791 alias XKeyEvent XKeyReleasedEvent; 11792 11793 struct XButtonEvent 11794 { 11795 int type; /* of event */ 11796 arch_ulong serial; /* # of last request processed by server */ 11797 Bool send_event; /* true if this came from a SendEvent request */ 11798 Display *display; /* Display the event was read from */ 11799 Window window; /* "event" window it is reported relative to */ 11800 Window root; /* root window that the event occurred on */ 11801 Window subwindow; /* child window */ 11802 Time time; /* milliseconds */ 11803 int x, y; /* pointer x, y coordinates in event window */ 11804 int x_root, y_root; /* coordinates relative to root */ 11805 KeyOrButtonMask state; /* key or button mask */ 11806 uint button; /* detail */ 11807 Bool same_screen; /* same screen flag */ 11808 } 11809 alias XButtonEvent XButtonPressedEvent; 11810 alias XButtonEvent XButtonReleasedEvent; 11811 11812 struct XMotionEvent{ 11813 int type; /* of event */ 11814 arch_ulong serial; /* # of last request processed by server */ 11815 Bool send_event; /* true if this came from a SendEvent request */ 11816 Display *display; /* Display the event was read from */ 11817 Window window; /* "event" window reported relative to */ 11818 Window root; /* root window that the event occurred on */ 11819 Window subwindow; /* child window */ 11820 Time time; /* milliseconds */ 11821 int x, y; /* pointer x, y coordinates in event window */ 11822 int x_root, y_root; /* coordinates relative to root */ 11823 KeyOrButtonMask state; /* key or button mask */ 11824 byte is_hint; /* detail */ 11825 Bool same_screen; /* same screen flag */ 11826 } 11827 alias XMotionEvent XPointerMovedEvent; 11828 11829 struct XCrossingEvent{ 11830 int type; /* of event */ 11831 arch_ulong serial; /* # of last request processed by server */ 11832 Bool send_event; /* true if this came from a SendEvent request */ 11833 Display *display; /* Display the event was read from */ 11834 Window window; /* "event" window reported relative to */ 11835 Window root; /* root window that the event occurred on */ 11836 Window subwindow; /* child window */ 11837 Time time; /* milliseconds */ 11838 int x, y; /* pointer x, y coordinates in event window */ 11839 int x_root, y_root; /* coordinates relative to root */ 11840 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 11841 NotifyDetail detail; 11842 /* 11843 * NotifyAncestor, NotifyVirtual, NotifyInferior, 11844 * NotifyNonlinear,NotifyNonlinearVirtual 11845 */ 11846 Bool same_screen; /* same screen flag */ 11847 Bool focus; /* Boolean focus */ 11848 KeyOrButtonMask state; /* key or button mask */ 11849 } 11850 alias XCrossingEvent XEnterWindowEvent; 11851 alias XCrossingEvent XLeaveWindowEvent; 11852 11853 struct XFocusChangeEvent{ 11854 int type; /* FocusIn or FocusOut */ 11855 arch_ulong serial; /* # of last request processed by server */ 11856 Bool send_event; /* true if this came from a SendEvent request */ 11857 Display *display; /* Display the event was read from */ 11858 Window window; /* window of event */ 11859 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 11860 NotifyGrab, NotifyUngrab */ 11861 NotifyDetail detail; 11862 /* 11863 * NotifyAncestor, NotifyVirtual, NotifyInferior, 11864 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 11865 * NotifyPointerRoot, NotifyDetailNone 11866 */ 11867 } 11868 alias XFocusChangeEvent XFocusInEvent; 11869 alias XFocusChangeEvent XFocusOutEvent; 11870 11871 enum CWBackPixmap = (1L<<0); 11872 enum CWBackPixel = (1L<<1); 11873 enum CWBorderPixmap = (1L<<2); 11874 enum CWBorderPixel = (1L<<3); 11875 enum CWBitGravity = (1L<<4); 11876 enum CWWinGravity = (1L<<5); 11877 enum CWBackingStore = (1L<<6); 11878 enum CWBackingPlanes = (1L<<7); 11879 enum CWBackingPixel = (1L<<8); 11880 enum CWOverrideRedirect = (1L<<9); 11881 enum CWSaveUnder = (1L<<10); 11882 enum CWEventMask = (1L<<11); 11883 enum CWDontPropagate = (1L<<12); 11884 enum CWColormap = (1L<<13); 11885 enum CWCursor = (1L<<14); 11886 11887 struct XWindowAttributes { 11888 int x, y; /* location of window */ 11889 int width, height; /* width and height of window */ 11890 int border_width; /* border width of window */ 11891 int depth; /* depth of window */ 11892 Visual *visual; /* the associated visual structure */ 11893 Window root; /* root of screen containing window */ 11894 int class_; /* InputOutput, InputOnly*/ 11895 int bit_gravity; /* one of the bit gravity values */ 11896 int win_gravity; /* one of the window gravity values */ 11897 int backing_store; /* NotUseful, WhenMapped, Always */ 11898 arch_ulong backing_planes; /* planes to be preserved if possible */ 11899 arch_ulong backing_pixel; /* value to be used when restoring planes */ 11900 Bool save_under; /* boolean, should bits under be saved? */ 11901 Colormap colormap; /* color map to be associated with window */ 11902 Bool map_installed; /* boolean, is color map currently installed*/ 11903 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 11904 arch_long all_event_masks; /* set of events all people have interest in*/ 11905 arch_long your_event_mask; /* my event mask */ 11906 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 11907 Bool override_redirect; /* boolean value for override-redirect */ 11908 Screen *screen; /* back pointer to correct screen */ 11909 } 11910 11911 enum IsUnmapped = 0; 11912 enum IsUnviewable = 1; 11913 enum IsViewable = 2; 11914 11915 struct XSetWindowAttributes { 11916 Pixmap background_pixmap;/* background, None, or ParentRelative */ 11917 arch_ulong background_pixel;/* background pixel */ 11918 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 11919 arch_ulong border_pixel;/* border pixel value */ 11920 int bit_gravity; /* one of bit gravity values */ 11921 int win_gravity; /* one of the window gravity values */ 11922 int backing_store; /* NotUseful, WhenMapped, Always */ 11923 arch_ulong backing_planes;/* planes to be preserved if possible */ 11924 arch_ulong backing_pixel;/* value to use in restoring planes */ 11925 Bool save_under; /* should bits under be saved? (popups) */ 11926 arch_long event_mask; /* set of events that should be saved */ 11927 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 11928 Bool override_redirect; /* boolean value for override_redirect */ 11929 Colormap colormap; /* color map to be associated with window */ 11930 Cursor cursor; /* cursor to be displayed (or None) */ 11931 } 11932 11933 11934 alias int Status; 11935 11936 11937 enum EventMask:int 11938 { 11939 NoEventMask =0, 11940 KeyPressMask =1<<0, 11941 KeyReleaseMask =1<<1, 11942 ButtonPressMask =1<<2, 11943 ButtonReleaseMask =1<<3, 11944 EnterWindowMask =1<<4, 11945 LeaveWindowMask =1<<5, 11946 PointerMotionMask =1<<6, 11947 PointerMotionHintMask =1<<7, 11948 Button1MotionMask =1<<8, 11949 Button2MotionMask =1<<9, 11950 Button3MotionMask =1<<10, 11951 Button4MotionMask =1<<11, 11952 Button5MotionMask =1<<12, 11953 ButtonMotionMask =1<<13, 11954 KeymapStateMask =1<<14, 11955 ExposureMask =1<<15, 11956 VisibilityChangeMask =1<<16, 11957 StructureNotifyMask =1<<17, 11958 ResizeRedirectMask =1<<18, 11959 SubstructureNotifyMask =1<<19, 11960 SubstructureRedirectMask=1<<20, 11961 FocusChangeMask =1<<21, 11962 PropertyChangeMask =1<<22, 11963 ColormapChangeMask =1<<23, 11964 OwnerGrabButtonMask =1<<24 11965 } 11966 11967 struct MwmHints { 11968 int flags; 11969 int functions; 11970 int decorations; 11971 int input_mode; 11972 int status; 11973 } 11974 11975 enum { 11976 MWM_HINTS_FUNCTIONS = (1L << 0), 11977 MWM_HINTS_DECORATIONS = (1L << 1), 11978 11979 MWM_FUNC_ALL = (1L << 0), 11980 MWM_FUNC_RESIZE = (1L << 1), 11981 MWM_FUNC_MOVE = (1L << 2), 11982 MWM_FUNC_MINIMIZE = (1L << 3), 11983 MWM_FUNC_MAXIMIZE = (1L << 4), 11984 MWM_FUNC_CLOSE = (1L << 5) 11985 } 11986 11987 import core.stdc.config : c_long, c_ulong; 11988 11989 /* Size hints mask bits */ 11990 11991 enum USPosition = (1L << 0) /* user specified x, y */; 11992 enum USSize = (1L << 1) /* user specified width, height */; 11993 enum PPosition = (1L << 2) /* program specified position */; 11994 enum PSize = (1L << 3) /* program specified size */; 11995 enum PMinSize = (1L << 4) /* program specified minimum size */; 11996 enum PMaxSize = (1L << 5) /* program specified maximum size */; 11997 enum PResizeInc = (1L << 6) /* program specified resize increments */; 11998 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 11999 enum PBaseSize = (1L << 8); 12000 enum PWinGravity = (1L << 9); 12001 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 12002 struct XSizeHints { 12003 arch_long flags; /* marks which fields in this structure are defined */ 12004 int x, y; /* Obsolete */ 12005 int width, height; /* Obsolete */ 12006 int min_width, min_height; 12007 int max_width, max_height; 12008 int width_inc, height_inc; 12009 struct Aspect { 12010 int x; /* numerator */ 12011 int y; /* denominator */ 12012 } 12013 12014 Aspect min_aspect; 12015 Aspect max_aspect; 12016 int base_width, base_height; 12017 int win_gravity; 12018 /* this structure may be extended in the future */ 12019 } 12020 12021 12022 12023 enum EventType:int 12024 { 12025 KeyPress =2, 12026 KeyRelease =3, 12027 ButtonPress =4, 12028 ButtonRelease =5, 12029 MotionNotify =6, 12030 EnterNotify =7, 12031 LeaveNotify =8, 12032 FocusIn =9, 12033 FocusOut =10, 12034 KeymapNotify =11, 12035 Expose =12, 12036 GraphicsExpose =13, 12037 NoExpose =14, 12038 VisibilityNotify =15, 12039 CreateNotify =16, 12040 DestroyNotify =17, 12041 UnmapNotify =18, 12042 MapNotify =19, 12043 MapRequest =20, 12044 ReparentNotify =21, 12045 ConfigureNotify =22, 12046 ConfigureRequest =23, 12047 GravityNotify =24, 12048 ResizeRequest =25, 12049 CirculateNotify =26, 12050 CirculateRequest =27, 12051 PropertyNotify =28, 12052 SelectionClear =29, 12053 SelectionRequest =30, 12054 SelectionNotify =31, 12055 ColormapNotify =32, 12056 ClientMessage =33, 12057 MappingNotify =34, 12058 LASTEvent =35 /* must be bigger than any event # */ 12059 } 12060 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 12061 struct XKeymapEvent 12062 { 12063 int type; 12064 arch_ulong serial; /* # of last request processed by server */ 12065 Bool send_event; /* true if this came from a SendEvent request */ 12066 Display *display; /* Display the event was read from */ 12067 Window window; 12068 byte[32] key_vector; 12069 } 12070 12071 struct XExposeEvent 12072 { 12073 int type; 12074 arch_ulong serial; /* # of last request processed by server */ 12075 Bool send_event; /* true if this came from a SendEvent request */ 12076 Display *display; /* Display the event was read from */ 12077 Window window; 12078 int x, y; 12079 int width, height; 12080 int count; /* if non-zero, at least this many more */ 12081 } 12082 12083 struct XGraphicsExposeEvent{ 12084 int type; 12085 arch_ulong serial; /* # of last request processed by server */ 12086 Bool send_event; /* true if this came from a SendEvent request */ 12087 Display *display; /* Display the event was read from */ 12088 Drawable drawable; 12089 int x, y; 12090 int width, height; 12091 int count; /* if non-zero, at least this many more */ 12092 int major_code; /* core is CopyArea or CopyPlane */ 12093 int minor_code; /* not defined in the core */ 12094 } 12095 12096 struct XNoExposeEvent{ 12097 int type; 12098 arch_ulong serial; /* # of last request processed by server */ 12099 Bool send_event; /* true if this came from a SendEvent request */ 12100 Display *display; /* Display the event was read from */ 12101 Drawable drawable; 12102 int major_code; /* core is CopyArea or CopyPlane */ 12103 int minor_code; /* not defined in the core */ 12104 } 12105 12106 struct XVisibilityEvent{ 12107 int type; 12108 arch_ulong serial; /* # of last request processed by server */ 12109 Bool send_event; /* true if this came from a SendEvent request */ 12110 Display *display; /* Display the event was read from */ 12111 Window window; 12112 VisibilityNotify state; /* Visibility state */ 12113 } 12114 12115 struct XCreateWindowEvent{ 12116 int type; 12117 arch_ulong serial; /* # of last request processed by server */ 12118 Bool send_event; /* true if this came from a SendEvent request */ 12119 Display *display; /* Display the event was read from */ 12120 Window parent; /* parent of the window */ 12121 Window window; /* window id of window created */ 12122 int x, y; /* window location */ 12123 int width, height; /* size of window */ 12124 int border_width; /* border width */ 12125 Bool override_redirect; /* creation should be overridden */ 12126 } 12127 12128 struct XDestroyWindowEvent 12129 { 12130 int type; 12131 arch_ulong serial; /* # of last request processed by server */ 12132 Bool send_event; /* true if this came from a SendEvent request */ 12133 Display *display; /* Display the event was read from */ 12134 Window event; 12135 Window window; 12136 } 12137 12138 struct XUnmapEvent 12139 { 12140 int type; 12141 arch_ulong serial; /* # of last request processed by server */ 12142 Bool send_event; /* true if this came from a SendEvent request */ 12143 Display *display; /* Display the event was read from */ 12144 Window event; 12145 Window window; 12146 Bool from_configure; 12147 } 12148 12149 struct XMapEvent 12150 { 12151 int type; 12152 arch_ulong serial; /* # of last request processed by server */ 12153 Bool send_event; /* true if this came from a SendEvent request */ 12154 Display *display; /* Display the event was read from */ 12155 Window event; 12156 Window window; 12157 Bool override_redirect; /* Boolean, is override set... */ 12158 } 12159 12160 struct XMapRequestEvent 12161 { 12162 int type; 12163 arch_ulong serial; /* # of last request processed by server */ 12164 Bool send_event; /* true if this came from a SendEvent request */ 12165 Display *display; /* Display the event was read from */ 12166 Window parent; 12167 Window window; 12168 } 12169 12170 struct XReparentEvent 12171 { 12172 int type; 12173 arch_ulong serial; /* # of last request processed by server */ 12174 Bool send_event; /* true if this came from a SendEvent request */ 12175 Display *display; /* Display the event was read from */ 12176 Window event; 12177 Window window; 12178 Window parent; 12179 int x, y; 12180 Bool override_redirect; 12181 } 12182 12183 struct XConfigureEvent 12184 { 12185 int type; 12186 arch_ulong serial; /* # of last request processed by server */ 12187 Bool send_event; /* true if this came from a SendEvent request */ 12188 Display *display; /* Display the event was read from */ 12189 Window event; 12190 Window window; 12191 int x, y; 12192 int width, height; 12193 int border_width; 12194 Window above; 12195 Bool override_redirect; 12196 } 12197 12198 struct XGravityEvent 12199 { 12200 int type; 12201 arch_ulong serial; /* # of last request processed by server */ 12202 Bool send_event; /* true if this came from a SendEvent request */ 12203 Display *display; /* Display the event was read from */ 12204 Window event; 12205 Window window; 12206 int x, y; 12207 } 12208 12209 struct XResizeRequestEvent 12210 { 12211 int type; 12212 arch_ulong serial; /* # of last request processed by server */ 12213 Bool send_event; /* true if this came from a SendEvent request */ 12214 Display *display; /* Display the event was read from */ 12215 Window window; 12216 int width, height; 12217 } 12218 12219 struct XConfigureRequestEvent 12220 { 12221 int type; 12222 arch_ulong serial; /* # of last request processed by server */ 12223 Bool send_event; /* true if this came from a SendEvent request */ 12224 Display *display; /* Display the event was read from */ 12225 Window parent; 12226 Window window; 12227 int x, y; 12228 int width, height; 12229 int border_width; 12230 Window above; 12231 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 12232 arch_ulong value_mask; 12233 } 12234 12235 struct XCirculateEvent 12236 { 12237 int type; 12238 arch_ulong serial; /* # of last request processed by server */ 12239 Bool send_event; /* true if this came from a SendEvent request */ 12240 Display *display; /* Display the event was read from */ 12241 Window event; 12242 Window window; 12243 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 12244 } 12245 12246 struct XCirculateRequestEvent 12247 { 12248 int type; 12249 arch_ulong serial; /* # of last request processed by server */ 12250 Bool send_event; /* true if this came from a SendEvent request */ 12251 Display *display; /* Display the event was read from */ 12252 Window parent; 12253 Window window; 12254 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 12255 } 12256 12257 struct XPropertyEvent 12258 { 12259 int type; 12260 arch_ulong serial; /* # of last request processed by server */ 12261 Bool send_event; /* true if this came from a SendEvent request */ 12262 Display *display; /* Display the event was read from */ 12263 Window window; 12264 Atom atom; 12265 Time time; 12266 PropertyNotification state; /* NewValue, Deleted */ 12267 } 12268 12269 struct XSelectionClearEvent 12270 { 12271 int type; 12272 arch_ulong serial; /* # of last request processed by server */ 12273 Bool send_event; /* true if this came from a SendEvent request */ 12274 Display *display; /* Display the event was read from */ 12275 Window window; 12276 Atom selection; 12277 Time time; 12278 } 12279 12280 struct XSelectionRequestEvent 12281 { 12282 int type; 12283 arch_ulong serial; /* # of last request processed by server */ 12284 Bool send_event; /* true if this came from a SendEvent request */ 12285 Display *display; /* Display the event was read from */ 12286 Window owner; 12287 Window requestor; 12288 Atom selection; 12289 Atom target; 12290 Atom property; 12291 Time time; 12292 } 12293 12294 struct XSelectionEvent 12295 { 12296 int type; 12297 arch_ulong serial; /* # of last request processed by server */ 12298 Bool send_event; /* true if this came from a SendEvent request */ 12299 Display *display; /* Display the event was read from */ 12300 Window requestor; 12301 Atom selection; 12302 Atom target; 12303 Atom property; /* ATOM or None */ 12304 Time time; 12305 } 12306 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 12307 12308 struct XColormapEvent 12309 { 12310 int type; 12311 arch_ulong serial; /* # of last request processed by server */ 12312 Bool send_event; /* true if this came from a SendEvent request */ 12313 Display *display; /* Display the event was read from */ 12314 Window window; 12315 Colormap colormap; /* COLORMAP or None */ 12316 Bool new_; /* C++ */ 12317 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 12318 } 12319 version(X86_64) static assert(XColormapEvent.sizeof == 56); 12320 12321 struct XClientMessageEvent 12322 { 12323 int type; 12324 arch_ulong serial; /* # of last request processed by server */ 12325 Bool send_event; /* true if this came from a SendEvent request */ 12326 Display *display; /* Display the event was read from */ 12327 Window window; 12328 Atom message_type; 12329 int format; 12330 union Data{ 12331 byte[20] b; 12332 short[10] s; 12333 arch_ulong[5] l; 12334 } 12335 Data data; 12336 12337 } 12338 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 12339 12340 struct XMappingEvent 12341 { 12342 int type; 12343 arch_ulong serial; /* # of last request processed by server */ 12344 Bool send_event; /* true if this came from a SendEvent request */ 12345 Display *display; /* Display the event was read from */ 12346 Window window; /* unused */ 12347 MappingType request; /* one of MappingModifier, MappingKeyboard, 12348 MappingPointer */ 12349 int first_keycode; /* first keycode */ 12350 int count; /* defines range of change w. first_keycode*/ 12351 } 12352 12353 struct XErrorEvent 12354 { 12355 int type; 12356 Display *display; /* Display the event was read from */ 12357 XID resourceid; /* resource id */ 12358 arch_ulong serial; /* serial number of failed request */ 12359 ubyte error_code; /* error code of failed request */ 12360 ubyte request_code; /* Major op-code of failed request */ 12361 ubyte minor_code; /* Minor op-code of failed request */ 12362 } 12363 12364 struct XAnyEvent 12365 { 12366 int type; 12367 arch_ulong serial; /* # of last request processed by server */ 12368 Bool send_event; /* true if this came from a SendEvent request */ 12369 Display *display;/* Display the event was read from */ 12370 Window window; /* window on which event was requested in event mask */ 12371 } 12372 12373 union XEvent{ 12374 int type; /* must not be changed; first element */ 12375 XAnyEvent xany; 12376 XKeyEvent xkey; 12377 XButtonEvent xbutton; 12378 XMotionEvent xmotion; 12379 XCrossingEvent xcrossing; 12380 XFocusChangeEvent xfocus; 12381 XExposeEvent xexpose; 12382 XGraphicsExposeEvent xgraphicsexpose; 12383 XNoExposeEvent xnoexpose; 12384 XVisibilityEvent xvisibility; 12385 XCreateWindowEvent xcreatewindow; 12386 XDestroyWindowEvent xdestroywindow; 12387 XUnmapEvent xunmap; 12388 XMapEvent xmap; 12389 XMapRequestEvent xmaprequest; 12390 XReparentEvent xreparent; 12391 XConfigureEvent xconfigure; 12392 XGravityEvent xgravity; 12393 XResizeRequestEvent xresizerequest; 12394 XConfigureRequestEvent xconfigurerequest; 12395 XCirculateEvent xcirculate; 12396 XCirculateRequestEvent xcirculaterequest; 12397 XPropertyEvent xproperty; 12398 XSelectionClearEvent xselectionclear; 12399 XSelectionRequestEvent xselectionrequest; 12400 XSelectionEvent xselection; 12401 XColormapEvent xcolormap; 12402 XClientMessageEvent xclient; 12403 XMappingEvent xmapping; 12404 XErrorEvent xerror; 12405 XKeymapEvent xkeymap; 12406 arch_ulong[24] pad; 12407 } 12408 12409 12410 struct Display { 12411 XExtData *ext_data; /* hook for extension to hang data */ 12412 _XPrivate *private1; 12413 int fd; /* Network socket. */ 12414 int private2; 12415 int proto_major_version;/* major version of server's X protocol */ 12416 int proto_minor_version;/* minor version of servers X protocol */ 12417 char *vendor; /* vendor of the server hardware */ 12418 XID private3; 12419 XID private4; 12420 XID private5; 12421 int private6; 12422 XID function(Display*)resource_alloc;/* allocator function */ 12423 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 12424 int bitmap_unit; /* padding and data requirements */ 12425 int bitmap_pad; /* padding requirements on bitmaps */ 12426 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 12427 int nformats; /* number of pixmap formats in list */ 12428 ScreenFormat *pixmap_format; /* pixmap format list */ 12429 int private8; 12430 int release; /* release of the server */ 12431 _XPrivate *private9; 12432 _XPrivate *private10; 12433 int qlen; /* Length of input event queue */ 12434 arch_ulong last_request_read; /* seq number of last event read */ 12435 arch_ulong request; /* sequence number of last request. */ 12436 XPointer private11; 12437 XPointer private12; 12438 XPointer private13; 12439 XPointer private14; 12440 uint max_request_size; /* maximum number 32 bit words in request*/ 12441 _XrmHashBucketRec *db; 12442 int function (Display*)private15; 12443 char *display_name; /* "host:display" string used on this connect*/ 12444 int default_screen; /* default screen for operations */ 12445 int nscreens; /* number of screens on this server*/ 12446 Screen *screens; /* pointer to list of screens */ 12447 arch_ulong motion_buffer; /* size of motion buffer */ 12448 arch_ulong private16; 12449 int min_keycode; /* minimum defined keycode */ 12450 int max_keycode; /* maximum defined keycode */ 12451 XPointer private17; 12452 XPointer private18; 12453 int private19; 12454 byte *xdefaults; /* contents of defaults from server */ 12455 /* there is more to this structure, but it is private to Xlib */ 12456 } 12457 12458 // I got these numbers from a C program as a sanity test 12459 version(X86_64) { 12460 static assert(Display.sizeof == 296); 12461 static assert(XPointer.sizeof == 8); 12462 static assert(XErrorEvent.sizeof == 40); 12463 static assert(XAnyEvent.sizeof == 40); 12464 static assert(XMappingEvent.sizeof == 56); 12465 static assert(XEvent.sizeof == 192); 12466 } else { 12467 static assert(Display.sizeof == 176); 12468 static assert(XPointer.sizeof == 4); 12469 static assert(XEvent.sizeof == 96); 12470 } 12471 12472 struct Depth 12473 { 12474 int depth; /* this depth (Z) of the depth */ 12475 int nvisuals; /* number of Visual types at this depth */ 12476 Visual *visuals; /* list of visuals possible at this depth */ 12477 } 12478 12479 alias void* GC; 12480 alias c_ulong VisualID; 12481 alias XID Colormap; 12482 alias XID Cursor; 12483 alias XID KeySym; 12484 alias uint KeyCode; 12485 enum None = 0; 12486 } 12487 12488 version(without_opengl) {} 12489 else { 12490 extern(C) nothrow @nogc { 12491 12492 12493 static if(!SdpyIsUsingIVGLBinds) { 12494 enum GLX_USE_GL= 1; /* support GLX rendering */ 12495 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 12496 enum GLX_LEVEL= 3; /* level in plane stacking */ 12497 enum GLX_RGBA= 4; /* true if RGBA mode */ 12498 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 12499 enum GLX_STEREO= 6; /* stereo buffering supported */ 12500 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 12501 enum GLX_RED_SIZE= 8; /* number of red component bits */ 12502 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 12503 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 12504 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 12505 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 12506 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 12507 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 12508 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 12509 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 12510 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 12511 12512 12513 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 12514 12515 12516 12517 enum GL_TRUE = 1; 12518 enum GL_FALSE = 0; 12519 alias int GLint; 12520 } 12521 12522 alias XID GLXContextID; 12523 alias XID GLXPixmap; 12524 alias XID GLXDrawable; 12525 alias XID GLXPbuffer; 12526 alias XID GLXWindow; 12527 alias XID GLXFBConfigID; 12528 alias void* GLXContext; 12529 12530 } 12531 } 12532 12533 enum AllocNone = 0; 12534 12535 extern(C) { 12536 /* WARNING, this type not in Xlib spec */ 12537 extern(C) alias XIOErrorHandler = int function (Display* display); 12538 } 12539 12540 extern(C) nothrow @nogc { 12541 struct Screen{ 12542 XExtData *ext_data; /* hook for extension to hang data */ 12543 Display *display; /* back pointer to display structure */ 12544 Window root; /* Root window id. */ 12545 int width, height; /* width and height of screen */ 12546 int mwidth, mheight; /* width and height of in millimeters */ 12547 int ndepths; /* number of depths possible */ 12548 Depth *depths; /* list of allowable depths on the screen */ 12549 int root_depth; /* bits per pixel */ 12550 Visual *root_visual; /* root visual */ 12551 GC default_gc; /* GC for the root root visual */ 12552 Colormap cmap; /* default color map */ 12553 uint white_pixel; 12554 uint black_pixel; /* White and Black pixel values */ 12555 int max_maps, min_maps; /* max and min color maps */ 12556 int backing_store; /* Never, WhenMapped, Always */ 12557 bool save_unders; 12558 int root_input_mask; /* initial root input mask */ 12559 } 12560 12561 struct Visual 12562 { 12563 XExtData *ext_data; /* hook for extension to hang data */ 12564 VisualID visualid; /* visual id of this visual */ 12565 int class_; /* class of screen (monochrome, etc.) */ 12566 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 12567 int bits_per_rgb; /* log base 2 of distinct color values */ 12568 int map_entries; /* color map entries */ 12569 } 12570 12571 alias Display* _XPrivDisplay; 12572 12573 Screen* ScreenOfDisplay(Display* dpy, int scr) { 12574 assert(dpy !is null); 12575 return &dpy.screens[scr]; 12576 } 12577 12578 Window RootWindow(Display *dpy,int scr) { 12579 return ScreenOfDisplay(dpy,scr).root; 12580 } 12581 12582 struct XWMHints { 12583 arch_long flags; 12584 Bool input; 12585 int initial_state; 12586 Pixmap icon_pixmap; 12587 Window icon_window; 12588 int icon_x, icon_y; 12589 Pixmap icon_mask; 12590 XID window_group; 12591 } 12592 12593 struct XClassHint { 12594 char* res_name; 12595 char* res_class; 12596 } 12597 12598 int DefaultScreen(Display *dpy) { 12599 return dpy.default_screen; 12600 } 12601 12602 int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 12603 int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 12604 int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 12605 int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 12606 int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 12607 auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 12608 12609 int ConnectionNumber(Display* dpy) { return dpy.fd; } 12610 12611 enum int AnyPropertyType = 0; 12612 enum int Success = 0; 12613 12614 enum int RevertToNone = None; 12615 enum int PointerRoot = 1; 12616 enum Time CurrentTime = 0; 12617 enum int RevertToPointerRoot = PointerRoot; 12618 enum int RevertToParent = 2; 12619 12620 int DefaultDepthOfDisplay(Display* dpy) { 12621 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 12622 } 12623 12624 Visual* DefaultVisual(Display *dpy,int scr) { 12625 return ScreenOfDisplay(dpy,scr).root_visual; 12626 } 12627 12628 GC DefaultGC(Display *dpy,int scr) { 12629 return ScreenOfDisplay(dpy,scr).default_gc; 12630 } 12631 12632 uint BlackPixel(Display *dpy,int scr) { 12633 return ScreenOfDisplay(dpy,scr).black_pixel; 12634 } 12635 12636 uint WhitePixel(Display *dpy,int scr) { 12637 return ScreenOfDisplay(dpy,scr).white_pixel; 12638 } 12639 12640 alias void* XFontSet; // i think 12641 struct XmbTextItem { 12642 char* chars; 12643 int nchars; 12644 int delta; 12645 XFontSet font_set; 12646 } 12647 12648 struct XTextItem { 12649 char* chars; 12650 int nchars; 12651 int delta; 12652 Font font; 12653 } 12654 12655 enum { 12656 GXclear = 0x0, /* 0 */ 12657 GXand = 0x1, /* src AND dst */ 12658 GXandReverse = 0x2, /* src AND NOT dst */ 12659 GXcopy = 0x3, /* src */ 12660 GXandInverted = 0x4, /* NOT src AND dst */ 12661 GXnoop = 0x5, /* dst */ 12662 GXxor = 0x6, /* src XOR dst */ 12663 GXor = 0x7, /* src OR dst */ 12664 GXnor = 0x8, /* NOT src AND NOT dst */ 12665 GXequiv = 0x9, /* NOT src XOR dst */ 12666 GXinvert = 0xa, /* NOT dst */ 12667 GXorReverse = 0xb, /* src OR NOT dst */ 12668 GXcopyInverted = 0xc, /* NOT src */ 12669 GXorInverted = 0xd, /* NOT src OR dst */ 12670 GXnand = 0xe, /* NOT src OR NOT dst */ 12671 GXset = 0xf, /* 1 */ 12672 } 12673 enum QueueMode : int { 12674 QueuedAlready, 12675 QueuedAfterReading, 12676 QueuedAfterFlush 12677 } 12678 12679 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 12680 12681 struct XPoint { 12682 short x; 12683 short y; 12684 } 12685 12686 enum CoordMode:int { 12687 CoordModeOrigin = 0, 12688 CoordModePrevious = 1 12689 } 12690 12691 enum PolygonShape:int { 12692 Complex = 0, 12693 Nonconvex = 1, 12694 Convex = 2 12695 } 12696 12697 struct XTextProperty { 12698 const(char)* value; /* same as Property routines */ 12699 Atom encoding; /* prop type */ 12700 int format; /* prop data format: 8, 16, or 32 */ 12701 arch_ulong nitems; /* number of data items in value */ 12702 } 12703 12704 version( X86_64 ) { 12705 static assert(XTextProperty.sizeof == 32); 12706 } 12707 12708 12709 struct XGCValues { 12710 int function_; /* logical operation */ 12711 arch_ulong plane_mask;/* plane mask */ 12712 arch_ulong foreground;/* foreground pixel */ 12713 arch_ulong background;/* background pixel */ 12714 int line_width; /* line width */ 12715 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 12716 int cap_style; /* CapNotLast, CapButt, 12717 CapRound, CapProjecting */ 12718 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 12719 int fill_style; /* FillSolid, FillTiled, 12720 FillStippled, FillOpaeueStippled */ 12721 int fill_rule; /* EvenOddRule, WindingRule */ 12722 int arc_mode; /* ArcChord, ArcPieSlice */ 12723 Pixmap tile; /* tile pixmap for tiling operations */ 12724 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 12725 int ts_x_origin; /* offset for tile or stipple operations */ 12726 int ts_y_origin; 12727 Font font; /* default text font for text operations */ 12728 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 12729 Bool graphics_exposures;/* boolean, should exposures be generated */ 12730 int clip_x_origin; /* origin for clipping */ 12731 int clip_y_origin; 12732 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 12733 int dash_offset; /* patterned/dashed line information */ 12734 char dashes; 12735 } 12736 12737 struct XColor { 12738 arch_ulong pixel; 12739 ushort red, green, blue; 12740 byte flags; 12741 byte pad; 12742 } 12743 12744 alias XErrorHandler = int function(Display*, XErrorEvent*); 12745 12746 struct XRectangle { 12747 short x; 12748 short y; 12749 ushort width; 12750 ushort height; 12751 } 12752 12753 enum ClipByChildren = 0; 12754 enum IncludeInferiors = 1; 12755 12756 enum Atom XA_PRIMARY = 1; 12757 enum Atom XA_SECONDARY = 2; 12758 enum Atom XA_STRING = 31; 12759 enum Atom XA_CARDINAL = 6; 12760 enum Atom XA_WM_NAME = 39; 12761 enum Atom XA_ATOM = 4; 12762 enum Atom XA_WINDOW = 33; 12763 enum Atom XA_WM_HINTS = 35; 12764 enum int PropModeAppend = 2; 12765 enum int PropModeReplace = 0; 12766 enum int PropModePrepend = 1; 12767 12768 enum int CopyFromParent = 0; 12769 enum int InputOutput = 1; 12770 12771 // XWMHints 12772 enum InputHint = 1 << 0; 12773 enum StateHint = 1 << 1; 12774 enum IconPixmapHint = (1L << 2); 12775 enum IconWindowHint = (1L << 3); 12776 enum IconPositionHint = (1L << 4); 12777 enum IconMaskHint = (1L << 5); 12778 enum WindowGroupHint = (1L << 6); 12779 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 12780 enum XUrgencyHint = (1L << 8); 12781 12782 // GC Components 12783 enum GCFunction = (1L<<0); 12784 enum GCPlaneMask = (1L<<1); 12785 enum GCForeground = (1L<<2); 12786 enum GCBackground = (1L<<3); 12787 enum GCLineWidth = (1L<<4); 12788 enum GCLineStyle = (1L<<5); 12789 enum GCCapStyle = (1L<<6); 12790 enum GCJoinStyle = (1L<<7); 12791 enum GCFillStyle = (1L<<8); 12792 enum GCFillRule = (1L<<9); 12793 enum GCTile = (1L<<10); 12794 enum GCStipple = (1L<<11); 12795 enum GCTileStipXOrigin = (1L<<12); 12796 enum GCTileStipYOrigin = (1L<<13); 12797 enum GCFont = (1L<<14); 12798 enum GCSubwindowMode = (1L<<15); 12799 enum GCGraphicsExposures= (1L<<16); 12800 enum GCClipXOrigin = (1L<<17); 12801 enum GCClipYOrigin = (1L<<18); 12802 enum GCClipMask = (1L<<19); 12803 enum GCDashOffset = (1L<<20); 12804 enum GCDashList = (1L<<21); 12805 enum GCArcMode = (1L<<22); 12806 enum GCLastBit = 22; 12807 12808 12809 enum int WithdrawnState = 0; 12810 enum int NormalState = 1; 12811 enum int IconicState = 3; 12812 12813 } 12814 } else version (OSXCocoa) { 12815 private: 12816 alias void* id; 12817 alias void* Class; 12818 alias void* SEL; 12819 alias void* IMP; 12820 alias void* Ivar; 12821 alias byte BOOL; 12822 alias const(void)* CFStringRef; 12823 alias const(void)* CFAllocatorRef; 12824 alias const(void)* CFTypeRef; 12825 alias const(void)* CGContextRef; 12826 alias const(void)* CGColorSpaceRef; 12827 alias const(void)* CGImageRef; 12828 alias ulong CGBitmapInfo; 12829 12830 struct objc_super { 12831 id self; 12832 Class superclass; 12833 } 12834 12835 struct CFRange { 12836 long location, length; 12837 } 12838 12839 struct NSPoint { 12840 double x, y; 12841 12842 static fromTuple(T)(T tupl) { 12843 return NSPoint(tupl.tupleof); 12844 } 12845 } 12846 struct NSSize { 12847 double width, height; 12848 } 12849 struct NSRect { 12850 NSPoint origin; 12851 NSSize size; 12852 } 12853 alias NSPoint CGPoint; 12854 alias NSSize CGSize; 12855 alias NSRect CGRect; 12856 12857 struct CGAffineTransform { 12858 double a, b, c, d, tx, ty; 12859 } 12860 12861 enum NSApplicationActivationPolicyRegular = 0; 12862 enum NSBackingStoreBuffered = 2; 12863 enum kCFStringEncodingUTF8 = 0x08000100; 12864 12865 enum : size_t { 12866 NSBorderlessWindowMask = 0, 12867 NSTitledWindowMask = 1 << 0, 12868 NSClosableWindowMask = 1 << 1, 12869 NSMiniaturizableWindowMask = 1 << 2, 12870 NSResizableWindowMask = 1 << 3, 12871 NSTexturedBackgroundWindowMask = 1 << 8 12872 } 12873 12874 enum : ulong { 12875 kCGImageAlphaNone, 12876 kCGImageAlphaPremultipliedLast, 12877 kCGImageAlphaPremultipliedFirst, 12878 kCGImageAlphaLast, 12879 kCGImageAlphaFirst, 12880 kCGImageAlphaNoneSkipLast, 12881 kCGImageAlphaNoneSkipFirst 12882 } 12883 enum : ulong { 12884 kCGBitmapAlphaInfoMask = 0x1F, 12885 kCGBitmapFloatComponents = (1 << 8), 12886 kCGBitmapByteOrderMask = 0x7000, 12887 kCGBitmapByteOrderDefault = (0 << 12), 12888 kCGBitmapByteOrder16Little = (1 << 12), 12889 kCGBitmapByteOrder32Little = (2 << 12), 12890 kCGBitmapByteOrder16Big = (3 << 12), 12891 kCGBitmapByteOrder32Big = (4 << 12) 12892 } 12893 enum CGPathDrawingMode { 12894 kCGPathFill, 12895 kCGPathEOFill, 12896 kCGPathStroke, 12897 kCGPathFillStroke, 12898 kCGPathEOFillStroke 12899 } 12900 enum objc_AssociationPolicy : size_t { 12901 OBJC_ASSOCIATION_ASSIGN = 0, 12902 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 12903 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 12904 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 12905 OBJC_ASSOCIATION_COPY = 0x303 //01403 12906 } 12907 12908 extern(C) { 12909 id objc_msgSend(id receiver, SEL selector, ...); 12910 id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...); 12911 id objc_getClass(const(char)* name); 12912 SEL sel_registerName(const(char)* str); 12913 Class objc_allocateClassPair(Class superclass, const(char)* name, 12914 size_t extra_bytes); 12915 void objc_registerClassPair(Class cls); 12916 BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types); 12917 id objc_getAssociatedObject(id object, void* key); 12918 void objc_setAssociatedObject(id object, void* key, id value, 12919 objc_AssociationPolicy policy); 12920 Ivar class_getInstanceVariable(Class cls, const(char)* name); 12921 id object_getIvar(id object, Ivar ivar); 12922 void object_setIvar(id object, Ivar ivar, id value); 12923 BOOL class_addIvar(Class cls, const(char)* name, 12924 size_t size, ubyte alignment, const(char)* types); 12925 12926 extern __gshared id NSApp; 12927 12928 void CFRelease(CFTypeRef obj); 12929 12930 CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator, 12931 const(char)* bytes, long numBytes, 12932 long encoding, 12933 BOOL isExternalRepresentation); 12934 long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding, 12935 char lossByte, bool isExternalRepresentation, 12936 char* buffer, long maxBufLen, long* usedBufLen); 12937 long CFStringGetLength(CFStringRef theString); 12938 12939 CGContextRef CGBitmapContextCreate(void* data, 12940 size_t width, size_t height, 12941 size_t bitsPerComponent, 12942 size_t bytesPerRow, 12943 CGColorSpaceRef colorspace, 12944 CGBitmapInfo bitmapInfo); 12945 void CGContextRelease(CGContextRef c); 12946 ubyte* CGBitmapContextGetData(CGContextRef c); 12947 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 12948 size_t CGBitmapContextGetWidth(CGContextRef c); 12949 size_t CGBitmapContextGetHeight(CGContextRef c); 12950 12951 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 12952 void CGColorSpaceRelease(CGColorSpaceRef cs); 12953 12954 void CGContextSetRGBStrokeColor(CGContextRef c, 12955 double red, double green, double blue, 12956 double alpha); 12957 void CGContextSetRGBFillColor(CGContextRef c, 12958 double red, double green, double blue, 12959 double alpha); 12960 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 12961 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, 12962 const(char)* str, size_t length); 12963 void CGContextStrokeLineSegments(CGContextRef c, 12964 const(CGPoint)* points, size_t count); 12965 12966 void CGContextBeginPath(CGContextRef c); 12967 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 12968 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 12969 void CGContextAddArc(CGContextRef c, double x, double y, double radius, 12970 double startAngle, double endAngle, long clockwise); 12971 void CGContextAddRect(CGContextRef c, CGRect rect); 12972 void CGContextAddLines(CGContextRef c, 12973 const(CGPoint)* points, size_t count); 12974 void CGContextSaveGState(CGContextRef c); 12975 void CGContextRestoreGState(CGContextRef c); 12976 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, 12977 ulong textEncoding); 12978 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 12979 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 12980 12981 void CGImageRelease(CGImageRef image); 12982 } 12983 12984 private: 12985 // A convenient method to create a CFString (=NSString) from a D string. 12986 CFStringRef createCFString(string str) { 12987 return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length, 12988 kCFStringEncodingUTF8, false); 12989 } 12990 12991 // Objective-C calls. 12992 RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) { 12993 auto _cmd = sel_registerName(selector.ptr); 12994 alias extern(C) RetType function(id, SEL, T) ExpectedType; 12995 return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args); 12996 } 12997 RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) { 12998 auto _cmd = sel_registerName(selector.ptr); 12999 auto cls = objc_getClass(className); 13000 alias extern(C) RetType function(id, SEL, T) ExpectedType; 13001 return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args); 13002 } 13003 RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) { 13004 return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args); 13005 } 13006 13007 alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay; 13008 alias objc_msgSend_classMethod!("alloc", id) alloc; 13009 alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:", 13010 id, NSRect, size_t, size_t, BOOL) initWithContentRect; 13011 alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle; 13012 alias objc_msgSend_specialized!("center", void) center; 13013 alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame; 13014 alias objc_msgSend_specialized!("setContentView:", void, id) setContentView; 13015 alias objc_msgSend_specialized!("release", void) release; 13016 alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor; 13017 alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor; 13018 alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront; 13019 alias objc_msgSend_specialized!("invalidate", void) invalidate; 13020 alias objc_msgSend_specialized!("close", void) close; 13021 alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:", 13022 id, double, id, SEL, id, BOOL) scheduledTimer; 13023 alias objc_msgSend_specialized!("run", void) run; 13024 alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext", 13025 id) currentNSGraphicsContext; 13026 alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort; 13027 alias objc_msgSend_specialized!("characters", CFStringRef) characters; 13028 alias objc_msgSend_specialized!("superclass", Class) superclass; 13029 alias objc_msgSend_specialized!("init", id) init; 13030 alias objc_msgSend_specialized!("addItem:", void, id) addItem; 13031 alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu; 13032 alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:", 13033 id, CFStringRef, SEL, CFStringRef) initWithTitle; 13034 alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu; 13035 alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate; 13036 alias objc_msgSend_specialized!("activateIgnoringOtherApps:", 13037 void, BOOL) activateIgnoringOtherApps; 13038 alias objc_msgSend_classMethod!("NSApplication", "sharedApplication", 13039 id) sharedNSApplication; 13040 alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy; 13041 } else static assert(0, "Unsupported operating system"); 13042 13043 13044 version(OSXCocoa) { 13045 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 13046 // 13047 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 13048 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 13049 // 13050 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 13051 // Probably won't even fully compile right now 13052 13053 import std.math : PI; 13054 import std.algorithm : map; 13055 import std.array : array; 13056 13057 alias SimpleWindow NativeWindowHandle; 13058 alias void delegate(id) NativeEventHandler; 13059 13060 __gshared Ivar simpleWindowIvar; 13061 13062 enum KEY_ESCAPE = 27; 13063 13064 mixin template NativeImageImplementation() { 13065 CGContextRef context; 13066 ubyte* rawData; 13067 final: 13068 13069 void convertToRgbaBytes(ubyte[] where) { 13070 assert(where.length == this.width * this.height * 4); 13071 13072 // if rawData had a length.... 13073 //assert(rawData.length == where.length); 13074 for(long idx = 0; idx < where.length; idx += 4) { 13075 auto alpha = rawData[idx + 3]; 13076 if(alpha == 255) { 13077 where[idx + 0] = rawData[idx + 0]; // r 13078 where[idx + 1] = rawData[idx + 1]; // g 13079 where[idx + 2] = rawData[idx + 2]; // b 13080 where[idx + 3] = rawData[idx + 3]; // a 13081 } else { 13082 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 13083 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 13084 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 13085 where[idx + 3] = rawData[idx + 3]; // a 13086 13087 } 13088 } 13089 } 13090 13091 void setFromRgbaBytes(in ubyte[] where) { 13092 // FIXME: this is probably wrong 13093 assert(where.length == this.width * this.height * 4); 13094 13095 // if rawData had a length.... 13096 //assert(rawData.length == where.length); 13097 for(long idx = 0; idx < where.length; idx += 4) { 13098 auto alpha = rawData[idx + 3]; 13099 if(alpha == 255) { 13100 rawData[idx + 0] = where[idx + 0]; // r 13101 rawData[idx + 1] = where[idx + 1]; // g 13102 rawData[idx + 2] = where[idx + 2]; // b 13103 rawData[idx + 3] = where[idx + 3]; // a 13104 } else { 13105 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 13106 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 13107 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 13108 rawData[idx + 3] = where[idx + 3]; // a 13109 13110 } 13111 } 13112 } 13113 13114 13115 void createImage(int width, int height, bool forcexshm=false) { 13116 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 13117 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 13118 colorSpace, 13119 kCGImageAlphaPremultipliedLast 13120 |kCGBitmapByteOrder32Big); 13121 CGColorSpaceRelease(colorSpace); 13122 rawData = CGBitmapContextGetData(context); 13123 } 13124 void dispose() { 13125 CGContextRelease(context); 13126 } 13127 13128 void setPixel(int x, int y, Color c) { 13129 auto offset = (y * width + x) * 4; 13130 if (c.a == 255) { 13131 rawData[offset + 0] = c.r; 13132 rawData[offset + 1] = c.g; 13133 rawData[offset + 2] = c.b; 13134 rawData[offset + 3] = c.a; 13135 } else { 13136 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 13137 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 13138 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 13139 rawData[offset + 3] = c.a; 13140 } 13141 } 13142 } 13143 13144 mixin template NativeScreenPainterImplementation() { 13145 CGContextRef context; 13146 ubyte[4] _outlineComponents; 13147 id view; 13148 13149 void create(NativeWindowHandle window) { 13150 context = window.drawingContext; 13151 view = window.view; 13152 } 13153 13154 void dispose() { 13155 setNeedsDisplay(view, true); 13156 } 13157 13158 // NotYetImplementedException 13159 Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); } 13160 void rasterOp(RasterOp op) {} 13161 Pen _activePen; 13162 Color _fillColor; 13163 Rectangle _clipRectangle; 13164 void setClipRectangle(int, int, int, int) {} 13165 void setFont(OperatingSystemFont) {} 13166 int fontHeight() { return 14; } 13167 13168 // end 13169 13170 void pen(Pen pen) { 13171 _activePen = pen; 13172 auto color = pen.color; // FIXME 13173 double alphaComponent = color.a/255.0f; 13174 CGContextSetRGBStrokeColor(context, 13175 color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 13176 13177 if (color.a != 255) { 13178 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 13179 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 13180 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 13181 _outlineComponents[3] = color.a; 13182 } else { 13183 _outlineComponents[0] = color.r; 13184 _outlineComponents[1] = color.g; 13185 _outlineComponents[2] = color.b; 13186 _outlineComponents[3] = color.a; 13187 } 13188 } 13189 13190 @property void fillColor(Color color) { 13191 CGContextSetRGBFillColor(context, 13192 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 13193 } 13194 13195 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 13196 // NotYetImplementedException for upper left/width/height 13197 auto cgImage = CGBitmapContextCreateImage(image.context); 13198 auto size = CGSize(CGBitmapContextGetWidth(image.context), 13199 CGBitmapContextGetHeight(image.context)); 13200 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 13201 CGImageRelease(cgImage); 13202 } 13203 13204 version(OSXCocoa) {} else // NotYetImplementedException 13205 void drawPixmap(Sprite image, int x, int y) { 13206 // FIXME: is this efficient? 13207 auto cgImage = CGBitmapContextCreateImage(image.context); 13208 auto size = CGSize(CGBitmapContextGetWidth(image.context), 13209 CGBitmapContextGetHeight(image.context)); 13210 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 13211 CGImageRelease(cgImage); 13212 } 13213 13214 13215 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 13216 // FIXME: alignment 13217 if (_outlineComponents[3] != 0) { 13218 CGContextSaveGState(context); 13219 auto invAlpha = 1.0f/_outlineComponents[3]; 13220 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 13221 _outlineComponents[1]*invAlpha, 13222 _outlineComponents[2]*invAlpha, 13223 _outlineComponents[3]/255.0f); 13224 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 13225 // auto cfstr = cast(id)createCFString(text); 13226 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 13227 // NSPoint(x, y), null); 13228 // CFRelease(cfstr); 13229 CGContextRestoreGState(context); 13230 } 13231 } 13232 13233 void drawPixel(int x, int y) { 13234 auto rawData = CGBitmapContextGetData(context); 13235 auto width = CGBitmapContextGetWidth(context); 13236 auto height = CGBitmapContextGetHeight(context); 13237 auto offset = ((height - y - 1) * width + x) * 4; 13238 rawData[offset .. offset+4] = _outlineComponents; 13239 } 13240 13241 void drawLine(int x1, int y1, int x2, int y2) { 13242 CGPoint[2] linePoints; 13243 linePoints[0] = CGPoint(x1, y1); 13244 linePoints[1] = CGPoint(x2, y2); 13245 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 13246 } 13247 13248 void drawRectangle(int x, int y, int width, int height) { 13249 CGContextBeginPath(context); 13250 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 13251 CGContextAddRect(context, rect); 13252 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 13253 } 13254 13255 void drawEllipse(int x1, int y1, int x2, int y2) { 13256 CGContextBeginPath(context); 13257 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 13258 CGContextAddEllipseInRect(context, rect); 13259 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 13260 } 13261 13262 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 13263 // @@@BUG@@@ Does not support elliptic arc (width != height). 13264 CGContextBeginPath(context); 13265 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 13266 start*PI/(180*64), finish*PI/(180*64), 0); 13267 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 13268 } 13269 13270 void drawPolygon(Point[] intPoints) { 13271 CGContextBeginPath(context); 13272 auto points = array(map!(CGPoint.fromTuple)(intPoints)); 13273 CGContextAddLines(context, points.ptr, points.length); 13274 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 13275 } 13276 } 13277 13278 mixin template NativeSimpleWindowImplementation() { 13279 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 13280 synchronized { 13281 if (NSApp == null) initializeApp(); 13282 } 13283 13284 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 13285 13286 // create the window. 13287 window = initWithContentRect(alloc("NSWindow"), 13288 contentRect, 13289 NSTitledWindowMask 13290 |NSClosableWindowMask 13291 |NSMiniaturizableWindowMask 13292 |NSResizableWindowMask, 13293 NSBackingStoreBuffered, 13294 true); 13295 13296 // set the title & move the window to center. 13297 auto windowTitle = createCFString(title); 13298 setTitle(window, windowTitle); 13299 CFRelease(windowTitle); 13300 center(window); 13301 13302 // create area to draw on. 13303 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 13304 drawingContext = CGBitmapContextCreate(null, width, height, 13305 8, 4*width, colorSpace, 13306 kCGImageAlphaPremultipliedLast 13307 |kCGBitmapByteOrder32Big); 13308 CGColorSpaceRelease(colorSpace); 13309 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 13310 auto matrix = CGContextGetTextMatrix(drawingContext); 13311 matrix.c = -matrix.c; 13312 matrix.d = -matrix.d; 13313 CGContextSetTextMatrix(drawingContext, matrix); 13314 13315 // create the subview that things will be drawn on. 13316 view = initWithFrame(alloc("SDGraphicsView"), contentRect); 13317 setContentView(window, view); 13318 object_setIvar(view, simpleWindowIvar, cast(id)this); 13319 release(view); 13320 13321 setBackgroundColor(window, whiteNSColor); 13322 makeKeyAndOrderFront(window, null); 13323 } 13324 void dispose() { 13325 closeWindow(); 13326 release(window); 13327 } 13328 void closeWindow() { 13329 invalidate(timer); 13330 .close(window); 13331 } 13332 13333 ScreenPainter getPainter() { 13334 return ScreenPainter(this, this); 13335 } 13336 13337 id window; 13338 id timer; 13339 id view; 13340 CGContextRef drawingContext; 13341 } 13342 13343 extern(C) { 13344 private: 13345 BOOL returnTrue3(id self, SEL _cmd, id app) { 13346 return true; 13347 } 13348 BOOL returnTrue2(id self, SEL _cmd) { 13349 return true; 13350 } 13351 13352 void pulse(id self, SEL _cmd) { 13353 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 13354 simpleWindow.handlePulse(); 13355 setNeedsDisplay(self, true); 13356 } 13357 void drawRect(id self, SEL _cmd, NSRect rect) { 13358 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 13359 auto curCtx = graphicsPort(currentNSGraphicsContext); 13360 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 13361 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), 13362 CGBitmapContextGetHeight(simpleWindow.drawingContext)); 13363 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 13364 CGImageRelease(cgImage); 13365 } 13366 void keyDown(id self, SEL _cmd, id event) { 13367 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 13368 13369 // the event may have multiple characters, and we send them all at 13370 // once. 13371 if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) { 13372 auto chars = characters(event); 13373 auto range = CFRange(0, CFStringGetLength(chars)); 13374 auto buffer = new char[range.length*3]; 13375 long actualLength; 13376 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false, 13377 buffer.ptr, cast(int) buffer.length, &actualLength); 13378 foreach (dchar dc; buffer[0..actualLength]) { 13379 if (simpleWindow.handleCharEvent) 13380 simpleWindow.handleCharEvent(dc); 13381 // NotYetImplementedException 13382 //if (simpleWindow.handleKeyEvent) 13383 //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp? 13384 } 13385 } 13386 13387 // the event's 'keyCode' is hardware-dependent. I don't think people 13388 // will like it. Let's leave it to the native handler. 13389 13390 // perform the default action. 13391 13392 // so the default action is to make a bomp sound and i dont want that 13393 // sooooooooo yeah not gonna do that. 13394 13395 //auto superData = objc_super(self, superclass(self)); 13396 //alias extern(C) void function(objc_super*, SEL, id) T; 13397 //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event); 13398 } 13399 } 13400 13401 // initialize the app so that it can be interacted with the user. 13402 // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html 13403 private void initializeApp() { 13404 // push an autorelease pool to avoid leaking. 13405 init(alloc("NSAutoreleasePool")); 13406 13407 // create a new NSApp instance 13408 sharedNSApplication; 13409 setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular); 13410 13411 // create the "Quit" menu. 13412 auto menuBar = init(alloc("NSMenu")); 13413 auto appMenuItem = init(alloc("NSMenuItem")); 13414 addItem(menuBar, appMenuItem); 13415 setMainMenu(NSApp, menuBar); 13416 release(appMenuItem); 13417 release(menuBar); 13418 13419 auto appMenu = init(alloc("NSMenu")); 13420 auto quitTitle = createCFString("Quit"); 13421 auto q = createCFString("q"); 13422 auto quitItem = initWithTitle(alloc("NSMenuItem"), 13423 quitTitle, sel_registerName("terminate:"), q); 13424 addItem(appMenu, quitItem); 13425 setSubmenu(appMenuItem, appMenu); 13426 release(quitItem); 13427 release(appMenu); 13428 CFRelease(q); 13429 CFRelease(quitTitle); 13430 13431 // assign a delegate for the application, allow it to quit when the last 13432 // window is closed. 13433 auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), 13434 "SDWindowCloseDelegate", 0); 13435 class_addMethod(delegateClass, 13436 sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"), 13437 &returnTrue3, "c@:@"); 13438 objc_registerClassPair(delegateClass); 13439 13440 auto appDelegate = init(alloc("SDWindowCloseDelegate")); 13441 setDelegate(NSApp, appDelegate); 13442 activateIgnoringOtherApps(NSApp, true); 13443 13444 // create a new view that draws the graphics and respond to keyDown 13445 // events. 13446 auto viewClass = objc_allocateClassPair(objc_getClass("NSView"), 13447 "SDGraphicsView", (void*).sizeof); 13448 class_addIvar(viewClass, "simpledisplay_simpleWindow", 13449 (void*).sizeof, (void*).alignof, "^v"); 13450 class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"), 13451 &pulse, "v@:"); 13452 class_addMethod(viewClass, sel_registerName("drawRect:"), 13453 &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); 13454 class_addMethod(viewClass, sel_registerName("isFlipped"), 13455 &returnTrue2, "c@:"); 13456 class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"), 13457 &returnTrue2, "c@:"); 13458 class_addMethod(viewClass, sel_registerName("keyDown:"), 13459 &keyDown, "v@:@"); 13460 objc_registerClassPair(viewClass); 13461 simpleWindowIvar = class_getInstanceVariable(viewClass, 13462 "simpledisplay_simpleWindow"); 13463 } 13464 } 13465 13466 version(without_opengl) {} else 13467 extern(System) nothrow @nogc { 13468 //enum uint GL_VERSION = 0x1F02; 13469 //const(char)* glGetString (/*GLenum*/uint); 13470 version(X11) { 13471 static if (!SdpyIsUsingIVGLBinds) { 13472 13473 enum GLX_X_RENDERABLE = 0x8012; 13474 enum GLX_DRAWABLE_TYPE = 0x8010; 13475 enum GLX_RENDER_TYPE = 0x8011; 13476 enum GLX_X_VISUAL_TYPE = 0x22; 13477 enum GLX_TRUE_COLOR = 0x8002; 13478 enum GLX_WINDOW_BIT = 0x00000001; 13479 enum GLX_RGBA_BIT = 0x00000001; 13480 enum GLX_COLOR_INDEX_BIT = 0x00000002; 13481 enum GLX_SAMPLE_BUFFERS = 0x186a0; 13482 enum GLX_SAMPLES = 0x186a1; 13483 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 13484 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 13485 } 13486 13487 // GLX_EXT_swap_control 13488 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 13489 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 13490 13491 //k8: ugly code to prevent warnings when sdpy is compiled into .a 13492 extern(System) { 13493 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 13494 } 13495 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 13496 13497 // this made public so we don't have to get it again and again 13498 public bool glXCreateContextAttribsARB_present () { 13499 if (glXCreateContextAttribsARBFn is cast(void*)1) { 13500 // get it 13501 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 13502 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 13503 } 13504 return (glXCreateContextAttribsARBFn !is null); 13505 } 13506 13507 // this made public so we don't have to get it again and again 13508 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 13509 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 13510 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 13511 } 13512 13513 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 13514 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 13515 if (_glx_swapInterval_fn is null) { 13516 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 13517 if (_glx_swapInterval_fn is null) { 13518 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 13519 return; 13520 } 13521 version(sdddd) { import std.stdio; writeln("glXSwapIntervalEXT found!"); } 13522 } 13523 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 13524 } 13525 } else version(Windows) { 13526 static if (!SdpyIsUsingIVGLBinds) { 13527 enum GL_TRUE = 1; 13528 enum GL_FALSE = 0; 13529 alias int GLint; 13530 13531 public void* glbindGetProcAddress (const(char)* name) { 13532 void* res = wglGetProcAddress(name); 13533 if (res is null) { 13534 /+ 13535 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 13536 import core.sys.windows.windef, core.sys.windows.winbase; 13537 __gshared HINSTANCE dll = null; 13538 if (dll is null) { 13539 dll = LoadLibraryA("opengl32.dll"); 13540 if (dll is null) return null; // <32, but idc 13541 } 13542 res = GetProcAddress(dll, name); 13543 +/ 13544 res = GetProcAddress(gl.libHandle, name); 13545 } 13546 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 13547 return res; 13548 } 13549 } 13550 13551 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 13552 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 13553 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 13554 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 13555 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 13556 13557 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 13558 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 13559 13560 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 13561 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 13562 13563 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 13564 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 13565 13566 void wglInitOtherFunctions () { 13567 if (wglCreateContextAttribsARB is null) { 13568 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 13569 } 13570 } 13571 } 13572 13573 static if (!SdpyIsUsingIVGLBinds) { 13574 13575 interface GL { 13576 extern(System) @nogc nothrow { 13577 13578 void glGetIntegerv(int, void*); 13579 void glMatrixMode(int); 13580 void glPushMatrix(); 13581 void glLoadIdentity(); 13582 void glOrtho(double, double, double, double, double, double); 13583 void glFrustum(double, double, double, double, double, double); 13584 13585 void glPopMatrix(); 13586 void glEnable(int); 13587 void glDisable(int); 13588 void glClear(int); 13589 void glBegin(int); 13590 void glVertex2f(float, float); 13591 void glVertex3f(float, float, float); 13592 void glEnd(); 13593 void glColor3b(byte, byte, byte); 13594 void glColor3ub(ubyte, ubyte, ubyte); 13595 void glColor4b(byte, byte, byte, byte); 13596 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 13597 void glColor3i(int, int, int); 13598 void glColor3ui(uint, uint, uint); 13599 void glColor4i(int, int, int, int); 13600 void glColor4ui(uint, uint, uint, uint); 13601 void glColor3f(float, float, float); 13602 void glColor4f(float, float, float, float); 13603 void glTranslatef(float, float, float); 13604 void glScalef(float, float, float); 13605 version(X11) { 13606 void glSecondaryColor3b(byte, byte, byte); 13607 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 13608 void glSecondaryColor3i(int, int, int); 13609 void glSecondaryColor3ui(uint, uint, uint); 13610 void glSecondaryColor3f(float, float, float); 13611 } 13612 13613 void glDrawElements(int, int, int, void*); 13614 13615 void glRotatef(float, float, float, float); 13616 13617 uint glGetError(); 13618 13619 void glDeleteTextures(int, uint*); 13620 13621 13622 void glRasterPos2i(int, int); 13623 void glDrawPixels(int, int, uint, uint, void*); 13624 void glClearColor(float, float, float, float); 13625 13626 13627 void glPixelStorei(uint, int); 13628 13629 void glGenTextures(uint, uint*); 13630 void glBindTexture(int, int); 13631 void glTexParameteri(uint, uint, int); 13632 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 13633 void glTexImage2D(int, int, int, int, int, int, int, int, in void*); 13634 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 13635 /*GLsizei*/int width, /*GLsizei*/int height, 13636 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 13637 version(linux) 13638 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 13639 /*GLsizei*/int width, /*GLsizei*/int height, 13640 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 13641 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 13642 13643 void glLineWidth(int); 13644 13645 13646 void glTexCoord2f(float, float); 13647 void glVertex2i(int, int); 13648 void glBlendFunc (int, int); 13649 void glDepthFunc (int); 13650 void glViewport(int, int, int, int); 13651 13652 void glClearDepth(double); 13653 13654 void glReadBuffer(uint); 13655 void glReadPixels(int, int, int, int, int, int, void*); 13656 13657 void glFlush(); 13658 void glFinish(); 13659 13660 version(Windows) { 13661 BOOL wglCopyContext(HGLRC, HGLRC, UINT); 13662 HGLRC wglCreateContext(HDC); 13663 HGLRC wglCreateLayerContext(HDC, int); 13664 BOOL wglDeleteContext(HGLRC); 13665 BOOL wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); 13666 HGLRC wglGetCurrentContext(); 13667 HDC wglGetCurrentDC(); 13668 int wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*); 13669 PROC wglGetProcAddress(LPCSTR); 13670 BOOL wglMakeCurrent(HDC, HGLRC); 13671 BOOL wglRealizeLayerPalette(HDC, int, BOOL); 13672 int wglSetLayerPaletteEntries(HDC, int, int, int, const(COLORREF)*); 13673 BOOL wglShareLists(HGLRC, HGLRC); 13674 BOOL wglSwapLayerBuffers(HDC, UINT); 13675 BOOL wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD); 13676 BOOL wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD); 13677 BOOL wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 13678 BOOL wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); 13679 } 13680 13681 } 13682 } 13683 13684 interface GLU { 13685 extern(System) @nogc nothrow { 13686 void gluLookAt(double, double, double, double, double, double, double, double, double); 13687 void gluPerspective(double, double, double, double); 13688 13689 char* gluErrorString(uint); 13690 } 13691 } 13692 13693 13694 enum GL_RED = 0x1903; 13695 enum GL_ALPHA = 0x1906; 13696 enum GL_UNPACK_ALIGNMENT = 0x0CF5; 13697 13698 enum uint GL_FRONT = 0x0404; 13699 13700 enum uint GL_BLEND = 0x0be2; 13701 enum uint GL_SRC_ALPHA = 0x0302; 13702 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 13703 enum uint GL_LEQUAL = 0x0203; 13704 13705 13706 enum uint GL_UNSIGNED_BYTE = 0x1401; 13707 enum uint GL_RGB = 0x1907; 13708 enum uint GL_BGRA = 0x80e1; 13709 enum uint GL_RGBA = 0x1908; 13710 enum uint GL_TEXTURE_2D = 0x0DE1; 13711 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 13712 enum uint GL_NEAREST = 0x2600; 13713 enum uint GL_LINEAR = 0x2601; 13714 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 13715 enum uint GL_TEXTURE_WRAP_S = 0x2802; 13716 enum uint GL_TEXTURE_WRAP_T = 0x2803; 13717 enum uint GL_REPEAT = 0x2901; 13718 enum uint GL_CLAMP = 0x2900; 13719 enum uint GL_CLAMP_TO_EDGE = 0x812F; 13720 enum uint GL_CLAMP_TO_BORDER = 0x812D; 13721 enum uint GL_DECAL = 0x2101; 13722 enum uint GL_MODULATE = 0x2100; 13723 enum uint GL_TEXTURE_ENV = 0x2300; 13724 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 13725 enum uint GL_REPLACE = 0x1E01; 13726 enum uint GL_LIGHTING = 0x0B50; 13727 enum uint GL_DITHER = 0x0BD0; 13728 13729 enum uint GL_NO_ERROR = 0; 13730 13731 13732 13733 enum int GL_VIEWPORT = 0x0BA2; 13734 enum int GL_MODELVIEW = 0x1700; 13735 enum int GL_TEXTURE = 0x1702; 13736 enum int GL_PROJECTION = 0x1701; 13737 enum int GL_DEPTH_TEST = 0x0B71; 13738 13739 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 13740 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 13741 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 13742 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 13743 13744 enum int GL_POINTS = 0x0000; 13745 enum int GL_LINES = 0x0001; 13746 enum int GL_LINE_LOOP = 0x0002; 13747 enum int GL_LINE_STRIP = 0x0003; 13748 enum int GL_TRIANGLES = 0x0004; 13749 enum int GL_TRIANGLE_STRIP = 5; 13750 enum int GL_TRIANGLE_FAN = 6; 13751 enum int GL_QUADS = 7; 13752 enum int GL_QUAD_STRIP = 8; 13753 enum int GL_POLYGON = 9; 13754 } 13755 } 13756 13757 version(without_opengl) {} else { 13758 static if(!SdpyIsUsingIVGLBinds) { 13759 version(Windows) { 13760 mixin DynamicLoad!(GL, "opengl32", true) gl; 13761 mixin DynamicLoad!(GLU, "glu32", true) glu; 13762 } else { 13763 mixin DynamicLoad!(GL, "GL", true) gl; 13764 mixin DynamicLoad!(GLU, "GLU", true) glu; 13765 } 13766 13767 shared static this() { 13768 gl.loadDynamicLibrary(); 13769 glu.loadDynamicLibrary(); 13770 } 13771 } 13772 } 13773 13774 version(linux) { 13775 version(with_eventloop) {} else { 13776 private int epollFd = -1; 13777 void prepareEventLoop() { 13778 if(epollFd != -1) 13779 return; // already initialized, no need to do it again 13780 import ep = core.sys.linux.epoll; 13781 13782 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 13783 if(epollFd == -1) 13784 throw new Exception("epoll create failure"); 13785 } 13786 } 13787 } else version(Posix) { 13788 void prepareEventLoop() {} 13789 } 13790 13791 version(X11) { 13792 import core.stdc.locale : LC_ALL; // rdmd fix 13793 __gshared bool sdx_isUTF8Locale; 13794 13795 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 13796 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 13797 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 13798 // anal magic is here. I (Ketmar) hope you like it. 13799 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 13800 // always return correct unicode symbols. The detection is here 'cause user can change locale 13801 // later. 13802 13803 // NOTE: IT IS VERY IMPORTANT THAT THIS BE THE LAST STATIC CTOR OF THE FILE since it tests librariesSuccessfullyLoaded 13804 shared static this () { 13805 if(!librariesSuccessfullyLoaded) 13806 return; 13807 13808 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 13809 13810 // this doesn't hurt; it may add some locking, but the speed is still 13811 // allows doing 60 FPS videogames; also, ignore the result, as most 13812 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 13813 // never seen this failing). 13814 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 13815 13816 setlocale(LC_ALL, ""); 13817 // check if out locale is UTF-8 13818 auto lct = setlocale(LC_CTYPE, null); 13819 if (lct is null) { 13820 sdx_isUTF8Locale = false; 13821 } else { 13822 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 13823 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 13824 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 13825 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 13826 { 13827 sdx_isUTF8Locale = true; 13828 break; 13829 } 13830 } 13831 } 13832 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 13833 } 13834 } 13835 13836 mixin template ExperimentalTextComponent2() { 13837 /+ 13838 Stage 1: get it working monospace 13839 Stage 2: use proportional font 13840 Stage 3: allow changes in inline style 13841 Stage 4: allow new fonts and sizes in the middle 13842 Stage 5: optimize gap buffer 13843 Stage 6: optimize layout 13844 Stage 7: word wrap 13845 Stage 8: justification 13846 Stage 9: editing, selection, etc. 13847 +/ 13848 } 13849 13850 13851 // Don't use this yet. When I'm happy with it, I will move it to the 13852 // regular module namespace. 13853 mixin template ExperimentalTextComponent() { 13854 13855 alias Rectangle = arsd.color.Rectangle; 13856 13857 struct ForegroundColor { 13858 Color color; 13859 alias color this; 13860 13861 this(Color c) { 13862 color = c; 13863 } 13864 13865 this(int r, int g, int b, int a = 255) { 13866 color = Color(r, g, b, a); 13867 } 13868 13869 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 13870 return ForegroundColor(mixin("Color." ~ s)); 13871 } 13872 } 13873 13874 struct BackgroundColor { 13875 Color color; 13876 alias color this; 13877 13878 this(Color c) { 13879 color = c; 13880 } 13881 13882 this(int r, int g, int b, int a = 255) { 13883 color = Color(r, g, b, a); 13884 } 13885 13886 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 13887 return BackgroundColor(mixin("Color." ~ s)); 13888 } 13889 } 13890 13891 static class InlineElement { 13892 string text; 13893 13894 BlockElement containingBlock; 13895 13896 Color color = Color.black; 13897 Color backgroundColor = Color.transparent; 13898 ushort styles; 13899 13900 string font; 13901 int fontSize; 13902 13903 int lineHeight; 13904 13905 void* identifier; 13906 13907 Rectangle boundingBox; 13908 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 13909 13910 bool isMergeCompatible(InlineElement other) { 13911 return 13912 containingBlock is other.containingBlock && 13913 color == other.color && 13914 backgroundColor == other.backgroundColor && 13915 styles == other.styles && 13916 font == other.font && 13917 fontSize == other.fontSize && 13918 lineHeight == other.lineHeight && 13919 true; 13920 } 13921 13922 int xOfIndex(size_t index) { 13923 if(index < letterXs.length) 13924 return letterXs[index]; 13925 else 13926 return boundingBox.right; 13927 } 13928 13929 InlineElement clone() { 13930 auto ie = new InlineElement(); 13931 ie.tupleof = this.tupleof; 13932 return ie; 13933 } 13934 13935 InlineElement getPreviousInlineElement() { 13936 InlineElement prev = null; 13937 foreach(ie; this.containingBlock.parts) { 13938 if(ie is this) 13939 break; 13940 prev = ie; 13941 } 13942 if(prev is null) { 13943 BlockElement pb; 13944 BlockElement cb = this.containingBlock; 13945 moar: 13946 foreach(ie; this.containingBlock.containingLayout.blocks) { 13947 if(ie is cb) 13948 break; 13949 pb = ie; 13950 } 13951 if(pb is null) 13952 return null; 13953 if(pb.parts.length == 0) { 13954 cb = pb; 13955 goto moar; 13956 } 13957 13958 prev = pb.parts[$-1]; 13959 13960 } 13961 return prev; 13962 } 13963 13964 InlineElement getNextInlineElement() { 13965 InlineElement next = null; 13966 foreach(idx, ie; this.containingBlock.parts) { 13967 if(ie is this) { 13968 if(idx + 1 < this.containingBlock.parts.length) 13969 next = this.containingBlock.parts[idx + 1]; 13970 break; 13971 } 13972 } 13973 if(next is null) { 13974 BlockElement n; 13975 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 13976 if(ie is this.containingBlock) { 13977 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 13978 n = this.containingBlock.containingLayout.blocks[idx + 1]; 13979 break; 13980 } 13981 } 13982 if(n is null) 13983 return null; 13984 13985 if(n.parts.length) 13986 next = n.parts[0]; 13987 else {} // FIXME 13988 13989 } 13990 return next; 13991 } 13992 13993 } 13994 13995 // Block elements are used entirely for positioning inline elements, 13996 // which are the things that are actually drawn. 13997 class BlockElement { 13998 InlineElement[] parts; 13999 uint alignment; 14000 14001 int whiteSpace; // pre, pre-wrap, wrap 14002 14003 TextLayout containingLayout; 14004 14005 // inputs 14006 Point where; 14007 Size minimumSize; 14008 Size maximumSize; 14009 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 14010 void* identifier; 14011 14012 Rectangle margin; 14013 Rectangle padding; 14014 14015 // outputs 14016 Rectangle[] boundingBoxes; 14017 } 14018 14019 struct TextIdentifyResult { 14020 InlineElement element; 14021 int offset; 14022 14023 private TextIdentifyResult fixupNewline() { 14024 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 14025 offset--; 14026 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 14027 offset--; 14028 } 14029 return this; 14030 } 14031 } 14032 14033 class TextLayout { 14034 BlockElement[] blocks; 14035 Rectangle boundingBox_; 14036 Rectangle boundingBox() { return boundingBox_; } 14037 void boundingBox(Rectangle r) { 14038 if(r != boundingBox_) { 14039 boundingBox_ = r; 14040 layoutInvalidated = true; 14041 } 14042 } 14043 14044 Rectangle contentBoundingBox() { 14045 Rectangle r; 14046 foreach(block; blocks) 14047 foreach(ie; block.parts) { 14048 if(ie.boundingBox.right > r.right) 14049 r.right = ie.boundingBox.right; 14050 if(ie.boundingBox.bottom > r.bottom) 14051 r.bottom = ie.boundingBox.bottom; 14052 } 14053 return r; 14054 } 14055 14056 BlockElement[] getBlocks() { 14057 return blocks; 14058 } 14059 14060 InlineElement[] getTexts() { 14061 InlineElement[] elements; 14062 foreach(block; blocks) 14063 elements ~= block.parts; 14064 return elements; 14065 } 14066 14067 string getPlainText() { 14068 string text; 14069 foreach(block; blocks) 14070 foreach(part; block.parts) 14071 text ~= part.text; 14072 return text; 14073 } 14074 14075 string getHtml() { 14076 return null; // FIXME 14077 } 14078 14079 this(Rectangle boundingBox) { 14080 this.boundingBox = boundingBox; 14081 } 14082 14083 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 14084 auto be = new BlockElement(); 14085 be.containingLayout = this; 14086 if(after is null) 14087 blocks ~= be; 14088 else { 14089 foreach(idx, b; blocks) { 14090 if(b is after.containingBlock) { 14091 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 14092 break; 14093 } 14094 } 14095 } 14096 return be; 14097 } 14098 14099 void clear() { 14100 blocks = null; 14101 selectionStart = selectionEnd = caret = Caret.init; 14102 } 14103 14104 void addText(Args...)(Args args) { 14105 if(blocks.length == 0) 14106 addBlock(); 14107 14108 InlineElement ie = new InlineElement(); 14109 foreach(idx, arg; args) { 14110 static if(is(typeof(arg) == ForegroundColor)) 14111 ie.color = arg; 14112 else static if(is(typeof(arg) == TextFormat)) { 14113 if(arg & 0x8000) // ~TextFormat.something turns it off 14114 ie.styles &= arg; 14115 else 14116 ie.styles |= arg; 14117 } else static if(is(typeof(arg) == string)) { 14118 static if(idx == 0 && args.length > 1) 14119 static assert(0, "Put styles before the string."); 14120 size_t lastLineIndex; 14121 foreach(cidx, char a; arg) { 14122 if(a == '\n') { 14123 ie.text = arg[lastLineIndex .. cidx + 1]; 14124 lastLineIndex = cidx + 1; 14125 ie.containingBlock = blocks[$-1]; 14126 blocks[$-1].parts ~= ie.clone; 14127 ie.text = null; 14128 } else { 14129 14130 } 14131 } 14132 14133 ie.text = arg[lastLineIndex .. $]; 14134 ie.containingBlock = blocks[$-1]; 14135 blocks[$-1].parts ~= ie.clone; 14136 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 14137 } 14138 } 14139 14140 invalidateLayout(); 14141 } 14142 14143 void tryMerge(InlineElement into, InlineElement what) { 14144 if(!into.isMergeCompatible(what)) { 14145 return; // cannot merge, different configs 14146 } 14147 14148 // cool, can merge, bring text together... 14149 into.text ~= what.text; 14150 14151 // and remove what 14152 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 14153 if(what.containingBlock.parts[a] is what) { 14154 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 14155 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 14156 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 14157 14158 } 14159 } 14160 14161 // FIXME: ensure no other carets have a reference to it 14162 } 14163 14164 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 14165 TextIdentifyResult identify(int x, int y, bool exact = false) { 14166 TextIdentifyResult inexactMatch; 14167 foreach(block; blocks) { 14168 foreach(part; block.parts) { 14169 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 14170 14171 // FIXME binary search 14172 int tidx; 14173 int lastX; 14174 foreach_reverse(idxo, lx; part.letterXs) { 14175 int idx = cast(int) idxo; 14176 if(lx <= x) { 14177 if(lastX && lastX - x < x - lx) 14178 tidx = idx + 1; 14179 else 14180 tidx = idx; 14181 break; 14182 } 14183 lastX = lx; 14184 } 14185 14186 return TextIdentifyResult(part, tidx).fixupNewline; 14187 } else if(!exact) { 14188 // we're not in the box, but are we on the same line? 14189 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 14190 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 14191 } 14192 } 14193 } 14194 14195 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 14196 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 14197 14198 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 14199 } 14200 14201 void moveCaretToPixelCoordinates(int x, int y) { 14202 auto result = identify(x, y); 14203 caret.inlineElement = result.element; 14204 caret.offset = result.offset; 14205 } 14206 14207 void selectToPixelCoordinates(int x, int y) { 14208 auto result = identify(x, y); 14209 14210 if(y < caretLastDrawnY1) { 14211 // on a previous line, carat is selectionEnd 14212 selectionEnd = caret; 14213 14214 selectionStart = Caret(this, result.element, result.offset); 14215 } else if(y > caretLastDrawnY2) { 14216 // on a later line 14217 selectionStart = caret; 14218 14219 selectionEnd = Caret(this, result.element, result.offset); 14220 } else { 14221 // on the same line... 14222 if(x <= caretLastDrawnX) { 14223 selectionEnd = caret; 14224 selectionStart = Caret(this, result.element, result.offset); 14225 } else { 14226 selectionStart = caret; 14227 selectionEnd = Caret(this, result.element, result.offset); 14228 } 14229 14230 } 14231 } 14232 14233 14234 /// Call this if the inputs change. It will reflow everything 14235 void redoLayout(ScreenPainter painter) { 14236 //painter.setClipRectangle(boundingBox); 14237 auto pos = Point(boundingBox.left, boundingBox.top); 14238 14239 int lastHeight; 14240 void nl() { 14241 pos.x = boundingBox.left; 14242 pos.y += lastHeight; 14243 } 14244 foreach(block; blocks) { 14245 nl(); 14246 foreach(part; block.parts) { 14247 part.letterXs = null; 14248 14249 auto size = painter.textSize(part.text); 14250 version(Windows) 14251 if(part.text.length && part.text[$-1] == '\n') 14252 size.height /= 2; // windows counts the new line at the end, but we don't want that 14253 14254 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 14255 14256 foreach(idx, char c; part.text) { 14257 // FIXME: unicode 14258 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 14259 } 14260 14261 pos.x += size.width; 14262 if(pos.x >= boundingBox.right) { 14263 pos.y += size.height; 14264 pos.x = boundingBox.left; 14265 lastHeight = 0; 14266 } else { 14267 lastHeight = size.height; 14268 } 14269 14270 if(part.text.length && part.text[$-1] == '\n') 14271 nl(); 14272 } 14273 } 14274 14275 layoutInvalidated = false; 14276 } 14277 14278 bool layoutInvalidated = true; 14279 void invalidateLayout() { 14280 layoutInvalidated = true; 14281 } 14282 14283 // FIXME: caret can remain sometimes when inserting 14284 // FIXME: inserting at the beginning once you already have something can eff it up. 14285 void drawInto(ScreenPainter painter, bool focused = false) { 14286 if(layoutInvalidated) 14287 redoLayout(painter); 14288 foreach(block; blocks) { 14289 foreach(part; block.parts) { 14290 painter.outlineColor = part.color; 14291 painter.fillColor = part.backgroundColor; 14292 14293 auto pos = part.boundingBox.upperLeft; 14294 auto size = part.boundingBox.size; 14295 14296 painter.drawText(pos, part.text); 14297 if(part.styles & TextFormat.underline) 14298 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 14299 if(part.styles & TextFormat.strikethrough) 14300 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 14301 } 14302 } 14303 14304 // on every redraw, I will force the caret to be 14305 // redrawn too, in order to eliminate perceived lag 14306 // when moving around with the mouse. 14307 eraseCaret(painter); 14308 14309 if(focused) { 14310 highlightSelection(painter); 14311 drawCaret(painter); 14312 } 14313 } 14314 14315 void highlightSelection(ScreenPainter painter) { 14316 if(selectionStart is selectionEnd) 14317 return; // no selection 14318 14319 if(selectionStart.inlineElement is null) return; 14320 if(selectionEnd.inlineElement is null) return; 14321 14322 assert(selectionStart.inlineElement !is null); 14323 assert(selectionEnd.inlineElement !is null); 14324 14325 painter.rasterOp = RasterOp.xor; 14326 painter.outlineColor = Color.transparent; 14327 painter.fillColor = Color(255, 255, 127); 14328 14329 auto at = selectionStart.inlineElement; 14330 auto atOffset = selectionStart.offset; 14331 bool done; 14332 while(at) { 14333 auto box = at.boundingBox; 14334 if(atOffset < at.letterXs.length) 14335 box.left = at.letterXs[atOffset]; 14336 14337 if(at is selectionEnd.inlineElement) { 14338 if(selectionEnd.offset < at.letterXs.length) 14339 box.right = at.letterXs[selectionEnd.offset]; 14340 done = true; 14341 } 14342 14343 painter.drawRectangle(box.upperLeft, box.width, box.height); 14344 14345 if(done) 14346 break; 14347 14348 at = at.getNextInlineElement(); 14349 atOffset = 0; 14350 } 14351 } 14352 14353 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 14354 bool caretShowingOnScreen = false; 14355 void drawCaret(ScreenPainter painter) { 14356 //painter.setClipRectangle(boundingBox); 14357 int x, y1, y2; 14358 if(caret.inlineElement is null) { 14359 x = boundingBox.left; 14360 y1 = boundingBox.top + 2; 14361 y2 = boundingBox.top + painter.fontHeight; 14362 } else { 14363 x = caret.inlineElement.xOfIndex(caret.offset); 14364 y1 = caret.inlineElement.boundingBox.top + 2; 14365 y2 = caret.inlineElement.boundingBox.bottom - 2; 14366 } 14367 14368 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 14369 eraseCaret(painter); 14370 14371 painter.pen = Pen(Color.white, 1); 14372 painter.rasterOp = RasterOp.xor; 14373 painter.drawLine( 14374 Point(x, y1), 14375 Point(x, y2) 14376 ); 14377 painter.rasterOp = RasterOp.normal; 14378 caretShowingOnScreen = !caretShowingOnScreen; 14379 14380 if(caretShowingOnScreen) { 14381 caretLastDrawnX = x; 14382 caretLastDrawnY1 = y1; 14383 caretLastDrawnY2 = y2; 14384 } 14385 } 14386 14387 Rectangle caretBoundingBox() { 14388 int x, y1, y2; 14389 if(caret.inlineElement is null) { 14390 x = boundingBox.left; 14391 y1 = boundingBox.top + 2; 14392 y2 = boundingBox.top + 16; 14393 } else { 14394 x = caret.inlineElement.xOfIndex(caret.offset); 14395 y1 = caret.inlineElement.boundingBox.top + 2; 14396 y2 = caret.inlineElement.boundingBox.bottom - 2; 14397 } 14398 14399 return Rectangle(x, y1, x + 1, y2); 14400 } 14401 14402 void eraseCaret(ScreenPainter painter) { 14403 //painter.setClipRectangle(boundingBox); 14404 if(!caretShowingOnScreen) return; 14405 painter.pen = Pen(Color.white, 1); 14406 painter.rasterOp = RasterOp.xor; 14407 painter.drawLine( 14408 Point(caretLastDrawnX, caretLastDrawnY1), 14409 Point(caretLastDrawnX, caretLastDrawnY2) 14410 ); 14411 14412 caretShowingOnScreen = false; 14413 painter.rasterOp = RasterOp.normal; 14414 } 14415 14416 /// Caret movement api 14417 /// These should give the user a logical result based on what they see on screen... 14418 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 14419 void moveUp() { 14420 if(caret.inlineElement is null) return; 14421 auto x = caret.inlineElement.xOfIndex(caret.offset); 14422 auto y = caret.inlineElement.boundingBox.top + 2; 14423 14424 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 14425 if(y < 0) 14426 return; 14427 14428 auto i = identify(x, y); 14429 14430 if(i.element) { 14431 caret.inlineElement = i.element; 14432 caret.offset = i.offset; 14433 } 14434 } 14435 void moveDown() { 14436 if(caret.inlineElement is null) return; 14437 auto x = caret.inlineElement.xOfIndex(caret.offset); 14438 auto y = caret.inlineElement.boundingBox.bottom - 2; 14439 14440 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 14441 14442 auto i = identify(x, y); 14443 if(i.element) { 14444 caret.inlineElement = i.element; 14445 caret.offset = i.offset; 14446 } 14447 } 14448 void moveLeft() { 14449 if(caret.inlineElement is null) return; 14450 if(caret.offset) 14451 caret.offset--; 14452 else { 14453 auto p = caret.inlineElement.getPreviousInlineElement(); 14454 if(p) { 14455 caret.inlineElement = p; 14456 if(p.text.length && p.text[$-1] == '\n') 14457 caret.offset = cast(int) p.text.length - 1; 14458 else 14459 caret.offset = cast(int) p.text.length; 14460 } 14461 } 14462 } 14463 void moveRight() { 14464 if(caret.inlineElement is null) return; 14465 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 14466 caret.offset++; 14467 } else { 14468 auto p = caret.inlineElement.getNextInlineElement(); 14469 if(p) { 14470 caret.inlineElement = p; 14471 caret.offset = 0; 14472 } 14473 } 14474 } 14475 void moveHome() { 14476 if(caret.inlineElement is null) return; 14477 auto x = 0; 14478 auto y = caret.inlineElement.boundingBox.top + 2; 14479 14480 auto i = identify(x, y); 14481 14482 if(i.element) { 14483 caret.inlineElement = i.element; 14484 caret.offset = i.offset; 14485 } 14486 } 14487 void moveEnd() { 14488 if(caret.inlineElement is null) return; 14489 auto x = int.max; 14490 auto y = caret.inlineElement.boundingBox.top + 2; 14491 14492 auto i = identify(x, y); 14493 14494 if(i.element) { 14495 caret.inlineElement = i.element; 14496 caret.offset = i.offset; 14497 } 14498 14499 } 14500 void movePageUp(ref Caret caret) {} 14501 void movePageDown(ref Caret caret) {} 14502 14503 void moveDocumentStart(ref Caret caret) { 14504 if(blocks.length && blocks[0].parts.length) 14505 caret = Caret(this, blocks[0].parts[0], 0); 14506 else 14507 caret = Caret.init; 14508 } 14509 14510 void moveDocumentEnd(ref Caret caret) { 14511 if(blocks.length) { 14512 auto parts = blocks[$-1].parts; 14513 if(parts.length) { 14514 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 14515 } else { 14516 caret = Caret.init; 14517 } 14518 } else 14519 caret = Caret.init; 14520 } 14521 14522 void deleteSelection() { 14523 if(selectionStart is selectionEnd) 14524 return; 14525 14526 if(selectionStart.inlineElement is null) return; 14527 if(selectionEnd.inlineElement is null) return; 14528 14529 assert(selectionStart.inlineElement !is null); 14530 assert(selectionEnd.inlineElement !is null); 14531 14532 auto at = selectionStart.inlineElement; 14533 14534 if(selectionEnd.inlineElement is at) { 14535 // same element, need to chop out 14536 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 14537 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 14538 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 14539 } else { 14540 // different elements, we can do it with slicing 14541 at.text = at.text[0 .. selectionStart.offset]; 14542 if(selectionStart.offset < at.letterXs.length) 14543 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 14544 14545 at = at.getNextInlineElement(); 14546 14547 while(at) { 14548 if(at is selectionEnd.inlineElement) { 14549 at.text = at.text[selectionEnd.offset .. $]; 14550 if(selectionEnd.offset < at.letterXs.length) 14551 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 14552 selectionEnd.offset = 0; 14553 break; 14554 } else { 14555 auto cfd = at; 14556 cfd.text = null; // delete the whole thing 14557 14558 at = at.getNextInlineElement(); 14559 14560 if(cfd.text.length == 0) { 14561 // and remove cfd 14562 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 14563 if(cfd.containingBlock.parts[a] is cfd) { 14564 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 14565 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 14566 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 14567 14568 } 14569 } 14570 } 14571 } 14572 } 14573 } 14574 14575 caret = selectionEnd; 14576 selectNone(); 14577 14578 invalidateLayout(); 14579 14580 } 14581 14582 /// Plain text editing api. These work at the current caret inside the selected inline element. 14583 void insert(in char[] text) { 14584 foreach(dchar ch; text) 14585 insert(ch); 14586 } 14587 /// ditto 14588 void insert(dchar ch) { 14589 14590 bool selectionDeleted = false; 14591 if(selectionStart !is selectionEnd) { 14592 deleteSelection(); 14593 selectionDeleted = true; 14594 } 14595 14596 if(ch == 127) { 14597 delete_(); 14598 return; 14599 } 14600 if(ch == 8) { 14601 if(!selectionDeleted) 14602 backspace(); 14603 return; 14604 } 14605 14606 invalidateLayout(); 14607 14608 if(ch == 13) ch = 10; 14609 auto e = caret.inlineElement; 14610 if(e is null) { 14611 addText("" ~ cast(char) ch) ; // FIXME 14612 return; 14613 } 14614 14615 if(caret.offset == e.text.length) { 14616 e.text ~= cast(char) ch; // FIXME 14617 caret.offset++; 14618 if(ch == 10) { 14619 auto c = caret.inlineElement.clone; 14620 c.text = null; 14621 c.letterXs = null; 14622 insertPartAfter(c,e); 14623 caret = Caret(this, c, 0); 14624 } 14625 } else { 14626 // FIXME cast char sucks 14627 if(ch == 10) { 14628 auto c = caret.inlineElement.clone; 14629 c.text = e.text[caret.offset .. $]; 14630 if(caret.offset < c.letterXs.length) 14631 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 14632 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 14633 if(caret.offset <= e.letterXs.length) { 14634 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 14635 } 14636 insertPartAfter(c,e); 14637 caret = Caret(this, c, 0); 14638 } else { 14639 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 14640 caret.offset++; 14641 } 14642 } 14643 } 14644 14645 void insertPartAfter(InlineElement what, InlineElement where) { 14646 foreach(idx, p; where.containingBlock.parts) { 14647 if(p is where) { 14648 if(idx + 1 == where.containingBlock.parts.length) 14649 where.containingBlock.parts ~= what; 14650 else 14651 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 14652 return; 14653 } 14654 } 14655 } 14656 14657 void cleanupStructures() { 14658 for(size_t i = 0; i < blocks.length; i++) { 14659 auto block = blocks[i]; 14660 for(size_t a = 0; a < block.parts.length; a++) { 14661 auto part = block.parts[a]; 14662 if(part.text.length == 0) { 14663 for(size_t b = a; b < block.parts.length - 1; b++) 14664 block.parts[b] = block.parts[b+1]; 14665 block.parts = block.parts[0 .. $-1]; 14666 } 14667 } 14668 if(block.parts.length == 0) { 14669 for(size_t a = i; a < blocks.length - 1; a++) 14670 blocks[a] = blocks[a+1]; 14671 blocks = blocks[0 .. $-1]; 14672 } 14673 } 14674 } 14675 14676 void backspace() { 14677 try_again: 14678 auto e = caret.inlineElement; 14679 if(e is null) 14680 return; 14681 if(caret.offset == 0) { 14682 auto prev = e.getPreviousInlineElement(); 14683 if(prev is null) 14684 return; 14685 auto newOffset = cast(int) prev.text.length; 14686 tryMerge(prev, e); 14687 caret.inlineElement = prev; 14688 caret.offset = prev is null ? 0 : newOffset; 14689 14690 goto try_again; 14691 } else if(caret.offset == e.text.length) { 14692 e.text = e.text[0 .. $-1]; 14693 caret.offset--; 14694 } else { 14695 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 14696 caret.offset--; 14697 } 14698 //cleanupStructures(); 14699 14700 invalidateLayout(); 14701 } 14702 void delete_() { 14703 if(selectionStart !is selectionEnd) 14704 deleteSelection(); 14705 else { 14706 auto before = caret; 14707 moveRight(); 14708 if(caret != before) { 14709 backspace(); 14710 } 14711 } 14712 14713 invalidateLayout(); 14714 } 14715 void overstrike() {} 14716 14717 /// Selection API. See also: caret movement. 14718 void selectAll() { 14719 moveDocumentStart(selectionStart); 14720 moveDocumentEnd(selectionEnd); 14721 } 14722 bool selectNone() { 14723 if(selectionStart != selectionEnd) { 14724 selectionStart = selectionEnd = Caret.init; 14725 return true; 14726 } 14727 return false; 14728 } 14729 14730 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 14731 /// They will modify the current selection if there is one and will splice one in if needed. 14732 void changeAttributes() {} 14733 14734 14735 /// Text search api. They manipulate the selection and/or caret. 14736 void findText(string text) {} 14737 void findIndex(size_t textIndex) {} 14738 14739 // sample event handlers 14740 14741 void handleEvent(KeyEvent event) { 14742 //if(event.type == KeyEvent.Type.KeyPressed) { 14743 14744 //} 14745 } 14746 14747 void handleEvent(dchar ch) { 14748 14749 } 14750 14751 void handleEvent(MouseEvent event) { 14752 14753 } 14754 14755 bool contentEditable; // can it be edited? 14756 bool contentCaretable; // is there a caret/cursor that moves around in there? 14757 bool contentSelectable; // selectable? 14758 14759 Caret caret; 14760 Caret selectionStart; 14761 Caret selectionEnd; 14762 14763 bool insertMode; 14764 } 14765 14766 struct Caret { 14767 TextLayout layout; 14768 InlineElement inlineElement; 14769 int offset; 14770 } 14771 14772 enum TextFormat : ushort { 14773 // decorations 14774 underline = 1, 14775 strikethrough = 2, 14776 14777 // font selectors 14778 14779 bold = 0x4000 | 1, // weight 700 14780 light = 0x4000 | 2, // weight 300 14781 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 14782 // bold | light is really invalid but should give weight 500 14783 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 14784 14785 italic = 0x4000 | 8, 14786 smallcaps = 0x4000 | 16, 14787 } 14788 14789 void* findFont(string family, int weight, TextFormat formats) { 14790 return null; 14791 } 14792 14793 } 14794 14795 static if(UsingSimpledisplayX11) { 14796 14797 enum _NET_WM_STATE_ADD = 1; 14798 enum _NET_WM_STATE_REMOVE = 0; 14799 enum _NET_WM_STATE_TOGGLE = 2; 14800 14801 /// X-specific. Use [SimpleWindow.requestAttention] instead for most casesl 14802 void demandAttention(SimpleWindow window, bool needs = true) { 14803 demandAttention(window.impl.window, needs); 14804 } 14805 14806 /// ditto 14807 void demandAttention(Window window, bool needs = true) { 14808 auto display = XDisplayConnection.get(); 14809 auto atom = XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", true); 14810 if(atom == None) 14811 return; // non-failure error 14812 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 14813 14814 XClientMessageEvent xclient; 14815 14816 xclient.type = EventType.ClientMessage; 14817 xclient.window = window; 14818 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 14819 xclient.format = 32; 14820 xclient.data.l[0] = needs ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 14821 xclient.data.l[1] = atom; 14822 //xclient.data.l[2] = atom2; 14823 // [2] == a second property 14824 // [3] == source. 0 == unknown, 1 == app, 2 == else 14825 14826 XSendEvent( 14827 display, 14828 RootWindow(display, DefaultScreen(display)), 14829 false, 14830 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 14831 cast(XEvent*) &xclient 14832 ); 14833 14834 /+ 14835 XChangeProperty( 14836 display, 14837 window.impl.window, 14838 GetAtom!"_NET_WM_STATE"(display), 14839 XA_ATOM, 14840 32 /* bits */, 14841 PropModeAppend, 14842 &atom, 14843 1); 14844 +/ 14845 } 14846 14847 /// X-specific 14848 TrueColorImage getWindowNetWmIcon(Window window) { 14849 try { 14850 auto display = XDisplayConnection.get; 14851 14852 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 14853 14854 if (data.length > arch_ulong.sizeof * 2) { 14855 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 14856 // these are an array of rgba images that we have to convert into pixmaps ourself 14857 14858 int width = cast(int) meta[0]; 14859 int height = cast(int) meta[1]; 14860 14861 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 14862 14863 static if(arch_ulong.sizeof == 4) { 14864 bytes = bytes[0 .. width * height * 4]; 14865 alias imageData = bytes; 14866 } else static if(arch_ulong.sizeof == 8) { 14867 bytes = bytes[0 .. width * height * 8]; 14868 auto imageData = new ubyte[](4 * width * height); 14869 } else static assert(0); 14870 14871 14872 14873 // this returns ARGB. Remember it is little-endian so 14874 // we have BGRA 14875 // our thing uses RGBA, which in little endian, is ABGR 14876 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 14877 auto r = bytes[idx + 2]; 14878 auto g = bytes[idx + 1]; 14879 auto b = bytes[idx + 0]; 14880 auto a = bytes[idx + 3]; 14881 14882 imageData[idx2 + 0] = r; 14883 imageData[idx2 + 1] = g; 14884 imageData[idx2 + 2] = b; 14885 imageData[idx2 + 3] = a; 14886 } 14887 14888 return new TrueColorImage(width, height, imageData); 14889 } 14890 14891 return null; 14892 } catch(Exception e) { 14893 return null; 14894 } 14895 } 14896 14897 } /* UsingSimpledisplayX11 */ 14898 14899 14900 void loadBinNameToWindowClassName () { 14901 import core.stdc.stdlib : realloc; 14902 version(linux) { 14903 // args[0] MAY be empty, so we'll just use this 14904 import core.sys.posix.unistd : readlink; 14905 char[1024] ebuf = void; // 1KB should be enough for everyone! 14906 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 14907 if (len < 1) return; 14908 } else /*version(Windows)*/ { 14909 import core.runtime : Runtime; 14910 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 14911 auto ebuf = Runtime.args[0]; 14912 auto len = ebuf.length; 14913 } 14914 auto pos = len; 14915 while (pos > 0 && ebuf[pos-1] != '/') --pos; 14916 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 14917 if (sdpyWindowClassStr is null) return; // oops 14918 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 14919 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 14920 } 14921 14922 /++ 14923 An interface representing a font. 14924 14925 This is still MAJOR work in progress. 14926 +/ 14927 interface DrawableFont { 14928 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 14929 } 14930 14931 /++ 14932 Loads a true type font using [arsd.ttf]. That module must be compiled 14933 in if you choose to use this function. 14934 14935 Be warned: this can be slow and memory hungry, especially on remote connections 14936 to the X server. 14937 14938 This is still MAJOR work in progress. 14939 +/ 14940 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 14941 import arsd.ttf; 14942 static class ArsdTtfFont : DrawableFont { 14943 TtfFont font; 14944 int size; 14945 this(in ubyte[] data, int size) { 14946 font = TtfFont(data); 14947 this.size = size; 14948 } 14949 14950 Sprite[string] cache; 14951 14952 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 14953 Sprite sprite = (text in cache) ? *(text in cache) : null; 14954 14955 auto fg = painter.impl._outlineColor; 14956 auto bg = painter.impl._fillColor; 14957 14958 if(sprite is null) { 14959 int width, height; 14960 auto data = font.renderString(text, size, width, height); 14961 auto image = new TrueColorImage(width, height); 14962 int pos = 0; 14963 foreach(y; 0 .. height) 14964 foreach(x; 0 .. width) { 14965 fg.a = data[0]; 14966 bg.a = 255; 14967 auto color = alphaBlend(fg, bg); 14968 image.imageData.bytes[pos++] = color.r; 14969 image.imageData.bytes[pos++] = color.g; 14970 image.imageData.bytes[pos++] = color.b; 14971 image.imageData.bytes[pos++] = data[0]; 14972 data = data[1 .. $]; 14973 } 14974 assert(data.length == 0); 14975 14976 sprite = new Sprite(painter.window, Image.fromMemoryImage(image)); 14977 cache[text.idup] = sprite; 14978 } 14979 14980 sprite.drawAt(painter, upperLeft); 14981 } 14982 } 14983 14984 return new ArsdTtfFont(data, size); 14985 } 14986 14987 class NotYetImplementedException : Exception { 14988 this(string file = __FILE__, size_t line = __LINE__) { 14989 super("Not yet implemented", file, line); 14990 } 14991 } 14992 14993 /// 14994 __gshared bool librariesSuccessfullyLoaded = true; 14995 /// 14996 __gshared bool openGlLibrariesSuccessfullyLoaded = true; 14997 14998 private mixin template DynamicLoad(Iface, string library, bool openGLRelated = false) { 14999 static foreach(name; __traits(derivedMembers, Iface)) 15000 mixin("__gshared typeof(&__traits(getMember, Iface, name)) " ~ name ~ ";"); 15001 15002 private void* libHandle; 15003 15004 void loadDynamicLibrary() { 15005 version(Posix) { 15006 import core.sys.posix.dlfcn; 15007 version(OSX) { 15008 version(X11) 15009 libHandle = dlopen("/usr/X11/lib/lib" ~ library ~ ".dylib", RTLD_NOW); 15010 else 15011 libHandle = dlopen(library ~ ".dylib", RTLD_NOW); 15012 } else 15013 libHandle = dlopen("lib" ~ library ~ ".so", RTLD_NOW); 15014 15015 static void* loadsym(void* l, const char* name) { 15016 import core.stdc.stdlib; 15017 if(l is null) 15018 return &abort; 15019 return dlsym(l, name); 15020 } 15021 } else version(Windows) { 15022 import core.sys.windows.windows; 15023 libHandle = LoadLibrary(library ~ ".dll"); 15024 static void* loadsym(void* l, const char* name) { 15025 import core.stdc.stdlib; 15026 if(l is null) 15027 return &abort; 15028 return GetProcAddress(l, name); 15029 } 15030 } 15031 if(libHandle is null) { 15032 if(openGLRelated) 15033 openGlLibrariesSuccessfullyLoaded = false; 15034 else 15035 librariesSuccessfullyLoaded = false; 15036 //throw new Exception("load failure of library " ~ library); 15037 } 15038 foreach(name; __traits(derivedMembers, Iface)) { 15039 mixin("alias tmp = " ~ name ~ ";"); 15040 tmp = cast(typeof(tmp)) loadsym(libHandle, name); 15041 if(tmp is null) throw new Exception("load failure of function " ~ name ~ " from " ~ library); 15042 } 15043 } 15044 15045 void unloadDynamicLibrary() { 15046 version(Posix) { 15047 import core.sys.posix.dlfcn; 15048 dlclose(libHandle); 15049 } else version(Windows) { 15050 import core.sys.windows.windows; 15051 FreeLibrary(libHandle); 15052 } 15053 foreach(name; __traits(derivedMembers, Iface)) 15054 mixin(name ~ " = null;"); 15055 } 15056 } 15057 15058 private alias scriptable = arsd_jsvar_compatible;