1 // https://dpaste.dzfl.pl/7a77355acaec 2 3 /* 4 Event Loop would be nices: 5 6 * add on idle - runs when nothing else happens 7 * send messages without a recipient window 8 * setTimeout 9 * setInterval 10 11 */ 12 13 /* 14 Text layout needs a lot of work. Plain drawText is useful but too 15 limited. It will need some kind of text context thing which it will 16 update and you can pass it on and get more details out of it. 17 18 It will need a bounding box, a current cursor location that is updated 19 as drawing continues, and various changable facts (which can also be 20 changed on the painter i guess) like font, color, size, background, 21 etc. 22 23 We can also fetch the caret location from it somehow. 24 25 Should prolly be an overload of drawText 26 27 blink taskbar / demand attention cross platform. FlashWindow and demandAttention 28 29 WS_EX_NOACTIVATE 30 WS_CHILD - owner and owned vs parent and child. Does X have something similar? 31 full screen windows. Can just set the atom on X. Windows will be harder. 32 33 moving windows. resizing windows. 34 35 hide cursor, capture cursor, change cursor. 36 37 REMEMBER: simpledisplay does NOT have to do everything! It just needs to make 38 sure the pieces are there to do its job easily and make other jobs possible. 39 */ 40 41 /++ 42 simpledisplay.d provides basic cross-platform GUI-related functionality, 43 including creating windows, drawing on them, working with the clipboard, 44 timers, OpenGL, and more. However, it does NOT provide high level GUI 45 widgets. See my minigui.d, an extension to this module, for that 46 functionality. 47 48 simpledisplay provides cross-platform wrapping for Windows and Linux 49 (and perhaps other OSes that use X11), but also does not prevent you 50 from using the underlying facilities if you need them. It has a goal 51 of working efficiently over a remote X link (at least as far as Xlib 52 reasonably allows.) 53 54 simpledisplay depends on [arsd.color|color.d], which should be available from the 55 same place where you got this file. Other than that, however, it has 56 very few dependencies and ones that don't come with the OS and/or the 57 compiler are all opt-in. 58 59 simpledisplay.d's home base is on my arsd repo on Github. The file is: 60 https://github.com/adamdruppe/arsd/blob/master/simpledisplay.d 61 62 simpledisplay is basically stable. I plan to refactor the internals, 63 and may add new features and fix bugs, but It do not expect to 64 significantly change the API. It has been stable a few years already now. 65 66 Installation_instructions: 67 68 `simpledisplay.d` does not have any dependencies outside the 69 operating system and `color.d`, so it should just work most the 70 time, but there are a few caveats on some systems: 71 72 Please note when compiling on Win64, you need to explicitly list 73 `-Lgdi32.lib -Luser32.lib` on the build command. If you want the Windows 74 subsystem too, use `-L/subsystem:windows -L/entry:mainCRTStartup`. 75 76 On Win32, you can pass `-L/subsystem:windows` if you don't want a 77 console to be automatically allocated. 78 79 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. 80 81 On Ubuntu, you might need to install X11 development libraries to 82 successfully link. 83 84 $(CONSOLE 85 $ sudo apt-get install libglc-dev 86 $ sudo apt-get install libx11-dev 87 ) 88 89 90 Jump_list: 91 92 Don't worry, you don't have to read this whole documentation file! 93 94 Check out the [#Event-example] and [#Pong-example] to get started quickly. 95 96 The main classes you may want to create are [SimpleWindow], [Timer], 97 [Image], and [Sprite]. 98 99 The main functions you'll want are [setClipboardText] and [getClipboardText]. 100 101 There are also platform-specific functions available such as [XDisplayConnection] 102 and [GetAtom] for X11, among others. 103 104 See the examples and topics list below to learn more. 105 106 107 $(H2 About this documentation) 108 109 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. 110 111 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! 112 113 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. 114 115 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. 116 117 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. 118 119 At points, I will talk about implementation details in the documentation. These are sometimes 120 subject to change, but nevertheless useful to understand what is really going on. You can learn 121 more about some of the referenced things by searching the web for info about using them from C. 122 You can always look at the source of simpledisplay.d too for the most authoritative source on 123 its specific implementation. If you disagree with how I did something, please contact me so we 124 can discuss it! 125 126 Examples: 127 128 $(H3 Event-example) 129 This program creates a window and draws events inside them as they 130 happen, scrolling the text in the window as needed. Run this program 131 and experiment to get a feel for where basic input events take place 132 in the library. 133 134 --- 135 // dmd example.d simpledisplay.d color.d 136 import arsd.simpledisplay; 137 import std.conv; 138 139 void main() { 140 auto window = new SimpleWindow(Size(500, 500), "Event example - simpledisplay.d"); 141 142 int y = 0; 143 144 void addLine(string text) { 145 auto painter = window.draw(); 146 147 if(y + painter.fontHeight >= window.height) { 148 painter.scrollArea(Point(0, 0), window.width, window.height, 0, painter.fontHeight); 149 y -= painter.fontHeight; 150 } 151 152 painter.outlineColor = Color.red; 153 painter.fillColor = Color.black; 154 painter.drawRectangle(Point(0, y), window.width, painter.fontHeight); 155 156 painter.outlineColor = Color.white; 157 158 painter.drawText(Point(10, y), text); 159 160 y += painter.fontHeight; 161 } 162 163 window.eventLoop(1000, 164 () { 165 addLine("Timer went off!"); 166 }, 167 (KeyEvent event) { 168 addLine(to!string(event)); 169 }, 170 (MouseEvent event) { 171 addLine(to!string(event)); 172 }, 173 (dchar ch) { 174 addLine(to!string(ch)); 175 } 176 ); 177 } 178 --- 179 180 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. 181 182 This program displays a pie chart. Clicking on a color will increase its share of the pie. 183 184 --- 185 186 --- 187 188 $(H2 Topics) 189 190 $(H3 $(ID topic-windows) Windows) 191 The [SimpleWindow] class is simpledisplay's flagship feature. It represents a single 192 window on the user's screen. 193 194 You may create multiple windows, if the underlying platform supports it. You may check 195 `static if(multipleWindowsSupported)` at compile time, or catch exceptions thrown by 196 SimpleWindow's constructor at runtime to handle those cases. 197 198 A single running event loop will handle as many windows as needed. 199 200 setEventHandlers function 201 eventLoop function 202 draw function 203 title property 204 205 $(H3 $(ID topic-event-loops) Event loops) 206 The simpledisplay event loop is designed to handle common cases easily while being extensible for more advanced cases, or replaceable by other libraries. 207 208 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: 209 210 --- 211 // dmd example.d simpledisplay.d color.d 212 import arsd.simpledisplay; 213 void main() { 214 auto window = new SimpleWindow(200, 200); 215 window.eventLoop(0, 216 delegate (dchar) { /* got a character key press */ } 217 ); 218 } 219 --- 220 221 $(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.) 222 223 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. 224 225 On Linux, simpledisplay also supports my [arsd.eventloop] module. Compile your program, including the eventloop.d file, with the `-version=with_eventloop` switch. 226 227 It should be possible to integrate simpledisplay with vibe.d as well, though I haven't tried. 228 229 $(H3 $(ID topic-notification-areas) Notification area (aka systray) icons) 230 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. 231 232 $(H3 $(ID topic-input-handling) Input handling) 233 There are event handlers for low-level keyboard and mouse events, and higher level handlers for character events. 234 235 $(H3 $(ID topic-2d-drawing) 2d Drawing) 236 To draw on your window, use the [SimpleWindow.draw] method. It returns a [ScreenPainter] structure with drawing methods. 237 238 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: 239 240 --- 241 // dmd example.d simpledisplay.d color.d 242 import arsd.simpledisplay; 243 void main() { 244 auto window = new SimpleWindow(200, 200); 245 { // introduce sub-scope 246 auto painter = window.draw(); // begin drawing 247 /* draw here */ 248 painter.outlineColor = Color.red; 249 painter.fillColor = Color.black; 250 painter.drawRectangle(Point(0, 0), 200, 200); 251 } // end scope, calling `painter`'s destructor, drawing to the screen. 252 window.eventLoop(0); // handle events 253 } 254 --- 255 256 Painting is done based on two color properties, a pen and a brush. 257 258 At this time, the 2d drawing does not support alpha blending. If you need that, use a 2d OpenGL context instead. 259 FIXME add example of 2d opengl drawing here 260 $(H3 $(ID topic-3d-drawing) 3d Drawing (or 2d with OpenGL)) 261 simpledisplay can create OpenGL contexts on your window. It works quite differently than 2d drawing. 262 263 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. 264 265 To start, you create a [SimpleWindow] with OpenGL enabled by passing the argument [OpenGlOptions.yes] to the constructor. 266 267 Next, you set [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlScene] to a delegate which draws your frame. 268 269 To force a redraw of the scene, call [SimpleWindow.redrawOpenGlScene|window.redrawOpenGlSceneNow()]. 270 271 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. 272 273 This example program will draw a rectangle on your window: 274 275 --- 276 // dmd example.d simpledisplay.d color.d 277 import arsd.simpledisplay; 278 279 void main() { 280 281 } 282 --- 283 284 $(H3 $(ID topic-images) Displaying images) 285 You can also load PNG images using [arsd.png]. 286 287 --- 288 // dmd example.d simpledisplay.d color.d png.d 289 import arsd.simpledisplay; 290 import arsd.png; 291 292 void main() { 293 auto image = Image.fromMemoryImage(readPng("image.png")); 294 displayImage(image); 295 } 296 --- 297 298 Compile with `dmd example.d simpledisplay.d png.d`. 299 300 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. 301 302 $(H3 $(ID topic-sprites) Sprites) 303 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. 304 305 $(H3 $(ID topic-clipboard) Clipboard) 306 The free functions [getClipboardText] and [setClipboardText] consist of simpledisplay's cross-platform clipboard support at this time. 307 308 It also has helpers for handling X-specific events. 309 310 $(H3 $(ID topic-timers) Timers) 311 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]. 312 313 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. 314 315 --- 316 import arsd.simpledisplay; 317 318 void main() { 319 auto window = new SimpleWindow(400, 400); 320 // every 100 ms, it will draw a random line 321 // on the window. 322 window.eventLoop(100, { 323 auto painter = window.draw(); 324 325 import std.random; 326 // random color 327 painter.outlineColor = Color(uniform(0, 256), uniform(0, 256), uniform(0, 256)); 328 // random line 329 painter.drawLine( 330 Point(uniform(0, window.width), uniform(0, window.height)), 331 Point(uniform(0, window.width), uniform(0, window.height))); 332 333 }); 334 } 335 --- 336 337 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. 338 339 The pulse timer and instances of the [Timer] class may be combined at will. 340 341 --- 342 import arsd.simpledisplay; 343 344 void main() { 345 auto window = new SimpleWindow(400, 400); 346 auto timer = new Timer(1000, delegate { 347 auto painter = window.draw(); 348 painter.clear(); 349 }); 350 351 window.eventLoop(0); 352 } 353 --- 354 355 Timers are currently only implemented on Windows, using `SetTimer` and Linux, using `timerfd_create`. These deliver timeout messages through your application event loop. 356 357 $(H3 $(ID topic-os-helpers) OS-specific helpers) 358 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. 359 360 See also: `xwindows.d` from my github. 361 362 $(H3 $(ID topic-os-extension) Extending with OS-specific functionality) 363 `handleNativeEvent` and `handleNativeGlobalEvent`. 364 365 $(H3 $(ID topic-integration) Integration with other libraries) 366 Integration with a third-party event loop is possible. 367 368 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. 369 370 $(H3 $(ID topic-guis) GUI widgets) 371 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! 372 373 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. 374 375 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.) 376 377 minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes. 378 379 $(H2 Platform-specific tips and tricks) 380 381 Windows_tips: 382 383 You can add icons or manifest files to your exe using a resource file. 384 385 To create a Windows .ico file, use the gimp or something. I'll write a helper 386 program later. 387 388 Create `yourapp.rc`: 389 390 ```rc 391 1 ICON filename.ico 392 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest" 393 ``` 394 395 And `yourapp.exe.manifest`: 396 397 ```xml 398 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 399 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 400 <assemblyIdentity 401 version="1.0.0.0" 402 processorArchitecture="*" 403 name="CompanyName.ProductName.YourApplication" 404 type="win32" 405 /> 406 <description>Your application description here.</description> 407 <dependency> 408 <dependentAssembly> 409 <assemblyIdentity 410 type="win32" 411 name="Microsoft.Windows.Common-Controls" 412 version="6.0.0.0" 413 processorArchitecture="*" 414 publicKeyToken="6595b64144ccf1df" 415 language="*" 416 /> 417 </dependentAssembly> 418 </dependency> 419 </assembly> 420 ``` 421 422 423 $(H2 $(ID developer-notes) Developer notes) 424 425 I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa 426 implementation though. 427 428 The NativeSimpleWindowImplementation and NativeScreenPainterImplementation both 429 suck. If I was rewriting it, I wouldn't do it that way again. 430 431 This file must not have any more required dependencies. If you need bindings, add 432 them right to this file. Once it gets into druntime and is there for a while, remove 433 bindings from here to avoid conflicts (or put them in an appropriate version block 434 so it continues to just work on old dmd), but wait a couple releases before making the 435 transition so this module remains usable with older versions of dmd. 436 437 You may have optional dependencies if needed by putting them in version blocks or 438 template functions. You may also extend the module with other modules with UFCS without 439 actually editing this - that is nice to do if you can. 440 441 Try to make functions work the same way across operating systems. I typically make 442 it thinly wrap Windows, then emulate that on Linux. 443 444 A goal of this is to keep a gui hello world to less than 250 KB. This means avoiding 445 Phobos! So try to avoid it. 446 447 See more comments throughout the source. 448 449 I realize this file is fairly large, but over half that is just bindings at the bottom 450 or documentation at the top. Some of the classes are a bit big too, but hopefully easy 451 to understand. I suggest you jump around the source by looking for a particular 452 declaration you're interested in, like `class SimpleWindow` using your editor's search 453 function, then look at one piece at a time. 454 455 Authors: Adam D. Ruppe with the help of others. If you need help, please email me with 456 destructionator@gmail.com or find me on IRC. Our channel is #d on Freenode. I go by 457 Destructionator or adam_d_ruppe, depending on which computer I'm logged into. 458 459 I live in the eastern United States, so I will most likely not be around at night in 460 that US east timezone. 461 462 License: Copyright Adam D. Ruppe, 2011-2017. Released under the Boost Software License. 463 464 Building documentation: You may wish to use the `arsd.ddoc` file from my github with 465 building the documentation for simpledisplay yourself. It will give it a bit more style. 466 Simply download the arsd.ddoc file and add it to your compile command when building docs. 467 `dmd -c simpledisplay.d color.d -D arsd.ddoc` 468 +/ 469 module arsd.simpledisplay; 470 471 // FIXME: tetris demo 472 // FIXME: space invaders demo 473 // FIXME: asteroids demo 474 475 /++ $(ID Pong-example) 476 $(H3 Pong) 477 478 This program creates a little Pong-like game. Player one is controlled 479 with the keyboard. Player two is controlled with the mouse. It demos 480 the pulse timer, event handling, and some basic drawing. 481 +/ 482 unittest { 483 // dmd example.d simpledisplay.d color.d 484 import arsd.simpledisplay; 485 486 enum paddleMovementSpeed = 8; 487 enum paddleHeight = 48; 488 489 void main() { 490 auto window = new SimpleWindow(600, 400, "Pong game!"); 491 492 int playerOnePosition, playerTwoPosition; 493 int playerOneMovement, playerTwoMovement; 494 int playerOneScore, playerTwoScore; 495 496 int ballX, ballY; 497 int ballDx, ballDy; 498 499 void serve() { 500 import std.random; 501 502 ballX = window.width / 2; 503 ballY = window.height / 2; 504 ballDx = uniform(-4, 4) * 3; 505 ballDy = uniform(-4, 4) * 3; 506 if(ballDx == 0) 507 ballDx = uniform(0, 2) == 0 ? 3 : -3; 508 } 509 510 serve(); 511 512 window.eventLoop(50, // set a 50 ms timer pulls 513 // This runs once per timer pulse 514 delegate () { 515 auto painter = window.draw(); 516 517 painter.clear(); 518 519 // Update everyone's motion 520 playerOnePosition += playerOneMovement; 521 playerTwoPosition += playerTwoMovement; 522 523 ballX += ballDx; 524 ballY += ballDy; 525 526 // Bounce off the top and bottom edges of the window 527 if(ballY + 7 >= window.height) 528 ballDy = -ballDy; 529 if(ballY - 8 <= 0) 530 ballDy = -ballDy; 531 532 // Bounce off the paddle, if it is in position 533 if(ballX - 8 <= 16) { 534 if(ballY + 7 > playerOnePosition && ballY - 8 < playerOnePosition + paddleHeight) { 535 ballDx = -ballDx + 1; // add some speed to keep it interesting 536 ballDy += playerOneMovement; // and y movement based on your controls too 537 ballX = 24; // move it past the paddle so it doesn't wiggle inside 538 } else { 539 // Missed it 540 playerTwoScore ++; 541 serve(); 542 } 543 } 544 545 if(ballX + 7 >= window.width - 16) { // do the same thing but for player 1 546 if(ballY + 7 > playerTwoPosition && ballY - 8 < playerTwoPosition + paddleHeight) { 547 ballDx = -ballDx - 1; 548 ballDy += playerTwoMovement; 549 ballX = window.width - 24; 550 } else { 551 // Missed it 552 playerOneScore ++; 553 serve(); 554 } 555 } 556 557 // Draw the paddles 558 painter.outlineColor = Color.black; 559 painter.drawLine(Point(16, playerOnePosition), Point(16, playerOnePosition + paddleHeight)); 560 painter.drawLine(Point(window.width - 16, playerTwoPosition), Point(window.width - 16, playerTwoPosition + paddleHeight)); 561 562 // Draw the ball 563 painter.fillColor = Color.red; 564 painter.outlineColor = Color.yellow; 565 painter.drawEllipse(Point(ballX - 8, ballY - 8), Point(ballX + 7, ballY + 7)); 566 567 // Draw the score 568 painter.outlineColor = Color.blue; 569 import std.conv; 570 painter.drawText(Point(64, 4), to!string(playerOneScore)); 571 painter.drawText(Point(window.width - 64, 4), to!string(playerTwoScore)); 572 573 }, 574 delegate (KeyEvent event) { 575 // Player 1's controls are the arrow keys on the keyboard 576 if(event.key == Key.Down) 577 playerOneMovement = event.pressed ? paddleMovementSpeed : 0; 578 if(event.key == Key.Up) 579 playerOneMovement = event.pressed ? -paddleMovementSpeed : 0; 580 581 }, 582 delegate (MouseEvent event) { 583 // Player 2's controls are mouse movement while the left button is held down 584 if(event.type == MouseEventType.motion && (event.modifierState & ModifierState.leftButtonDown)) { 585 if(event.dy > 0) 586 playerTwoMovement = paddleMovementSpeed; 587 else if(event.dy < 0) 588 playerTwoMovement = -paddleMovementSpeed; 589 } else { 590 playerTwoMovement = 0; 591 } 592 } 593 ); 594 } 595 } 596 597 /++ $(ID example-minesweeper) 598 599 This minesweeper demo shows how we can implement another classic 600 game with simpledisplay and shows some mouse input and basic output 601 code. 602 +/ 603 unittest { 604 import arsd.simpledisplay; 605 606 enum GameSquare { 607 mine = 0, 608 clear, 609 m1, m2, m3, m4, m5, m6, m7, m8 610 } 611 612 enum UserSquare { 613 unknown, 614 revealed, 615 flagged, 616 questioned 617 } 618 619 enum GameState { 620 inProgress, 621 lose, 622 win 623 } 624 625 GameSquare[] board; 626 UserSquare[] userState; 627 GameState gameState; 628 int boardWidth; 629 int boardHeight; 630 631 bool isMine(int x, int y) { 632 if(x < 0 || y < 0 || x >= boardWidth || y >= boardHeight) 633 return false; 634 return board[y * boardWidth + x] == GameSquare.mine; 635 } 636 637 GameState reveal(int x, int y) { 638 if(board[y * boardWidth + x] == GameSquare.clear) { 639 floodFill(userState, boardWidth, boardHeight, 640 UserSquare.unknown, UserSquare.revealed, 641 x, y, 642 (x, y) { 643 if(board[y * boardWidth + x] == GameSquare.clear) 644 return true; 645 else { 646 userState[y * boardWidth + x] = UserSquare.revealed; 647 return false; 648 } 649 }); 650 } else { 651 userState[y * boardWidth + x] = UserSquare.revealed; 652 if(isMine(x, y)) 653 return GameState.lose; 654 } 655 656 foreach(state; userState) { 657 if(state == UserSquare.unknown || state == UserSquare.questioned) 658 return GameState.inProgress; 659 } 660 661 return GameState.win; 662 } 663 664 void initializeBoard(int width, int height, int numberOfMines) { 665 boardWidth = width; 666 boardHeight = height; 667 board.length = width * height; 668 669 userState.length = width * height; 670 userState[] = UserSquare.unknown; 671 672 import std.algorithm, std.random, std.range; 673 674 board[] = GameSquare.clear; 675 676 foreach(minePosition; randomSample(iota(0, board.length), numberOfMines)) 677 board[minePosition] = GameSquare.mine; 678 679 int x; 680 int y; 681 foreach(idx, ref square; board) { 682 if(square == GameSquare.clear) { 683 int danger = 0; 684 danger += isMine(x-1, y-1)?1:0; 685 danger += isMine(x-1, y)?1:0; 686 danger += isMine(x-1, y+1)?1:0; 687 danger += isMine(x, y-1)?1:0; 688 danger += isMine(x, y+1)?1:0; 689 danger += isMine(x+1, y-1)?1:0; 690 danger += isMine(x+1, y)?1:0; 691 danger += isMine(x+1, y+1)?1:0; 692 693 square = cast(GameSquare) (danger + 1); 694 } 695 696 x++; 697 if(x == width) { 698 x = 0; 699 y++; 700 } 701 } 702 } 703 704 void redraw(SimpleWindow window) { 705 import std.conv; 706 707 auto painter = window.draw(); 708 709 painter.clear(); 710 711 final switch(gameState) with(GameState) { 712 case inProgress: 713 break; 714 case win: 715 painter.fillColor = Color.green; 716 painter.drawRectangle(Point(0, 0), window.width, window.height); 717 return; 718 case lose: 719 painter.fillColor = Color.red; 720 painter.drawRectangle(Point(0, 0), window.width, window.height); 721 return; 722 } 723 724 int x = 0; 725 int y = 0; 726 727 foreach(idx, square; board) { 728 auto state = userState[idx]; 729 730 final switch(state) with(UserSquare) { 731 case unknown: 732 painter.outlineColor = Color.black; 733 painter.fillColor = Color(128,128,128); 734 735 painter.drawRectangle( 736 Point(x * 20, y * 20), 737 20, 20 738 ); 739 break; 740 case revealed: 741 if(square == GameSquare.clear) { 742 painter.outlineColor = Color.white; 743 painter.fillColor = Color.white; 744 745 painter.drawRectangle( 746 Point(x * 20, y * 20), 747 20, 20 748 ); 749 } else { 750 painter.outlineColor = Color.black; 751 painter.fillColor = Color.white; 752 753 painter.drawText( 754 Point(x * 20, y * 20), 755 to!string(square)[1..2], 756 Point(x * 20 + 20, y * 20 + 20), 757 TextAlignment.Center | TextAlignment.VerticalCenter); 758 } 759 break; 760 case flagged: 761 painter.outlineColor = Color.black; 762 painter.fillColor = Color.red; 763 painter.drawRectangle( 764 Point(x * 20, y * 20), 765 20, 20 766 ); 767 break; 768 case questioned: 769 painter.outlineColor = Color.black; 770 painter.fillColor = Color.yellow; 771 painter.drawRectangle( 772 Point(x * 20, y * 20), 773 20, 20 774 ); 775 break; 776 } 777 778 x++; 779 if(x == boardWidth) { 780 x = 0; 781 y++; 782 } 783 } 784 785 } 786 787 void main() { 788 auto window = new SimpleWindow(200, 200); 789 790 initializeBoard(10, 10, 10); 791 792 redraw(window); 793 window.eventLoop(0, 794 delegate (MouseEvent me) { 795 if(me.type != MouseEventType.buttonPressed) 796 return; 797 auto x = me.x / 20; 798 auto y = me.y / 20; 799 if(x >= 0 && x < boardWidth && y >= 0 && y < boardHeight) { 800 if(me.button == MouseButton.left) { 801 gameState = reveal(x, y); 802 } else { 803 userState[y*boardWidth+x] = UserSquare.flagged; 804 } 805 redraw(window); 806 } 807 } 808 ); 809 } 810 } 811 812 version(without_opengl) { 813 enum SdpyIsUsingIVGLBinds = false; 814 } else /*version(Posix)*/ { 815 static if (__traits(compiles, (){import iv.glbinds;})) { 816 enum SdpyIsUsingIVGLBinds = true; 817 public import iv.glbinds; 818 //pragma(msg, "SDPY: using iv.glbinds"); 819 } else { 820 enum SdpyIsUsingIVGLBinds = false; 821 } 822 //} else { 823 // enum SdpyIsUsingIVGLBinds = false; 824 } 825 826 827 version(Windows) { 828 //import core.sys.windows.windows; 829 import core.sys.windows.winnls; 830 import core.sys.windows.windef; 831 import core.sys.windows.basetyps; 832 import core.sys.windows.winbase; 833 import core.sys.windows.winuser; 834 import core.sys.windows.shellapi; 835 import core.sys.windows.wingdi; 836 static import gdi = core.sys.windows.wingdi; // so i 837 838 pragma(lib, "gdi32"); 839 pragma(lib, "user32"); 840 } else version (linux) { 841 //k8: this is hack for rdmd. sorry. 842 static import core.sys.linux.epoll; 843 static import core.sys.linux.timerfd; 844 } 845 846 847 // FIXME: icons on Windows don't look quite right, I think the transparency mask is off. 848 849 // http://wiki.dlang.org/Simpledisplay.d 850 851 // see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led 852 853 // Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl 854 // but can i control the scroll lock led 855 856 857 // Note: if you are using Image on X, you might want to do: 858 /* 859 static if(UsingSimpledisplayX11) { 860 if(!Image.impl.xshmAvailable) { 861 // the images will use the slower XPutImage, you might 862 // want to consider an alternative method to get better speed 863 } 864 } 865 866 If the shared memory extension is available though, simpledisplay uses it 867 for a significant speed boost whenever you draw large Images. 868 */ 869 870 // 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. 871 872 // WARNING: if you are using with_eventloop, don't forget to call XFlush(XDisplayConnection.get()); before calling loop()! 873 874 /* 875 Biggest FIXME: 876 make sure the key event numbers match between X and Windows OR provide symbolic constants on each system 877 878 clean up opengl contexts when their windows close 879 880 fix resizing the bitmaps/pixmaps 881 */ 882 883 // BTW on Windows: 884 // -L/SUBSYSTEM:WINDOWS:5.0 885 // to dmd will make a nice windows binary w/o a console if you want that. 886 887 /* 888 Stuff to add: 889 890 use multibyte functions everywhere we can 891 892 OpenGL windows 893 more event stuff 894 extremely basic windows w/ no decoration for tooltips, splash screens, etc. 895 896 897 resizeEvent 898 and make the windows non-resizable by default, 899 or perhaps stretched (if I can find something in X like StretchBlt) 900 901 take a screenshot function! 902 903 Pens and brushes? 904 Maybe a global event loop? 905 906 Mouse deltas 907 Key items 908 */ 909 910 /* 911 From MSDN: 912 913 You can also use the GET_X_LPARAM or GET_Y_LPARAM macro to extract the x- or y-coordinate. 914 915 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. 916 917 */ 918 919 version(linux) { 920 version = X11; 921 version(without_libnotify) { 922 // we cool 923 } 924 else 925 version = libnotify; 926 } 927 928 version(libnotify) { 929 pragma(lib, "dl"); 930 import core.sys.posix.dlfcn; 931 932 void delegate()[int] libnotify_action_delegates; 933 int libnotify_action_delegates_count; 934 extern(C) static void libnotify_action_callback_sdpy(void* notification, char* action, void* user_data) { 935 auto idx = cast(int) user_data; 936 if(auto dgptr = idx in libnotify_action_delegates) { 937 (*dgptr)(); 938 libnotify_action_delegates.remove(idx); 939 } 940 } 941 942 struct C_DynamicLibrary { 943 void* handle; 944 this(string name) { 945 handle = dlopen((name ~ "\0").ptr, RTLD_NOW); 946 if(handle is null) 947 throw new Exception("dlopen"); 948 } 949 950 void close() { 951 dlclose(handle); 952 } 953 954 ~this() { 955 // close 956 } 957 958 template call(string func, Ret, Args...) { 959 extern(C) Ret function(Args) fptr; 960 typeof(fptr) call() { 961 fptr = cast(typeof(fptr)) dlsym(handle, func); 962 return fptr; 963 } 964 } 965 } 966 967 C_DynamicLibrary* libnotify; 968 } 969 970 version(OSX) { 971 version(OSXCocoa) {} 972 else { version = X11; } 973 } 974 //version = OSXCocoa; // this was written by KennyTM 975 version(FreeBSD) 976 version = X11; 977 version(Solaris) 978 version = X11; 979 980 981 void featureNotImplemented()() { 982 version(allow_unimplemented_features) 983 throw new NotYetImplementedException(); 984 else 985 static assert(0); 986 } 987 988 // these are so the static asserts don't trigger unless you want to 989 // add support to it for an OS 990 version(Windows) 991 version = with_timer; 992 version(linux) 993 version = with_timer; 994 995 /// 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. 996 version(Windows) 997 enum bool UsingSimpledisplayWindows = true; 998 else 999 enum bool UsingSimpledisplayWindows = false; 1000 1001 /// 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. 1002 version(X11) 1003 enum bool UsingSimpledisplayX11 = true; 1004 else 1005 enum bool UsingSimpledisplayX11 = false; 1006 1007 /// 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. 1008 version(OSXCocoa) 1009 enum bool UsingSimpledisplayCocoa = true; 1010 else 1011 enum bool UsingSimpledisplayCocoa = false; 1012 1013 /// Does this platform support multiple windows? If not, trying to create another will cause it to throw an exception. 1014 version(Windows) 1015 enum multipleWindowsSupported = true; 1016 else version(X11) 1017 enum multipleWindowsSupported = true; 1018 else version(OSXCocoa) 1019 enum multipleWindowsSupported = true; 1020 else 1021 static assert(0); 1022 1023 version(without_opengl) 1024 enum bool OpenGlEnabled = false; 1025 else 1026 enum bool OpenGlEnabled = true; 1027 1028 1029 /++ 1030 After selecting a type from [WindowTypes], you may further customize 1031 its behavior by setting one or more of these flags. 1032 1033 1034 The different window types have different meanings of `normal`. If the 1035 window type already is a good match for what you want to do, you should 1036 just use [WindowFlags.normal], the default, which will do the right thing 1037 for your users. 1038 1039 The window flags will not always be honored by the operating system 1040 and window managers; they are hints, not commands. 1041 +/ 1042 enum WindowFlags : int { 1043 normal = 0, /// 1044 skipTaskbar = 1, /// 1045 alwaysOnTop = 2, /// 1046 alwaysOnBottom = 4, /// 1047 cannotBeActivated = 8, /// 1048 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. 1049 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. 1050 dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. 1051 } 1052 1053 /++ 1054 When creating a window, you can pass a type to SimpleWindow's constructor, 1055 then further customize the window by changing `WindowFlags`. 1056 1057 1058 You should mostly only need [normal], [undecorated], and [eventOnly] for normal 1059 use. The others are there to build a foundation for a higher level GUI toolkit, 1060 but are themselves not as high level as you might think from their names. 1061 1062 This list is based on the EMWH spec for X11. 1063 http://standards.freedesktop.org/wm-spec/1.4/ar01s05.html#idm139704063786896 1064 +/ 1065 enum WindowTypes : int { 1066 /// An ordinary application window. 1067 normal, 1068 /// 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. 1069 undecorated, 1070 /// 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. 1071 eventOnly, 1072 /// A drop down menu, such as from a menu bar 1073 dropdownMenu, 1074 /// A popup menu, such as from a right click 1075 popupMenu, 1076 /// A popup bubble notification 1077 notification, 1078 /* 1079 menu, /// a tearable menu bar 1080 splashScreen, /// a loading splash screen for your application 1081 tooltip, /// A tiny window showing temporary help text or something. 1082 comboBoxDropdown, 1083 dialog, 1084 toolbar 1085 */ 1086 /// a child nested inside the parent. You must pass a parent window to the ctor 1087 nestedChild, 1088 } 1089 1090 1091 private __gshared ushort sdpyOpenGLContextVersion = 0; // default: use legacy call 1092 private __gshared bool sdpyOpenGLContextCompatible = true; // default: allow "deprecated" features 1093 private __gshared char* sdpyWindowClassStr = null; 1094 private __gshared bool sdpyOpenGLContextAllowFallback = false; 1095 1096 /** 1097 Set OpenGL context version to use. This has no effect on non-OpenGL windows. 1098 You may want to change context version if you want to use advanced shaders or 1099 other modern OpenGL techinques. This setting doesn't affect already created 1100 windows. You may use version 2.1 as your default, which should be supported 1101 by any box since 2006, so seems to be a reasonable choice. 1102 1103 Note that by default version is set to `0`, which forces SimpleDisplay to use 1104 old context creation code without any version specified. This is the safest 1105 way to init OpenGL, but it may not give you access to advanced features. 1106 1107 See available OpenGL versions here: https://en.wikipedia.org/wiki/OpenGL 1108 */ 1109 void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion = cast(ushort)(hi<<8|lo); } 1110 1111 /** 1112 Set OpenGL context mode. Modern (3.0+) OpenGL versions deprecated old fixed 1113 pipeline functions, and without "compatible" mode you won't be able to use 1114 your old non-shader-based code with such contexts. By default SimpleDisplay 1115 creates compatible context, so you can gradually upgrade your OpenGL code if 1116 you want to (or leave it as is, as it should "just work"). 1117 */ 1118 @property void openGLContextCompatible() (bool v) { sdpyOpenGLContextCompatible = v; } 1119 1120 /** 1121 Set to `true` to allow creating OpenGL context with lower version than requested 1122 instead of throwing. If fallback was activated (or legacy OpenGL was requested), 1123 `openGLContextFallbackActivated()` will return `true`. 1124 */ 1125 @property void openGLContextAllowFallback() (bool v) { sdpyOpenGLContextAllowFallback = v; } 1126 1127 /** 1128 After creating OpenGL window, you can check this to see if you got only "legacy" OpenGL context. 1129 */ 1130 @property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); } 1131 1132 1133 /** 1134 Set window class name for all following `new SimpleWindow()` calls. 1135 1136 WARNING! For Windows, you should set your class name before creating any 1137 window, and NEVER change it after that! 1138 */ 1139 void sdpyWindowClass (const(char)[] v) { 1140 import core.stdc.stdlib : realloc; 1141 if (v.length == 0) v = "SimpleDisplayWindow"; 1142 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, v.length+1); 1143 if (sdpyWindowClassStr is null) return; // oops 1144 sdpyWindowClassStr[0..v.length+1] = 0; 1145 sdpyWindowClassStr[0..v.length] = v[]; 1146 } 1147 1148 /** 1149 Get current window class name. 1150 */ 1151 string sdpyWindowClass () { 1152 if (sdpyWindowClassStr is null) return null; 1153 foreach (immutable idx; 0..size_t.max-1) { 1154 if (sdpyWindowClassStr[idx] == 0) return sdpyWindowClassStr[0..idx].idup; 1155 } 1156 return null; 1157 } 1158 1159 /++ 1160 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. 1161 +/ 1162 float[2] getDpi() { 1163 float[2] dpi; 1164 version(Windows) { 1165 HDC screen = GetDC(null); 1166 dpi[0] = GetDeviceCaps(screen, LOGPIXELSX); 1167 dpi[1] = GetDeviceCaps(screen, LOGPIXELSY); 1168 } else version(X11) { 1169 auto display = XDisplayConnection.get; 1170 auto screen = DefaultScreen(display); 1171 1172 void fallback() { 1173 // 25.4 millimeters in an inch... 1174 dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; 1175 dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; 1176 } 1177 1178 char* resourceString = XResourceManagerString(display); 1179 XrmInitialize(); 1180 1181 auto db = XrmGetStringDatabase(resourceString); 1182 1183 if (resourceString) { 1184 XrmValue value; 1185 char* type; 1186 if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { 1187 if (value.addr) { 1188 import core.stdc.stdlib; 1189 dpi[0] = atof(cast(char*) value.addr); 1190 dpi[1] = dpi[0]; 1191 } else { 1192 fallback(); 1193 } 1194 } else { 1195 fallback(); 1196 } 1197 } else { 1198 fallback(); 1199 } 1200 } 1201 1202 return dpi; 1203 } 1204 1205 version(X11) { 1206 extern(C) char* XResourceManagerString(Display*); 1207 extern(C) void XrmInitialize(); 1208 extern(C) XrmDatabase XrmGetStringDatabase(char* data); 1209 extern(C) bool XrmGetResource(XrmDatabase, const char*, const char*, char**, XrmValue*); 1210 alias XrmDatabase = void*; 1211 struct XrmValue { 1212 uint size; 1213 void* addr; 1214 } 1215 } 1216 1217 TrueColorImage trueColorImageFromNativeHandle(NativeWindowHandle handle, int width, int height) { 1218 throw new Exception("not implemented"); 1219 version(none) { 1220 version(X11) { 1221 auto display = XDisplayConnection.get; 1222 auto image = XGetImage(display, handle, 0, 0, width, height, (cast(c_ulong) ~0) /*AllPlanes*/, ZPixmap); 1223 1224 // https://github.com/adamdruppe/arsd/issues/98 1225 1226 // FIXME: copy that shit 1227 1228 XDestroyImage(image); 1229 } else version(Windows) { 1230 // I just need to BitBlt that shit... BUT WAIT IT IS ALREADY IN A DIB!!!!!!! 1231 1232 } else featureNotImplemented(); 1233 1234 return null; 1235 } 1236 } 1237 1238 /++ 1239 The flagship window class. 1240 1241 1242 SimpleWindow tries to make ordinary windows very easy to create and use without locking you 1243 out of more advanced or complex features of the underlying windowing system. 1244 1245 For many applications, you can simply call `new SimpleWindow(some_width, some_height, "some title")` 1246 and get a suitable window to work with. 1247 1248 From there, you can opt into additional features, like custom resizability and OpenGL support 1249 with the next two constructor arguments. Or, if you need even more, you can set a window type 1250 and customization flags with the final two constructor arguments. 1251 1252 If none of that works for you, you can also create a window using native function calls, then 1253 wrap the window in a SimpleWindow instance by calling `new SimpleWindow(native_handle)`. Remember, 1254 though, if you do this, managing the window is still your own responsibility! Notably, you 1255 will need to destroy it yourself. 1256 +/ 1257 class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { 1258 1259 /// Be warned: this can be a very slow operation 1260 /// FIXME NOT IMPLEMENTED 1261 TrueColorImage takeScreenshot() { 1262 version(Windows) 1263 return trueColorImageFromNativeHandle(impl.hwnd, width, height); 1264 else version(OSXCocoa) 1265 throw new NotYetImplementedException(); 1266 else 1267 return trueColorImageFromNativeHandle(impl.window, width, height); 1268 } 1269 1270 version(X11) { 1271 void recreateAfterDisconnect() { 1272 if(!stateDiscarded) return; 1273 1274 if(_parent !is null && _parent.stateDiscarded) 1275 _parent.recreateAfterDisconnect(); 1276 1277 bool wasHidden = hidden; 1278 1279 activeScreenPainter = null; // should already be done but just to confirm 1280 1281 impl.createWindow(_width, _height, _title, openglMode, _parent); 1282 1283 if(recreateAdditionalConnectionState) 1284 recreateAdditionalConnectionState(); 1285 1286 hidden = wasHidden; 1287 stateDiscarded = false; 1288 } 1289 1290 bool stateDiscarded; 1291 void discardConnectionState() { 1292 if(XDisplayConnection.display) 1293 impl.dispose(); // if display is already null, it is hopeless to try to destroy stuff on it anyway 1294 if(discardAdditionalConnectionState) 1295 discardAdditionalConnectionState(); 1296 stateDiscarded = true; 1297 } 1298 1299 void delegate() discardAdditionalConnectionState; 1300 void delegate() recreateAdditionalConnectionState; 1301 } 1302 1303 1304 SimpleWindow _parent; 1305 bool beingOpenKeepsAppOpen = true; 1306 /++ 1307 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. 1308 1309 The constructor tries to have sane default arguments, so for many cases, you only need to provide a few of them. 1310 1311 Params: 1312 1313 width = the width of the window's client area, in pixels 1314 height = the height of the window's client area, in pixels 1315 title = the title of the window (seen in the title bar, taskbar, etc.). You can change it after construction with the [SimpleWindow.title\ property. 1316 opengl = [OpenGlOptions] are yes and no. If yes, it creates an OpenGL context on the window. 1317 resizable = [Resizability] has three options: 1318 $(P `allowResizing`, which allows the window to be resized by the user. The `windowResized` delegate will be called when the size is changed.) 1319 $(P `fixedSize` will not allow the user to resize the window.) 1320 $(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.) 1321 windowType = The type of window you want to make. 1322 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. 1323 parent = the parent window, if applicable 1324 +/ 1325 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) { 1326 this._width = width; 1327 this._height = height; 1328 this.openglMode = opengl; 1329 this.resizability = resizable; 1330 this.windowType = windowType; 1331 this.customizationFlags = customizationFlags; 1332 this._title = (title is null ? "D Application" : title); 1333 this._parent = parent; 1334 impl.createWindow(width, height, this._title, opengl, parent); 1335 1336 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.nestedChild) 1337 beingOpenKeepsAppOpen = false; 1338 } 1339 1340 /// Same as above, except using the `Size` struct instead of separate width and height. 1341 this(Size size, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible) { 1342 this(size.width, size.height, title, opengl, resizable); 1343 } 1344 1345 1346 /++ 1347 Creates a window based on the given [Image]. It's client area 1348 width and height is equal to the image. (A window's client area 1349 is the drawable space inside; it excludes the title bar, etc.) 1350 1351 Windows based on images will not be resizable and do not use OpenGL. 1352 1353 It will draw the image in upon creation, but this will be overwritten 1354 upon any draws, including the initial window visible event. 1355 1356 You probably do not want to use this and it may be removed from 1357 the library eventually, or I might change it to be a "permanent" 1358 background image; one that is automatically drawn on it before any 1359 other drawing event. idk. 1360 +/ 1361 this(Image image, string title = null) { 1362 this(image.width, image.height, title); 1363 this.image = image; 1364 } 1365 1366 /++ 1367 Wraps a native window handle with very little additional processing - notably no destruction 1368 this is incomplete so don't use it for much right now. The purpose of this is to make native 1369 windows created through the low level API (so you can use platform-specific options and 1370 other details SimpleWindow does not expose) available to the event loop wrappers. 1371 +/ 1372 this(NativeWindowHandle nativeWindow) { 1373 version(Windows) 1374 impl.hwnd = nativeWindow; 1375 else version(X11) { 1376 impl.window = nativeWindow; 1377 display = XDisplayConnection.get(); // get initial display to not segfault 1378 } else version(OSXCocoa) 1379 throw new NotYetImplementedException(); 1380 else featureNotImplemented(); 1381 // FIXME: set the size correctly 1382 _width = 1; 1383 _height = 1; 1384 nativeMapping[nativeWindow] = this; 1385 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 1386 _suppressDestruction = true; // so it doesn't try to close 1387 } 1388 1389 /// Experimental, do not use yet 1390 /++ 1391 Grabs exclusive input from the user until you release it with 1392 [releaseInputGrab]. 1393 1394 1395 Note: it is extremely rude to do this without good reason. 1396 Reasons may include doing some kind of mouse drag operation 1397 or popping up a temporary menu that should get events and will 1398 be dismissed at ease by the user clicking away. 1399 1400 Params: 1401 keyboard = do you want to grab keyboard input? 1402 mouse = grab mouse input? 1403 confine = confine the mouse cursor to inside this window? 1404 +/ 1405 void grabInput(bool keyboard = true, bool mouse = true, bool confine = false) { 1406 static if(UsingSimpledisplayX11) { 1407 XSync(XDisplayConnection.get, 0); 1408 if(keyboard) 1409 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 1410 if(mouse) { 1411 if(auto res = XGrabPointer(XDisplayConnection.get, this.impl.window, false /* owner_events */, 1412 EventMask.PointerMotionMask // FIXME: not efficient 1413 | EventMask.ButtonPressMask 1414 | EventMask.ButtonReleaseMask 1415 /* event mask */, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync, confine ? this.impl.window : None, None, CurrentTime) 1416 ) 1417 { 1418 XSync(XDisplayConnection.get, 0); 1419 import core.stdc.stdio; 1420 printf("Grab input failed %d\n", res); 1421 //throw new Exception("Grab input failed"); 1422 } else { 1423 // cool 1424 } 1425 } 1426 1427 } else version(Windows) { 1428 // FIXME: keyboard? 1429 SetCapture(impl.hwnd); 1430 if(confine) { 1431 RECT rcClip; 1432 //RECT rcOldClip; 1433 //GetClipCursor(&rcOldClip); 1434 GetWindowRect(hwnd, &rcClip); 1435 ClipCursor(&rcClip); 1436 } 1437 } else version(OSXCocoa) { 1438 throw new NotYetImplementedException(); 1439 } else static assert(0); 1440 } 1441 1442 /++ 1443 Releases the grab acquired by [grabInput]. 1444 +/ 1445 void releaseInputGrab() { 1446 static if(UsingSimpledisplayX11) { 1447 XUngrabPointer(XDisplayConnection.get, CurrentTime); 1448 } else version(Windows) { 1449 ReleaseCapture(); 1450 ClipCursor(null); 1451 } else version(OSXCocoa) { 1452 throw new NotYetImplementedException(); 1453 } else static assert(0); 1454 } 1455 1456 /++ 1457 Sets the input focus to this window. 1458 1459 You shouldn't call this very often - please let the user control the input focus. 1460 +/ 1461 void focus() { 1462 static if(UsingSimpledisplayX11) { 1463 XSetInputFocus(XDisplayConnection.get, this.impl.window, RevertToParent, CurrentTime); 1464 } else version(Windows) { 1465 SetFocus(this.impl.hwnd); 1466 } else version(OSXCocoa) { 1467 throw new NotYetImplementedException(); 1468 } else static assert(0); 1469 } 1470 1471 /++ 1472 Requests attention from the user for this window. 1473 1474 1475 The typical result of this function is to change the color 1476 of the taskbar icon, though it may be tweaked on specific 1477 platforms. 1478 1479 It is meant to unobtrusively tell the user that something 1480 relevant to them happened in the background and they should 1481 check the window when they get a chance. Upon receiving the 1482 keyboard focus, the window will automatically return to its 1483 natural state. 1484 1485 If the window already has the keyboard focus, this function 1486 may do nothing, because the user is presumed to already be 1487 giving the window attention. 1488 1489 Implementation_note: 1490 1491 `requestAttention` uses the _NET_WM_STATE_DEMANDS_ATTENTION 1492 atom on X11 and the FlashWindow function on Windows. 1493 +/ 1494 void requestAttention() { 1495 if(_focused) 1496 return; 1497 1498 version(Windows) { 1499 FLASHWINFO info; 1500 info.cbSize = info.sizeof; 1501 info.hwnd = impl.hwnd; 1502 info.dwFlags = FLASHW_TRAY; 1503 info.uCount = 1; 1504 1505 FlashWindowEx(&info); 1506 1507 } else version(X11) { 1508 demandingAttention = true; 1509 demandAttention(this, true); 1510 } else version(OSXCocoa) { 1511 throw new NotYetImplementedException(); 1512 } else static assert(0); 1513 } 1514 1515 private bool _focused; 1516 1517 version(X11) private bool demandingAttention; 1518 1519 /// This will be called when WM wants to close your window (i.e. user clicked "close" icon, for example). 1520 /// You'll have to call `close()` manually if you set this delegate. 1521 void delegate () closeQuery; 1522 1523 /// This will be called when window visibility was changed. 1524 void delegate (bool becomesVisible) visibilityChanged; 1525 1526 /// This will be called when window becomes visible for the first time. 1527 /// You can do OpenGL initialization here. Note that in X11 you can't call 1528 /// [setAsCurrentOpenGlContext] right after window creation, or X11 may 1529 /// fail to send reparent and map events (hit that with proprietary NVidia drivers). 1530 private bool _visibleForTheFirstTimeCalled; 1531 void delegate () visibleForTheFirstTime; 1532 1533 /// Returns true if the window has been closed. 1534 final @property bool closed() { return _closed; } 1535 1536 /// Returns true if the window is focused. 1537 final @property bool focused() { return _focused; } 1538 1539 private bool _visible; 1540 /// Returns true if the window is visible (mapped). 1541 final @property bool visible() { return _visible; } 1542 1543 /// Closes the window. If there are no more open windows, the event loop will terminate. 1544 void close() { 1545 if (!_closed) { 1546 if (onClosing !is null) onClosing(); 1547 impl.closeWindow(); 1548 _closed = true; 1549 } 1550 } 1551 1552 /// Alias for `hidden = false` 1553 void show() { 1554 hidden = false; 1555 } 1556 1557 /// Alias for `hidden = true` 1558 void hide() { 1559 hidden = true; 1560 } 1561 1562 /// Hide cursor when it enters the window. 1563 void hideCursor() { 1564 version(OSXCocoa) throw new NotYetImplementedException(); else 1565 if (!_closed) impl.hideCursor(); 1566 } 1567 1568 /// Don't hide cursor when it enters the window. 1569 void showCursor() { 1570 version(OSXCocoa) throw new NotYetImplementedException(); else 1571 if (!_closed) impl.showCursor(); 1572 } 1573 1574 /** "Warp" mouse pointer to coordinates relative to window top-left corner. Return "success" flag. 1575 * 1576 * Currently only supported on X11, so Windows implementation will return `false`. 1577 * 1578 * Note: "warping" pointer will not send any synthesised mouse events, so you probably doesn't want 1579 * to use it to move mouse pointer to some active GUI area, for example, as your window won't 1580 * receive "mouse moved here" event. 1581 */ 1582 bool warpMouse (int x, int y) { 1583 version(X11) { 1584 if (!_closed) { impl.warpMouse(x, y); return true; } 1585 } 1586 return false; 1587 } 1588 1589 /// Send dummy window event to ping event loop. Required to process NotificationIcon on X11, for example. 1590 void sendDummyEvent () { 1591 version(X11) { 1592 if (!_closed) { impl.sendDummyEvent(); } 1593 } 1594 } 1595 1596 /// Set window minimal size. 1597 void setMinSize (int minwidth, int minheight) { 1598 version(OSXCocoa) throw new NotYetImplementedException(); else 1599 if (!_closed) impl.setMinSize(minwidth, minheight); 1600 } 1601 1602 /// Set window maximal size. 1603 void setMaxSize (int maxwidth, int maxheight) { 1604 version(OSXCocoa) throw new NotYetImplementedException(); else 1605 if (!_closed) impl.setMaxSize(maxwidth, maxheight); 1606 } 1607 1608 /// Set window resize step (window size will be changed with the given granularity on supported platforms). 1609 /// Currently only supported on X11. 1610 void setResizeGranularity (int granx, int grany) { 1611 version(OSXCocoa) throw new NotYetImplementedException(); else 1612 if (!_closed) impl.setResizeGranularity(granx, grany); 1613 } 1614 1615 /// Move window. 1616 void move(int x, int y) { 1617 version(OSXCocoa) throw new NotYetImplementedException(); else 1618 if (!_closed) impl.move(x, y); 1619 } 1620 1621 /// ditto 1622 void move(Point p) { 1623 version(OSXCocoa) throw new NotYetImplementedException(); else 1624 if (!_closed) impl.move(p.x, p.y); 1625 } 1626 1627 /++ 1628 Resize window. 1629 1630 Note that the width and height of the window are NOT instantly 1631 updated - it waits for the window manager to approve the resize 1632 request, which means you must return to the event loop before the 1633 width and height are actually changed. 1634 +/ 1635 void resize(int w, int h) { 1636 version(OSXCocoa) throw new NotYetImplementedException(); else 1637 if (!_closed) impl.resize(w, h); 1638 } 1639 1640 /// Move and resize window (this can be faster and more visually pleasant than doing it separately). 1641 void moveResize (int x, int y, int w, int h) { 1642 version(OSXCocoa) throw new NotYetImplementedException(); else 1643 if (!_closed) impl.moveResize(x, y, w, h); 1644 } 1645 1646 private bool _hidden; 1647 1648 /// Returns true if the window is hidden. 1649 final @property bool hidden() { 1650 return _hidden; 1651 } 1652 1653 /// Shows or hides the window based on the bool argument. 1654 final @property void hidden(bool b) { 1655 _hidden = b; 1656 version(Windows) { 1657 ShowWindow(impl.hwnd, b ? SW_HIDE : SW_SHOW); 1658 } else version(X11) { 1659 if(b) 1660 //XUnmapWindow(impl.display, impl.window); 1661 XWithdrawWindow(impl.display, impl.window, DefaultScreen(impl.display)); 1662 else 1663 XMapWindow(impl.display, impl.window); 1664 } else version(OSXCocoa) { 1665 throw new NotYetImplementedException(); 1666 } else static assert(0); 1667 } 1668 1669 /// Sets the window opacity. On X11 this requires a compositor to be running. On windows the WindowFlags.extraComposite must be set at window creation. 1670 void opacity(double opacity) @property 1671 in { 1672 assert(opacity >= 0 && opacity <= 1); 1673 } body { 1674 version (Windows) { 1675 impl.setOpacity(cast(ubyte)(255 * opacity)); 1676 } else version (X11) { 1677 impl.setOpacity(cast(uint)(uint.max * opacity)); 1678 } else throw new NotYetImplementedException(); 1679 } 1680 1681 /++ 1682 Sets your event handlers, without entering the event loop. Useful if you 1683 have multiple windows - set the handlers on each window, then only do eventLoop on your main window. 1684 +/ 1685 void setEventHandlers(T...)(T eventHandlers) { 1686 // FIXME: add more events 1687 foreach(handler; eventHandlers) { 1688 static if(__traits(compiles, handleKeyEvent = handler)) { 1689 handleKeyEvent = handler; 1690 } else static if(__traits(compiles, handleCharEvent = handler)) { 1691 handleCharEvent = handler; 1692 } else static if(__traits(compiles, handlePulse = handler)) { 1693 handlePulse = handler; 1694 } else static if(__traits(compiles, handleMouseEvent = handler)) { 1695 handleMouseEvent = handler; 1696 } else static assert(0, "I can't use this event handler " ~ typeof(handler).stringof ~ "\nHave you tried using the delegate keyword?"); 1697 } 1698 } 1699 1700 /// The event loop automatically returns when the window is closed 1701 /// pulseTimeout is given in milliseconds. If pulseTimeout == 0, no 1702 /// pulse timer is created. The event loop will block until an event 1703 /// arrives or the pulse timer goes off. 1704 final int eventLoop(T...)( 1705 long pulseTimeout, /// set to zero if you don't want a pulse. 1706 T eventHandlers) /// delegate list like std.concurrency.receive 1707 { 1708 setEventHandlers(eventHandlers); 1709 1710 version(with_eventloop) { 1711 // delegates event loop to my other module 1712 version(X11) 1713 XFlush(display); 1714 1715 import arsd.eventloop; 1716 auto handle = setInterval(handlePulse, cast(int) pulseTimeout); 1717 scope(exit) clearInterval(handle); 1718 1719 loop(); 1720 return 0; 1721 } else version(OSXCocoa) { 1722 // FIXME 1723 if (handlePulse !is null && pulseTimeout != 0) { 1724 timer = scheduledTimer(pulseTimeout*1e-3, 1725 view, sel_registerName("simpledisplay_pulse"), 1726 null, true); 1727 } 1728 1729 setNeedsDisplay(view, true); 1730 run(NSApp); 1731 return 0; 1732 } else { 1733 EventLoop el = EventLoop(pulseTimeout, handlePulse); 1734 return el.run(); 1735 } 1736 } 1737 1738 /++ 1739 This lets you draw on the window (or its backing buffer) using basic 1740 2D primitives. 1741 1742 Be sure to call this in a limited scope because your changes will not 1743 actually appear on the window until ScreenPainter's destructor runs. 1744 1745 Returns: an instance of [ScreenPainter], which has the drawing methods 1746 on it to draw on this window. 1747 +/ 1748 ScreenPainter draw() { 1749 return impl.getPainter(); 1750 } 1751 1752 // This is here to implement the interface we use for various native handlers. 1753 NativeEventHandler getNativeEventHandler() { return handleNativeEvent; } 1754 1755 // maps native window handles to SimpleWindow instances, if there are any 1756 // you shouldn't need this, but it is public in case you do in a native event handler or something 1757 public __gshared SimpleWindow[NativeWindowHandle] nativeMapping; 1758 1759 /// Width of the window's drawable client area, in pixels. 1760 @scriptable 1761 final @property int width() { return _width; } 1762 1763 /// Height of the window's drawable client area, in pixels. 1764 @scriptable 1765 final @property int height() { return _height; } 1766 1767 private int _width; 1768 private int _height; 1769 1770 // HACK: making the best of some copy constructor woes with refcounting 1771 private ScreenPainterImplementation* activeScreenPainter_; 1772 1773 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 1774 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 1775 1776 private OpenGlOptions openglMode; 1777 private Resizability resizability; 1778 private WindowTypes windowType; 1779 private int customizationFlags; 1780 1781 /// `true` if OpenGL was initialized for this window. 1782 @property bool isOpenGL () const pure nothrow @safe @nogc { 1783 version(without_opengl) 1784 return false; 1785 else 1786 return (openglMode == OpenGlOptions.yes); 1787 } 1788 @property Resizability resizingMode () const pure nothrow @safe @nogc { return resizability; } /// Original resizability. 1789 @property WindowTypes type () const pure nothrow @safe @nogc { return windowType; } /// Original window type. 1790 @property int customFlags () const pure nothrow @safe @nogc { return customizationFlags; } /// Original customization flags. 1791 1792 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 1793 /// to call this, as it's not recommended to share window between threads. 1794 void mtLock () { 1795 version(X11) { 1796 XLockDisplay(this.display); 1797 } 1798 } 1799 1800 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 1801 /// to call this, as it's not recommended to share window between threads. 1802 void mtUnlock () { 1803 version(X11) { 1804 XUnlockDisplay(this.display); 1805 } 1806 } 1807 1808 /// Emit a beep to get user's attention. 1809 void beep () { 1810 version(X11) { 1811 XBell(this.display, 100); 1812 } else version(Windows) { 1813 MessageBeep(0xFFFFFFFF); 1814 } 1815 } 1816 1817 1818 1819 version(without_opengl) {} else { 1820 1821 /// 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`. 1822 void delegate() redrawOpenGlScene; 1823 1824 /// This will allow you to change OpenGL vsync state. 1825 final @property void vsync (bool wait) { 1826 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 1827 version(X11) { 1828 setAsCurrentOpenGlContext(); 1829 glxSetVSync(display, impl.window, wait); 1830 } 1831 } 1832 1833 /// Set this to `false` if you don't need to do `glFinish()` after `swapOpenGlBuffers()`. 1834 /// Note that at least NVidia proprietary driver may segfault if you will modify texture fast 1835 /// enough without waiting 'em to finish their frame bussiness. 1836 bool useGLFinish = true; 1837 1838 // FIXME: it should schedule it for the end of the current iteration of the event loop... 1839 /// 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. 1840 void redrawOpenGlSceneNow() { 1841 version(X11) if (!this._visible) return; // no need to do this if window is invisible 1842 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 1843 if(redrawOpenGlScene is null) 1844 return; 1845 1846 this.mtLock(); 1847 scope(exit) this.mtUnlock(); 1848 1849 this.setAsCurrentOpenGlContext(); 1850 1851 redrawOpenGlScene(); 1852 1853 this.swapOpenGlBuffers(); 1854 // 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. 1855 if (useGLFinish) glFinish(); 1856 } 1857 1858 1859 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 1860 void setAsCurrentOpenGlContext() { 1861 assert(openglMode == OpenGlOptions.yes); 1862 version(X11) { 1863 if(glXMakeCurrent(display, impl.window, impl.glc) == 0) 1864 throw new Exception("glXMakeCurrent"); 1865 } else version(Windows) { 1866 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 1867 if (!wglMakeCurrent(ghDC, ghRC)) 1868 throw new Exception("wglMakeCurrent"); // let windows users suffer too 1869 } 1870 } 1871 1872 /// Makes all gl* functions target this window until changed. This is only valid if you passed `OpenGlOptions.yes` to the constructor. 1873 /// This doesn't throw, returning success flag instead. 1874 bool setAsCurrentOpenGlContextNT() nothrow { 1875 assert(openglMode == OpenGlOptions.yes); 1876 version(X11) { 1877 return (glXMakeCurrent(display, impl.window, impl.glc) != 0); 1878 } else version(Windows) { 1879 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 1880 return wglMakeCurrent(ghDC, ghRC) ? true : false; 1881 } 1882 } 1883 1884 /// 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. 1885 /// This doesn't throw, returning success flag instead. 1886 bool releaseCurrentOpenGlContext() nothrow { 1887 assert(openglMode == OpenGlOptions.yes); 1888 version(X11) { 1889 return (glXMakeCurrent(display, 0, null) != 0); 1890 } else version(Windows) { 1891 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 1892 return wglMakeCurrent(ghDC, null) ? true : false; 1893 } 1894 } 1895 1896 /++ 1897 simpledisplay always uses double buffering, usually automatically. This 1898 manually swaps the OpenGL buffers. 1899 1900 1901 You should not need to call this yourself because simpledisplay will do it 1902 for you after calling your `redrawOpenGlScene`. 1903 1904 Remember that this may throw an exception, which you can catch in a multithreaded 1905 application to keep your thread from dying from an unhandled exception. 1906 +/ 1907 void swapOpenGlBuffers() { 1908 assert(openglMode == OpenGlOptions.yes); 1909 version(X11) { 1910 if (!this._visible) return; // no need to do this if window is invisible 1911 if (this._closed) return; // window may be closed, but timer is still firing; avoid GLXBadDrawable error 1912 glXSwapBuffers(display, impl.window); 1913 } else version(Windows) { 1914 SwapBuffers(ghDC); 1915 } 1916 } 1917 } 1918 1919 /++ 1920 Set the window title, which is visible on the window manager title bar, operating system taskbar, etc. 1921 1922 1923 --- 1924 auto window = new SimpleWindow(100, 100, "First title"); 1925 window.title = "A new title"; 1926 --- 1927 1928 You may call this function at any time. 1929 +/ 1930 @property void title(string title) { 1931 _title = title; 1932 version(OSXCocoa) throw new NotYetImplementedException(); else 1933 impl.setTitle(title); 1934 } 1935 1936 private string _title; 1937 1938 /// Gets the title 1939 @property string title() { 1940 if(_title is null) 1941 _title = getRealTitle(); 1942 return _title; 1943 } 1944 1945 /++ 1946 Get the title as set by the window manager. 1947 May not match what you attempted to set. 1948 +/ 1949 string getRealTitle() { 1950 static if(is(typeof(impl.getTitle()))) 1951 return impl.getTitle(); 1952 else 1953 return null; 1954 } 1955 1956 /// Set the icon that is seen in the title bar or taskbar, etc., for the user. 1957 @property void icon(MemoryImage icon) { 1958 auto tci = icon.getAsTrueColorImage(); 1959 version(Windows) { 1960 winIcon = new WindowsIcon(icon); 1961 SendMessageA(impl.hwnd, 0x0080 /*WM_SETICON*/, 0 /*ICON_SMALL*/, cast(LPARAM) winIcon.hIcon); // there is also 1 == ICON_BIG 1962 } else version(X11) { 1963 // FIXME: ensure this is correct 1964 auto display = XDisplayConnection.get; 1965 arch_ulong[] buffer; 1966 buffer ~= icon.width; 1967 buffer ~= icon.height; 1968 foreach(c; tci.imageData.colors) { 1969 arch_ulong b; 1970 b |= c.a << 24; 1971 b |= c.r << 16; 1972 b |= c.g << 8; 1973 b |= c.b; 1974 buffer ~= b; 1975 } 1976 1977 XChangeProperty( 1978 display, 1979 impl.window, 1980 GetAtom!"_NET_WM_ICON"(display), 1981 GetAtom!"CARDINAL"(display), 1982 32 /* bits */, 1983 0 /*PropModeReplace*/, 1984 buffer.ptr, 1985 cast(int) buffer.length); 1986 } else version(OSXCocoa) { 1987 throw new NotYetImplementedException(); 1988 } else static assert(0); 1989 } 1990 1991 version(Windows) 1992 private WindowsIcon winIcon; 1993 1994 bool _suppressDestruction; 1995 1996 ~this() { 1997 if(_suppressDestruction) 1998 return; 1999 impl.dispose(); 2000 } 2001 2002 private bool _closed; 2003 2004 // the idea here is to draw something temporary on top of the main picture e.g. a blinking cursor 2005 /* 2006 ScreenPainter drawTransiently() { 2007 return impl.getPainter(); 2008 } 2009 */ 2010 2011 /// Draws an image on the window. This is meant to provide quick look 2012 /// of a static image generated elsewhere. 2013 @property void image(Image i) { 2014 version(Windows) { 2015 BITMAP bm; 2016 HDC hdc = GetDC(hwnd); 2017 HDC hdcMem = CreateCompatibleDC(hdc); 2018 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 2019 2020 GetObject(i.handle, bm.sizeof, &bm); 2021 2022 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 2023 2024 SelectObject(hdcMem, hbmOld); 2025 DeleteDC(hdcMem); 2026 DeleteDC(hwnd); 2027 2028 /* 2029 RECT r; 2030 r.right = i.width; 2031 r.bottom = i.height; 2032 InvalidateRect(hwnd, &r, false); 2033 */ 2034 } else 2035 version(X11) { 2036 if(!destroyed) { 2037 if(i.usingXshm) 2038 XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false); 2039 else 2040 XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height); 2041 } 2042 } else 2043 version(OSXCocoa) { 2044 draw().drawImage(Point(0, 0), i); 2045 setNeedsDisplay(view, true); 2046 } else static assert(0); 2047 } 2048 2049 /++ 2050 Changes the cursor for the window. If the cursor is hidden via [hideCursor], this has no effect. 2051 2052 --- 2053 window.cursor = GenericCursor.Help; 2054 // now the window mouse cursor is set to a generic help 2055 --- 2056 2057 +/ 2058 @property void cursor(MouseCursor cursor) { 2059 version(OSXCocoa) 2060 featureNotImplemented(); 2061 else 2062 if(this.impl.curHidden <= 0) { 2063 static if(UsingSimpledisplayX11) { 2064 auto ch = cursor.cursorHandle; 2065 XDefineCursor(XDisplayConnection.get(), this.impl.window, ch); 2066 } else version(Windows) { 2067 auto ch = cursor.cursorHandle; 2068 impl.currentCursor = ch; 2069 SetCursor(ch); // redraw without waiting for mouse movement to update 2070 } else featureNotImplemented(); 2071 } 2072 2073 } 2074 2075 /// What follows are the event handlers. These are set automatically 2076 /// by the eventLoop function, but are still public so you can change 2077 /// them later. wasPressed == true means key down. false == key up. 2078 2079 /// Handles a low-level keyboard event. Settable through setEventHandlers. 2080 void delegate(KeyEvent ke) handleKeyEvent; 2081 2082 /// Handles a higher level keyboard event - c is the character just pressed. Settable through setEventHandlers. 2083 void delegate(dchar c) handleCharEvent; 2084 2085 /// Handles a timer pulse. Settable through setEventHandlers. 2086 void delegate() handlePulse; 2087 2088 /// Called when the focus changes, param is if we have it (true) or are losing it (false). 2089 void delegate(bool) onFocusChange; 2090 2091 /** Called inside `close()` method. Our window is still alive, and we can free various resources. 2092 * Sometimes it is easier to setup the delegate instead of subclassing. */ 2093 void delegate() onClosing; 2094 2095 /** Called when we received destroy notification. At this stage we cannot do much with our window 2096 * (as it is already dead, and it's native handle cannot be used), but we still can do some 2097 * last minute cleanup. */ 2098 void delegate() onDestroyed; 2099 2100 static if (UsingSimpledisplayX11) 2101 /** Called when Expose event comes. See Xlib manual to understand the arguments. 2102 * Return `false` if you want Simpledisplay to copy backbuffer, or `true` if you did it yourself. 2103 * You will probably never need to setup this handler, it is for very low-level stuff. 2104 * 2105 * WARNING! Xlib is multithread-locked when this handles is called! */ 2106 bool delegate(int x, int y, int width, int height, int eventsLeft) handleExpose; 2107 2108 //version(Windows) 2109 //bool delegate(WPARAM wParam, LPARAM lParam) handleWM_PAINT; 2110 2111 private { 2112 int lastMouseX = int.min; 2113 int lastMouseY = int.min; 2114 void mdx(ref MouseEvent ev) { 2115 if(lastMouseX == int.min || lastMouseY == int.min) { 2116 ev.dx = 0; 2117 ev.dy = 0; 2118 } else { 2119 ev.dx = ev.x - lastMouseX; 2120 ev.dy = ev.y - lastMouseY; 2121 } 2122 2123 lastMouseX = ev.x; 2124 lastMouseY = ev.y; 2125 } 2126 } 2127 2128 /// Mouse event handler. Settable through setEventHandlers. 2129 void delegate(MouseEvent) handleMouseEvent; 2130 2131 /// use to redraw child widgets if you use system apis to add stuff 2132 void delegate() paintingFinished; 2133 2134 void delegate() paintingFinishedDg() { 2135 return paintingFinished; 2136 } 2137 2138 /// handle a resize, after it happens. You must construct the window with Resizability.allowResizing 2139 /// for this to ever happen. 2140 void delegate(int width, int height) windowResized; 2141 2142 /** Platform specific - handle any native messages this window gets. 2143 * 2144 * Note: this is called *in addition to* other event handlers, unless you return zero indicating that you handled it. 2145 2146 * On Windows, it takes the form of int delegate(HWND,UINT, WPARAM, LPARAM). 2147 2148 * On X11, it takes the form of int delegate(XEvent). 2149 2150 * IMPORTANT: it used to be static in old versions of simpledisplay.d, but I always used 2151 * it as if it wasn't static... so now I just fixed it so it isn't anymore. 2152 **/ 2153 NativeEventHandler handleNativeEvent; 2154 2155 /// This is the same as handleNativeEvent, but static so it can hook ALL events in the loop. 2156 /// If you used to use handleNativeEvent depending on it being static, just change it to use 2157 /// this instead and it will work the same way. 2158 __gshared NativeEventHandler handleNativeGlobalEvent; 2159 2160 // private: 2161 /// The native implementation is available, but you shouldn't use it unless you are 2162 /// familiar with the underlying operating system, don't mind depending on it, and 2163 /// know simpledisplay.d's internals too. It is virtually private; you can hopefully 2164 /// do what you need to do with handleNativeEvent instead. 2165 /// 2166 /// This is likely to eventually change to be just a struct holding platform-specific 2167 /// handles instead of a template mixin at some point because I'm not happy with the 2168 /// code duplication here (ironically). 2169 mixin NativeSimpleWindowImplementation!() impl; 2170 2171 /** 2172 This is in-process one-way (from anything to window) event sending mechanics. 2173 It is thread-safe, so it can be used in multi-threaded applications to send, 2174 for example, "wake up and repaint" events when thread completed some operation. 2175 This will allow to avoid using timer pulse to check events with synchronization, 2176 'cause event handler will be called in UI thread. You can stop guessing which 2177 pulse frequency will be enough for your app. 2178 Note that events handlers may be called in arbitrary order, i.e. last registered 2179 handler can be called first, and vice versa. 2180 */ 2181 public: 2182 /** Is our custom event queue empty? Can be used in simple cases to prevent 2183 * "spamming" window with events it can't cope with. 2184 * It is safe to call this from non-UI threads. 2185 */ 2186 @property bool eventQueueEmpty() () { 2187 synchronized(this) { 2188 foreach (const ref o; eventQueue[0..eventQueueUsed]) if (!o.doProcess) return false; 2189 } 2190 return true; 2191 } 2192 2193 /** Does our custom event queue contains at least one with the given type? 2194 * Can be used in simple cases to prevent "spamming" window with events 2195 * it can't cope with. 2196 * It is safe to call this from non-UI threads. 2197 */ 2198 @property bool eventQueued(ET:Object) () { 2199 synchronized(this) { 2200 foreach (const ref o; eventQueue[0..eventQueueUsed]) { 2201 if (!o.doProcess) { 2202 if (cast(ET)(o.evt)) return true; 2203 } 2204 } 2205 } 2206 return false; 2207 } 2208 2209 /** Add listener for custom event. Can be used like this: 2210 * 2211 * --------------------- 2212 * auto eid = win.addEventListener((MyStruct evt) { ... }); 2213 * ... 2214 * win.removeEventListener(eid); 2215 * --------------------- 2216 * 2217 * Returns: 0 on failure (should never happen, so ignore it) 2218 * 2219 * $(WARNING Don't use this method in object destructors!) 2220 * 2221 * $(WARNING It is better to register all event handlers and don't remove 'em, 2222 * 'cause if event handler id counter will overflow, you won't be able 2223 * to register any more events.) 2224 */ 2225 uint addEventListener(ET:Object) (void delegate (ET) dg) { 2226 if (dg is null) return 0; // ignore empty handlers 2227 synchronized(this) { 2228 //FIXME: abort on overflow? 2229 if (++lastUsedHandlerId == 0) { --lastUsedHandlerId; return 0; } // alas, can't register more events. at all. 2230 EventHandlerEntry e; 2231 e.dg = delegate (Object o) { 2232 if (auto co = cast(ET)o) { 2233 try { 2234 dg(co); 2235 } catch (Exception) { 2236 // sorry! 2237 } 2238 return true; 2239 } 2240 return false; 2241 }; 2242 e.id = lastUsedHandlerId; 2243 auto optr = eventHandlers.ptr; 2244 eventHandlers ~= e; 2245 if (eventHandlers.ptr !is optr) { 2246 import core.memory : GC; 2247 if (eventHandlers.ptr is GC.addrOf(eventHandlers.ptr)) GC.setAttr(eventHandlers.ptr, GC.BlkAttr.NO_INTERIOR); 2248 } 2249 return lastUsedHandlerId; 2250 } 2251 } 2252 2253 /// Remove event listener. It is safe to pass invalid event id here. 2254 /// $(WARNING Don't use this method in object destructors!) 2255 void removeEventListener() (uint id) { 2256 if (id == 0 || id > lastUsedHandlerId) return; 2257 synchronized(this) { 2258 foreach (immutable idx; 0..eventHandlers.length) { 2259 if (eventHandlers[idx].id == id) { 2260 foreach (immutable c; idx+1..eventHandlers.length) eventHandlers[c-1] = eventHandlers[c]; 2261 eventHandlers[$-1].dg = null; 2262 eventHandlers.length -= 1; 2263 eventHandlers.assumeSafeAppend; 2264 return; 2265 } 2266 } 2267 } 2268 } 2269 2270 /// Post event to queue. It is safe to call this from non-UI threads. 2271 /// If `timeoutmsecs` is greater than zero, the event will be delayed for at least `timeoutmsecs` milliseconds. 2272 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 2273 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 2274 bool postTimeout(ET:Object) (ET evt, uint timeoutmsecs, bool replace=false) { 2275 if (this.closed) return false; // closed windows can't handle events 2276 2277 // remove all events of type `ET` 2278 void removeAllET () { 2279 uint eidx = 0, ec = eventQueueUsed; 2280 auto eptr = eventQueue.ptr; 2281 while (eidx < ec) { 2282 if (eptr.doProcess) { ++eidx; ++eptr; continue; } 2283 if (cast(ET)eptr.evt !is null) { 2284 // i found her! 2285 if (inCustomEventProcessor) { 2286 // if we're in custom event processing loop, processor will clear it for us 2287 eptr.evt = null; 2288 ++eidx; 2289 ++eptr; 2290 } else { 2291 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 2292 ec = --eventQueueUsed; 2293 // clear last event (it is already copied) 2294 eventQueue.ptr[ec].evt = null; 2295 } 2296 } else { 2297 ++eidx; 2298 ++eptr; 2299 } 2300 } 2301 } 2302 2303 if (evt is null) { 2304 if (replace) { synchronized(this) removeAllET(); } 2305 // ignore empty events, they can't be handled anyway 2306 return false; 2307 } 2308 2309 // add events even if no event FD/event object created yet 2310 synchronized(this) { 2311 if (replace) removeAllET(); 2312 if (eventQueueUsed == uint.max) return false; // just in case 2313 if (eventQueueUsed < eventQueue.length) { 2314 eventQueue[eventQueueUsed++] = QueuedEvent(evt, timeoutmsecs); 2315 } else { 2316 if (eventQueue.capacity == eventQueue.length) { 2317 // need to reallocate; do a trick to ensure that old array is cleared 2318 auto oarr = eventQueue; 2319 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 2320 // just in case, do yet another check 2321 if (oarr.length != 0 && oarr.ptr !is eventQueue.ptr) foreach (ref e; oarr[0..eventQueueUsed]) e.evt = null; 2322 import core.memory : GC; 2323 if (eventQueue.ptr is GC.addrOf(eventQueue.ptr)) GC.setAttr(eventQueue.ptr, GC.BlkAttr.NO_INTERIOR); 2324 } else { 2325 auto optr = eventQueue.ptr; 2326 eventQueue ~= QueuedEvent(evt, timeoutmsecs); 2327 assert(eventQueue.ptr is optr); 2328 } 2329 ++eventQueueUsed; 2330 assert(eventQueueUsed == eventQueue.length); 2331 } 2332 if (!eventWakeUp()) { 2333 // can't wake up event processor, so there is no reason to keep the event 2334 assert(eventQueueUsed > 0); 2335 eventQueue[--eventQueueUsed].evt = null; 2336 return false; 2337 } 2338 return true; 2339 } 2340 } 2341 2342 /// Post event to queue. It is safe to call this from non-UI threads. 2343 /// if `replace` is `true`, replace all existing events typed `ET` with the new one (if `evt` is empty, remove 'em all) 2344 /// Returns `true` if event was queued. Always returns `false` if `evt` is null. 2345 bool postEvent(ET:Object) (ET evt, bool replace=false) { 2346 return postTimeout!ET(evt, 0, replace); 2347 } 2348 2349 private: 2350 private import core.time : MonoTime; 2351 2352 version(X11) { 2353 __gshared int customEventFD = -1; 2354 __gshared int customSignalFD = -1; 2355 } else version(Windows) { 2356 __gshared HANDLE customEventH = null; 2357 } 2358 2359 // wake up event processor 2360 bool eventWakeUp () { 2361 version(X11) { 2362 import core.sys.posix.unistd : write; 2363 ulong n = 1; 2364 if (customEventFD >= 0) write(customEventFD, &n, n.sizeof); 2365 return true; 2366 } else version(Windows) { 2367 if (customEventH !is null) SetEvent(customEventH); 2368 return true; 2369 } else { 2370 // not implemented for other OSes 2371 return false; 2372 } 2373 } 2374 2375 static struct QueuedEvent { 2376 Object evt; 2377 bool timed = false; 2378 MonoTime hittime = MonoTime.zero; 2379 bool doProcess = false; // process event at the current iteration (internal flag) 2380 2381 this (Object aevt, uint toutmsecs) { 2382 evt = aevt; 2383 if (toutmsecs > 0) { 2384 import core.time : msecs; 2385 timed = true; 2386 hittime = MonoTime.currTime+toutmsecs.msecs; 2387 } 2388 } 2389 } 2390 2391 alias CustomEventHandler = bool delegate (Object o) nothrow; 2392 static struct EventHandlerEntry { 2393 CustomEventHandler dg; 2394 uint id; 2395 } 2396 2397 uint lastUsedHandlerId; 2398 EventHandlerEntry[] eventHandlers; 2399 QueuedEvent[] eventQueue = null; 2400 uint eventQueueUsed = 0; // to avoid `.assumeSafeAppend` and length changes 2401 bool inCustomEventProcessor = false; // required to properly remove events 2402 2403 // process queued events and call custom event handlers 2404 // this will not process events posted from called handlers (such events are postponed for the next iteration) 2405 void processCustomEvents () { 2406 bool hasSomethingToDo = false; 2407 uint ecount; 2408 bool ocep; 2409 synchronized(this) { 2410 ocep = inCustomEventProcessor; 2411 inCustomEventProcessor = true; 2412 ecount = eventQueueUsed; // user may want to post new events from an event handler; process 'em on next iteration 2413 auto ctt = MonoTime.currTime; 2414 bool hasEmpty = false; 2415 // mark events to process (this is required for `eventQueued()`) 2416 foreach (ref qe; eventQueue[0..ecount]) { 2417 if (qe.evt is null) { hasEmpty = true; continue; } 2418 if (qe.timed) { 2419 qe.doProcess = (qe.hittime <= ctt); 2420 } else { 2421 qe.doProcess = true; 2422 } 2423 hasSomethingToDo = (hasSomethingToDo || qe.doProcess); 2424 } 2425 if (!hasSomethingToDo) { 2426 // remove empty events 2427 if (hasEmpty) { 2428 uint eidx = 0, ec = eventQueueUsed; 2429 auto eptr = eventQueue.ptr; 2430 while (eidx < ec) { 2431 if (eptr.evt is null) { 2432 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 2433 ec = --eventQueueUsed; 2434 eventQueue.ptr[ec].evt = null; // make GC life easier 2435 } else { 2436 ++eidx; 2437 ++eptr; 2438 } 2439 } 2440 } 2441 inCustomEventProcessor = ocep; 2442 return; 2443 } 2444 } 2445 // process marked events 2446 uint efree = 0; // non-processed events will be put at this index 2447 EventHandlerEntry[] eh; 2448 Object evt; 2449 foreach (immutable eidx; 0..ecount) { 2450 synchronized(this) { 2451 if (!eventQueue[eidx].doProcess) { 2452 // skip this event 2453 assert(efree <= eidx); 2454 if (efree != eidx) { 2455 // copy this event to queue start 2456 eventQueue[efree] = eventQueue[eidx]; 2457 eventQueue[eidx].evt = null; // just in case 2458 } 2459 ++efree; 2460 continue; 2461 } 2462 evt = eventQueue[eidx].evt; 2463 eventQueue[eidx].evt = null; // in case event handler will hit GC 2464 if (evt is null) continue; // just in case 2465 // try all handlers; this can be slow, but meh... 2466 eh = eventHandlers; 2467 } 2468 foreach (ref evhan; eh) if (evhan.dg !is null) evhan.dg(evt); 2469 evt = null; 2470 eh = null; 2471 } 2472 synchronized(this) { 2473 // move all unprocessed events to queue top; efree holds first "free index" 2474 foreach (immutable eidx; ecount..eventQueueUsed) { 2475 assert(efree <= eidx); 2476 if (efree != eidx) eventQueue[efree] = eventQueue[eidx]; 2477 ++efree; 2478 } 2479 eventQueueUsed = efree; 2480 // wake up event processor on next event loop iteration if we have more queued events 2481 // also, remove empty events 2482 bool awaken = false; 2483 uint eidx = 0, ec = eventQueueUsed; 2484 auto eptr = eventQueue.ptr; 2485 while (eidx < ec) { 2486 if (eptr.evt is null) { 2487 foreach (immutable c; eidx+1..ec) eventQueue.ptr[c-1] = eventQueue.ptr[c]; 2488 ec = --eventQueueUsed; 2489 eventQueue.ptr[ec].evt = null; // make GC life easier 2490 } else { 2491 if (!awaken && !eptr.timed) { eventWakeUp(); awaken = true; } 2492 ++eidx; 2493 ++eptr; 2494 } 2495 } 2496 inCustomEventProcessor = ocep; 2497 } 2498 } 2499 2500 // for all windows in nativeMapping 2501 static void processAllCustomEvents () { 2502 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 2503 if (sw is null || sw.closed) continue; 2504 sw.processCustomEvents(); 2505 } 2506 } 2507 2508 // 0: infinite (i.e. no scheduled events in queue) 2509 uint eventQueueTimeoutMSecs () { 2510 synchronized(this) { 2511 if (eventQueueUsed == 0) return 0; 2512 if (inCustomEventProcessor) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 2513 uint res = int.max; 2514 auto ctt = MonoTime.currTime; 2515 foreach (const ref qe; eventQueue[0..eventQueueUsed]) { 2516 if (qe.evt is null) assert(0, "WUTAFUUUUUUU..."); // the thing that should not be. ABSOLUTELY! (c) 2517 if (qe.doProcess) continue; // just in case 2518 if (!qe.timed) return 1; // minimal 2519 if (qe.hittime <= ctt) return 1; // minimal 2520 auto tms = (qe.hittime-ctt).total!"msecs"; 2521 if (tms < 1) tms = 1; // safety net 2522 if (tms >= int.max) tms = int.max-1; // and another safety net 2523 if (res > tms) res = cast(uint)tms; 2524 } 2525 return (res >= int.max ? 0 : res); 2526 } 2527 } 2528 2529 // for all windows in nativeMapping 2530 static uint eventAllQueueTimeoutMSecs () { 2531 uint res = uint.max; 2532 foreach (SimpleWindow sw; SimpleWindow.nativeMapping.byValue) { 2533 if (sw is null || sw.closed) continue; 2534 uint to = sw.eventQueueTimeoutMSecs(); 2535 if (to && to < res) { 2536 res = to; 2537 if (to == 1) break; // can't have less than this 2538 } 2539 } 2540 return (res >= int.max ? 0 : res); 2541 } 2542 } 2543 2544 2545 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing). 2546 /// See [GenericCursor] 2547 class MouseCursor { 2548 int osId; 2549 bool isStockCursor; 2550 private this(int osId) { 2551 this.osId = osId; 2552 this.isStockCursor = true; 2553 } 2554 2555 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx 2556 this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {} 2557 2558 version(Windows) { 2559 HCURSOR cursor_; 2560 HCURSOR cursorHandle() { 2561 if(cursor_ is null) 2562 cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId)); 2563 return cursor_; 2564 } 2565 2566 } else static if(UsingSimpledisplayX11) { 2567 Cursor cursor_ = None; 2568 int xDisplaySequence; 2569 2570 Cursor cursorHandle() { 2571 if(this.osId == None) 2572 return None; 2573 2574 // we need to reload if we on a new X connection 2575 if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) { 2576 cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId); 2577 xDisplaySequence = XDisplayConnection.connectionSequenceNumber; 2578 } 2579 return cursor_; 2580 } 2581 } 2582 } 2583 2584 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 2585 // https://tronche.com/gui/x/xlib/appendix/b/ 2586 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx 2587 /// 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 2588 enum GenericCursorType { 2589 Default, /// The default arrow pointer. 2590 Wait, /// A cursor indicating something is loading and the user must wait. 2591 Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser. 2592 Help, /// A cursor indicating the user can get help about the pointer location. 2593 Cross, /// A crosshair. 2594 Text, /// An i-beam shape, typically used to indicate text selection is possible. 2595 Move, /// Pointer indicating movement is possible. May also be used as SizeAll 2596 UpArrow, /// An arrow pointing straight up. 2597 Progress, /// The hourglass and arrow, indicating the computer is working but the user can still work. Not great results on X11. 2598 NotAllowed, /// Indicates the current operation is not allowed. Not great results on X11. 2599 SizeNesw, /// Arrow pointing northeast and southwest (lower-left corner resize indicator) 2600 SizeNs, /// Arrow pointing north and south (upper/lower edge resize indicator) 2601 SizeNwse, /// Arrow pointing northwest and southeast (upper-left corner resize indicator) 2602 SizeWe, /// Arrow pointing west and east (left/right edge resize indicator) 2603 2604 } 2605 2606 /* 2607 X_plus == css cell == Windows ? 2608 */ 2609 2610 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types. 2611 static struct GenericCursor { 2612 static: 2613 /// 2614 MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) { 2615 static MouseCursor mc; 2616 2617 auto type = __traits(getMember, GenericCursorType, str); 2618 2619 if(mc is null) { 2620 2621 version(Windows) { 2622 int osId; 2623 final switch(type) { 2624 case GenericCursorType.Default: osId = IDC_ARROW; break; 2625 case GenericCursorType.Wait: osId = IDC_WAIT; break; 2626 case GenericCursorType.Hand: osId = IDC_HAND; break; 2627 case GenericCursorType.Help: osId = IDC_HELP; break; 2628 case GenericCursorType.Cross: osId = IDC_CROSS; break; 2629 case GenericCursorType.Text: osId = IDC_IBEAM; break; 2630 case GenericCursorType.Move: osId = IDC_SIZEALL; break; 2631 case GenericCursorType.UpArrow: osId = IDC_UPARROW; break; 2632 case GenericCursorType.Progress: osId = IDC_APPSTARTING; break; 2633 case GenericCursorType.NotAllowed: osId = IDC_NO; break; 2634 case GenericCursorType.SizeNesw: osId = IDC_SIZENESW; break; 2635 case GenericCursorType.SizeNs: osId = IDC_SIZENS; break; 2636 case GenericCursorType.SizeNwse: osId = IDC_SIZENWSE; break; 2637 case GenericCursorType.SizeWe: osId = IDC_SIZEWE; break; 2638 } 2639 } else static if(UsingSimpledisplayX11) { 2640 int osId; 2641 final switch(type) { 2642 case GenericCursorType.Default: osId = None; break; 2643 case GenericCursorType.Wait: osId = 150 /* XC_watch */; break; 2644 case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break; 2645 case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break; 2646 case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break; 2647 case GenericCursorType.Text: osId = 152 /* XC_xterm */; break; 2648 case GenericCursorType.Move: osId = 52 /* XC_fleur */; break; 2649 case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break; 2650 case GenericCursorType.Progress: osId = 150 /* XC_watch, best i can do i think */; break; 2651 2652 case GenericCursorType.NotAllowed: osId = 24 /* XC_circle. not great */; break; 2653 case GenericCursorType.SizeNesw: osId = 12 /* XC_bottom_left_corner */ ; break; 2654 case GenericCursorType.SizeNs: osId = 116 /* XC_sb_v_double_arrow */; break; 2655 case GenericCursorType.SizeNwse: osId = 14 /* XC_bottom_right_corner */; break; 2656 case GenericCursorType.SizeWe: osId = 108 /* XC_sb_h_double_arrow */; break; 2657 } 2658 2659 } else featureNotImplemented(); 2660 2661 mc = new MouseCursor(osId); 2662 } 2663 return mc; 2664 } 2665 } 2666 2667 2668 /++ 2669 If you want to get more control over the event loop, you can use this. 2670 2671 Typically though, you can just call [SimpleWindow.eventLoop]. 2672 +/ 2673 struct EventLoop { 2674 @disable this(); 2675 2676 /// Gets a reference to an existing event loop 2677 static EventLoop get() { 2678 return EventLoop(0, null); 2679 } 2680 2681 /// Construct an application-global event loop for yourself 2682 /// See_Also: [SimpleWindow.setEventHandlers] 2683 this(long pulseTimeout, void delegate() handlePulse) { 2684 if(impl is null) 2685 impl = new EventLoopImpl(pulseTimeout, handlePulse); 2686 else { 2687 if(pulseTimeout) { 2688 impl.pulseTimeout = pulseTimeout; 2689 impl.handlePulse = handlePulse; 2690 } 2691 } 2692 impl.refcount++; 2693 } 2694 2695 ~this() { 2696 if(impl is null) 2697 return; 2698 impl.refcount--; 2699 if(impl.refcount == 0) 2700 impl.dispose(); 2701 2702 } 2703 2704 this(this) { 2705 if(impl is null) 2706 return; 2707 impl.refcount++; 2708 } 2709 2710 /// Runs the event loop until the whileCondition, if present, returns false 2711 int run(bool delegate() whileCondition = null) { 2712 assert(impl !is null); 2713 impl.notExited = true; 2714 return impl.run(whileCondition); 2715 } 2716 2717 /// Exits the event loop 2718 void exit() { 2719 assert(impl !is null); 2720 impl.notExited = false; 2721 } 2722 2723 version(linux) 2724 ref void delegate(int) signalHandler() { 2725 assert(impl !is null); 2726 return impl.signalHandler; 2727 } 2728 2729 static EventLoopImpl* impl; 2730 } 2731 2732 version(linux) 2733 void delegate(int, int) globalHupHandler; 2734 2735 version(linux) 2736 void makeNonBlocking(int fd) { 2737 import fcntl = core.sys.posix.fcntl; 2738 auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0); 2739 if(flags == -1) 2740 throw new Exception("fcntl get"); 2741 flags |= fcntl.O_NONBLOCK; 2742 auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags); 2743 if(s == -1) 2744 throw new Exception("fcntl set"); 2745 } 2746 2747 struct EventLoopImpl { 2748 int refcount; 2749 2750 bool notExited = true; 2751 2752 version(linux) { 2753 static import ep = core.sys.linux.epoll; 2754 static import unix = core.sys.posix.unistd; 2755 static import err = core.stdc.errno; 2756 import core.sys.linux.timerfd; 2757 2758 void delegate(int) signalHandler; 2759 } 2760 2761 version(X11) { 2762 int pulseFd = -1; 2763 version(linux) ep.epoll_event[16] events = void; 2764 } else version(Windows) { 2765 Timer pulser; 2766 HANDLE[] handles; 2767 } 2768 2769 2770 /// "Lock" this window handle, to do multithreaded synchronization. You probably won't need 2771 /// to call this, as it's not recommended to share window between threads. 2772 void mtLock () { 2773 version(X11) { 2774 XLockDisplay(this.display); 2775 } 2776 } 2777 2778 version(X11) 2779 auto display() { return XDisplayConnection.get; } 2780 2781 /// "Unlock" this window handle, to do multithreaded synchronization. You probably won't need 2782 /// to call this, as it's not recommended to share window between threads. 2783 void mtUnlock () { 2784 version(X11) { 2785 XUnlockDisplay(this.display); 2786 } 2787 } 2788 2789 version(with_eventloop) 2790 void initialize(long pulseTimeout) {} 2791 else 2792 void initialize(long pulseTimeout) { 2793 version(Windows) { 2794 if(pulseTimeout) 2795 pulser = new Timer(cast(int) pulseTimeout, handlePulse); 2796 2797 if (customEventH is null) { 2798 customEventH = CreateEvent(null, FALSE/*autoreset*/, FALSE/*initial state*/, null); 2799 if (customEventH !is null) { 2800 handles ~= customEventH; 2801 } else { 2802 // this is something that should not be; better be safe than sorry 2803 throw new Exception("can't create eventfd for custom event processing"); 2804 } 2805 } 2806 2807 SimpleWindow.processAllCustomEvents(); // process events added before event object creation 2808 } 2809 2810 version(linux) { 2811 prepareEventLoop(); 2812 { 2813 auto display = XDisplayConnection.get; 2814 // adding Xlib file 2815 ep.epoll_event ev = void; 2816 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 2817 ev.events = ep.EPOLLIN; 2818 ev.data.fd = display.fd; 2819 //import std.conv; 2820 if(ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, display.fd, &ev) == -1) 2821 throw new Exception("add x fd");// ~ to!string(epollFd)); 2822 displayFd = display.fd; 2823 } 2824 2825 if(pulseTimeout) { 2826 pulseFd = timerfd_create(CLOCK_MONOTONIC, 0); 2827 if(pulseFd == -1) 2828 throw new Exception("pulse timer create failed"); 2829 2830 itimerspec value; 2831 value.it_value.tv_sec = cast(int) (pulseTimeout / 1000); 2832 value.it_value.tv_nsec = (pulseTimeout % 1000) * 1000_000; 2833 2834 value.it_interval.tv_sec = cast(int) (pulseTimeout / 1000); 2835 value.it_interval.tv_nsec = (pulseTimeout % 1000) * 1000_000; 2836 2837 if(timerfd_settime(pulseFd, 0, &value, null) == -1) 2838 throw new Exception("couldn't make pulse timer"); 2839 2840 ep.epoll_event ev = void; 2841 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 2842 ev.events = ep.EPOLLIN; 2843 ev.data.fd = pulseFd; 2844 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, pulseFd, &ev); 2845 } 2846 2847 // eventfd for custom events 2848 if (customEventFD == -1) { 2849 customEventFD = eventfd(0, 0); 2850 if (customEventFD >= 0) { 2851 ep.epoll_event ev = void; 2852 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 2853 ev.events = ep.EPOLLIN; 2854 ev.data.fd = customEventFD; 2855 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customEventFD, &ev); 2856 } else { 2857 // this is something that should not be; better be safe than sorry 2858 throw new Exception("can't create eventfd for custom event processing"); 2859 } 2860 } 2861 2862 if (customSignalFD == -1) { 2863 import core.sys.linux.sys.signalfd; 2864 2865 sigset_t sigset; 2866 auto err = sigemptyset(&sigset); 2867 assert(!err); 2868 err = sigaddset(&sigset, SIGINT); 2869 assert(!err); 2870 err = sigaddset(&sigset, SIGHUP); 2871 assert(!err); 2872 err = sigprocmask(SIG_BLOCK, &sigset, null); 2873 assert(!err); 2874 2875 customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK); 2876 assert(customSignalFD != -1); 2877 2878 ep.epoll_event ev = void; 2879 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 2880 ev.events = ep.EPOLLIN; 2881 ev.data.fd = customSignalFD; 2882 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev); 2883 } 2884 } 2885 2886 SimpleWindow.processAllCustomEvents(); // process events added before event FD creation 2887 2888 version(linux) { 2889 this.mtLock(); 2890 scope(exit) this.mtUnlock(); 2891 XPending(display); // no, really 2892 } 2893 2894 disposed = false; 2895 } 2896 2897 bool disposed = true; 2898 version(X11) 2899 int displayFd = -1; 2900 2901 version(with_eventloop) 2902 void dispose() {} 2903 else 2904 void dispose() { 2905 disposed = true; 2906 version(X11) { 2907 if(pulseFd != -1) { 2908 import unix = core.sys.posix.unistd; 2909 unix.close(pulseFd); 2910 pulseFd = -1; 2911 } 2912 2913 version(linux) 2914 if(displayFd != -1) { 2915 // 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 2916 ep.epoll_event ev = void; 2917 { import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy 2918 ev.events = ep.EPOLLIN; 2919 ev.data.fd = displayFd; 2920 //import std.conv; 2921 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, displayFd, &ev); 2922 displayFd = -1; 2923 } 2924 2925 } else version(Windows) { 2926 if(pulser !is null) { 2927 pulser.destroy(); 2928 pulser = null; 2929 } 2930 if (customEventH !is null) { 2931 CloseHandle(customEventH); 2932 customEventH = null; 2933 } 2934 } 2935 } 2936 2937 this(long pulseTimeout, void delegate() handlePulse) { 2938 this.pulseTimeout = pulseTimeout; 2939 this.handlePulse = handlePulse; 2940 initialize(pulseTimeout); 2941 } 2942 2943 private long pulseTimeout; 2944 void delegate() handlePulse; 2945 2946 ~this() { 2947 dispose(); 2948 } 2949 2950 version(linux) 2951 ref int customEventFD() { return SimpleWindow.customEventFD; } 2952 version(linux) 2953 ref int customSignalFD() { return SimpleWindow.customSignalFD; } 2954 version(Windows) 2955 ref auto customEventH() { return SimpleWindow.customEventH; } 2956 2957 version(with_eventloop) { 2958 int loopHelper(bool delegate() whileCondition) { 2959 // FIXME: whileCondition 2960 import arsd.eventloop; 2961 loop(); 2962 return 0; 2963 } 2964 } else 2965 int loopHelper(bool delegate() whileCondition) { 2966 version(X11) { 2967 bool done = false; 2968 2969 XFlush(display); 2970 insideXEventLoop = true; 2971 scope(exit) insideXEventLoop = false; 2972 2973 version(linux) { 2974 while(!done && (whileCondition is null || whileCondition() == true) && notExited) { 2975 bool forceXPending = false; 2976 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 2977 // eh... some events may be queued for "squashing" (or "late delivery"), so we have to do the following magic 2978 { 2979 this.mtLock(); 2980 scope(exit) this.mtUnlock(); 2981 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 2982 } 2983 //{ import core.stdc.stdio; printf("*** wto=%d; force=%d\n", wto, (forceXPending ? 1 : 0)); } 2984 auto nfds = ep.epoll_wait(epollFd, events.ptr, events.length, (wto == 0 || wto >= int.max ? -1 : cast(int)wto)); 2985 if(nfds == -1) { 2986 if(err.errno == err.EINTR) { 2987 continue; // interrupted by signal, just try again 2988 } 2989 throw new Exception("epoll wait failure"); 2990 } 2991 2992 SimpleWindow.processAllCustomEvents(); // anyway 2993 //version(sdddd) { import std.stdio; writeln("nfds=", nfds, "; [0]=", events[0].data.fd); } 2994 foreach(idx; 0 .. nfds) { 2995 if(done) break; 2996 auto fd = events[idx].data.fd; 2997 assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume. 2998 auto flags = events[idx].events; 2999 if(flags & ep.EPOLLIN) { 3000 if (fd == customSignalFD) { 3001 version(linux) { 3002 import core.sys.linux.sys.signalfd; 3003 import core.sys.posix.unistd : read; 3004 signalfd_siginfo info; 3005 read(customSignalFD, &info, info.sizeof); 3006 3007 auto sig = info.ssi_signo; 3008 3009 if(EventLoop.get.signalHandler !is null) { 3010 EventLoop.get.signalHandler()(sig); 3011 } else { 3012 EventLoop.get.exit(); 3013 } 3014 } 3015 } else if(fd == display.fd) { 3016 version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); } 3017 this.mtLock(); 3018 scope(exit) this.mtUnlock(); 3019 while(!done && XPending(display)) { 3020 done = doXNextEvent(this.display); 3021 } 3022 forceXPending = false; 3023 } else if(fd == pulseFd) { 3024 long expirationCount; 3025 // 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... 3026 3027 handlePulse(); 3028 3029 // read just to clear the buffer so poll doesn't trigger again 3030 // BTW I read AFTER the pulse because if the pulse handler takes 3031 // a lot of time to execute, we don't want the app to get stuck 3032 // in a loop of timer hits without a chance to do anything else 3033 // 3034 // IOW handlePulse happens at most once per pulse interval. 3035 unix.read(pulseFd, &expirationCount, expirationCount.sizeof); 3036 } else if (fd == customEventFD) { 3037 // we have some custom events; process 'em 3038 import core.sys.posix.unistd : read; 3039 ulong n; 3040 read(customEventFD, &n, n.sizeof); // reset counter value to zero again 3041 //{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); } 3042 //SimpleWindow.processAllCustomEvents(); 3043 } else { 3044 // some other timer 3045 version(sdddd) { import std.stdio; writeln("unknown fd: ", fd); } 3046 3047 if(Timer* t = fd in Timer.mapping) 3048 (*t).trigger(); 3049 3050 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 3051 (*pfr).ready(flags); 3052 3053 // or i might add support for other FDs too 3054 // but for now it is just timer 3055 // (if you want other fds, use arsd.eventloop and compile with -version=with_eventloop), it offers a fuller api for arbitrary stuff. 3056 } 3057 } 3058 if(flags & ep.EPOLLHUP) { 3059 if(PosixFdReader* pfr = fd in PosixFdReader.mapping) 3060 (*pfr).hup(flags); 3061 if(globalHupHandler) 3062 globalHupHandler(fd, flags); 3063 } 3064 /+ 3065 } else { 3066 // not interested in OUT, we are just reading here. 3067 // 3068 // error or hup might also be reported 3069 // but it shouldn't here since we are only 3070 // using a few types of FD and Xlib will report 3071 // if it dies. 3072 // so instead of thoughtfully handling it, I'll 3073 // just throw. for now at least 3074 3075 throw new Exception("epoll did something else"); 3076 } 3077 +/ 3078 } 3079 // if we won't call `XPending()` here, libX may delay some internal event delivery. 3080 // i.e. we HAVE to repeatedly call `XPending()` even if libX fd wasn't signalled! 3081 if (!done && forceXPending) { 3082 this.mtLock(); 3083 scope(exit) this.mtUnlock(); 3084 //{ import core.stdc.stdio; printf("*** queued: %d\n", XEventsQueued(this.display, QueueMode.QueuedAlready)); } 3085 while(!done && XPending(display)) { 3086 done = doXNextEvent(this.display); 3087 } 3088 } 3089 } 3090 } else { 3091 // Generic fallback: yes to simple pulse support, 3092 // but NO timer support! 3093 3094 // FIXME: we could probably support the POSIX timer_create 3095 // signal-based option, but I'm in no rush to write it since 3096 // I prefer the fd-based functions. 3097 while (!done && (whileCondition is null || whileCondition() == true) && notExited) { 3098 while(!done && 3099 (pulseTimeout == 0 || (XPending(display) > 0))) 3100 { 3101 this.mtLock(); 3102 scope(exit) this.mtUnlock(); 3103 done = doXNextEvent(this.display); 3104 } 3105 if(!done && pulseTimeout !=0) { 3106 if(handlePulse !is null) 3107 handlePulse(); 3108 import core.thread; 3109 Thread.sleep(dur!"msecs"(pulseTimeout)); 3110 } 3111 } 3112 } 3113 } 3114 3115 version(Windows) { 3116 int ret = -1; 3117 MSG message; 3118 while(ret != 0 && (whileCondition is null || whileCondition() == true) && notExited) { 3119 auto wto = SimpleWindow.eventAllQueueTimeoutMSecs(); 3120 auto waitResult = MsgWaitForMultipleObjectsEx( 3121 cast(int) handles.length, handles.ptr, 3122 (wto == 0 ? INFINITE : wto), /* timeout */ 3123 0x04FF, /* QS_ALLINPUT */ 3124 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 3125 3126 SimpleWindow.processAllCustomEvents(); // anyway 3127 enum WAIT_OBJECT_0 = 0; 3128 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 3129 auto h = handles[waitResult - WAIT_OBJECT_0]; 3130 if(auto e = h in WindowsHandleReader.mapping) { 3131 (*e).ready(); 3132 } 3133 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 3134 // message ready 3135 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 3136 ret = GetMessage(&message, null, 0, 0); 3137 if(ret == -1) 3138 throw new Exception("GetMessage failed"); 3139 TranslateMessage(&message); 3140 DispatchMessage(&message); 3141 3142 if(ret == 0) // WM_QUIT 3143 break; 3144 } 3145 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 3146 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 3147 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 3148 // timeout, should never happen since we aren't using it 3149 } else if(waitResult == 0xFFFFFFFF) { 3150 // failed 3151 throw new Exception("MsgWaitForMultipleObjectsEx failed"); 3152 } else { 3153 // idk.... 3154 } 3155 } 3156 3157 // return message.wParam; 3158 return 0; 3159 } else { 3160 return 0; 3161 } 3162 } 3163 3164 int run(bool delegate() whileCondition = null) { 3165 if(disposed) 3166 initialize(this.pulseTimeout); 3167 3168 version(X11) { 3169 try { 3170 return loopHelper(whileCondition); 3171 } catch(XDisconnectException e) { 3172 if(e.userRequested) { 3173 foreach(item; CapableOfHandlingNativeEvent.nativeHandleMapping) 3174 item.discardConnectionState(); 3175 XCloseDisplay(XDisplayConnection.display); 3176 } 3177 3178 XDisplayConnection.display = null; 3179 3180 this.dispose(); 3181 3182 throw e; 3183 } 3184 } else { 3185 return loopHelper(whileCondition); 3186 } 3187 } 3188 } 3189 3190 3191 /++ 3192 Provides an icon on the system notification area (also known as the system tray). 3193 3194 3195 If a notification area is not available with the NotificationIcon object is created, 3196 it will silently succeed and simply attempt to create one when an area becomes available. 3197 3198 3199 NotificationAreaIcon on Windows assumes you are on Windows Vista or later. 3200 If this is wrong, pass -version=WindowsXP to dmd when compiling and it will 3201 use the older version. 3202 +/ 3203 version(OSXCocoa) {} else // NotYetImplementedException 3204 class NotificationAreaIcon : CapableOfHandlingNativeEvent { 3205 3206 version(X11) { 3207 void recreateAfterDisconnect() { 3208 stateDiscarded = false; 3209 clippixmap = None; 3210 throw new Exception("NOT IMPLEMENTED"); 3211 } 3212 3213 bool stateDiscarded; 3214 void discardConnectionState() { 3215 stateDiscarded = true; 3216 } 3217 } 3218 3219 3220 version(X11) { 3221 Image img; 3222 3223 NativeEventHandler getNativeEventHandler() { 3224 return delegate int(XEvent e) { 3225 switch(e.type) { 3226 case EventType.Expose: 3227 //case EventType.VisibilityNotify: 3228 redraw(); 3229 break; 3230 case EventType.ClientMessage: 3231 version(sddddd) { 3232 import std.stdio; 3233 writeln("\t", e.xclient.message_type == GetAtom!("_XEMBED")(XDisplayConnection.get)); 3234 writeln("\t", e.xclient.format); 3235 writeln("\t", e.xclient.data.l); 3236 } 3237 break; 3238 case EventType.ButtonPress: 3239 auto event = e.xbutton; 3240 if (onClick !is null || onClickEx !is null) { 3241 MouseButton mb = cast(MouseButton)0; 3242 switch (event.button) { 3243 case 1: mb = MouseButton.left; break; // left 3244 case 2: mb = MouseButton.middle; break; // middle 3245 case 3: mb = MouseButton.right; break; // right 3246 case 4: mb = MouseButton.wheelUp; break; // scroll up 3247 case 5: mb = MouseButton.wheelDown; break; // scroll down 3248 case 6: break; // idk 3249 case 7: break; // idk 3250 case 8: mb = MouseButton.backButton; break; 3251 case 9: mb = MouseButton.forwardButton; break; 3252 default: 3253 } 3254 if (mb) { 3255 try { onClick()(mb); } catch (Exception) {} 3256 if (onClickEx !is null) try { onClickEx(event.x_root, event.y_root, mb, cast(ModifierState)event.state); } catch (Exception) {} 3257 } 3258 } 3259 break; 3260 case EventType.EnterNotify: 3261 if (onEnter !is null) { 3262 onEnter(e.xcrossing.x_root, e.xcrossing.y_root, cast(ModifierState)e.xcrossing.state); 3263 } 3264 break; 3265 case EventType.LeaveNotify: 3266 if (onLeave !is null) try { onLeave(); } catch (Exception) {} 3267 break; 3268 case EventType.DestroyNotify: 3269 active = false; 3270 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(nativeHandle); 3271 break; 3272 case EventType.ConfigureNotify: 3273 auto event = e.xconfigure; 3274 this.width = event.width; 3275 this.height = event.height; 3276 //import std.stdio; writeln(width, " x " , height, " @ ", event.x, " ", event.y); 3277 redraw(); 3278 break; 3279 default: return 1; 3280 } 3281 return 1; 3282 }; 3283 } 3284 3285 /* private */ void hideBalloon() { 3286 balloon.close(); 3287 version(with_timer) 3288 timer.destroy(); 3289 balloon = null; 3290 version(with_timer) 3291 timer = null; 3292 } 3293 3294 void redraw() { 3295 if (!active) return; 3296 3297 auto display = XDisplayConnection.get; 3298 auto gc = DefaultGC(display, DefaultScreen(display)); 3299 XClearWindow(display, nativeHandle); 3300 3301 XSetClipMask(display, gc, clippixmap); 3302 3303 XSetForeground(display, gc, 3304 cast(uint) 0 << 16 | 3305 cast(uint) 0 << 8 | 3306 cast(uint) 0); 3307 XFillRectangle(display, nativeHandle, gc, 0, 0, width, height); 3308 3309 if (img is null) { 3310 XSetForeground(display, gc, 3311 cast(uint) 0 << 16 | 3312 cast(uint) 127 << 8 | 3313 cast(uint) 0); 3314 XFillArc(display, nativeHandle, 3315 gc, width / 4, height / 4, width * 2 / 4, height * 2 / 4, 0 * 64, 360 * 64); 3316 } else { 3317 int dx = 0; 3318 int dy = 0; 3319 if(width > img.width) 3320 dx = (width - img.width) / 2; 3321 if(height > img.height) 3322 dy = (height - img.height) / 2; 3323 XSetClipOrigin(display, gc, dx, dy); 3324 3325 if (img.usingXshm) 3326 XShmPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height, false); 3327 else 3328 XPutImage(display, cast(Drawable)nativeHandle, gc, img.handle, 0, 0, dx, dy, img.width, img.height); 3329 } 3330 XSetClipMask(display, gc, None); 3331 flushGui(); 3332 } 3333 3334 static Window getTrayOwner() { 3335 auto display = XDisplayConnection.get; 3336 auto i = cast(int) DefaultScreen(display); 3337 if(i < 10 && i >= 0) { 3338 static Atom atom; 3339 if(atom == None) 3340 atom = XInternAtom(display, cast(char*) ("_NET_SYSTEM_TRAY_S"~(cast(char) (i + '0')) ~ '\0').ptr, false); 3341 return XGetSelectionOwner(display, atom); 3342 } 3343 return None; 3344 } 3345 3346 static void sendTrayMessage(arch_long message, arch_long d1, arch_long d2, arch_long d3) { 3347 auto to = getTrayOwner(); 3348 auto display = XDisplayConnection.get; 3349 XEvent ev; 3350 ev.xclient.type = EventType.ClientMessage; 3351 ev.xclient.window = to; 3352 ev.xclient.message_type = GetAtom!("_NET_SYSTEM_TRAY_OPCODE", true)(display); 3353 ev.xclient.format = 32; 3354 ev.xclient.data.l[0] = CurrentTime; 3355 ev.xclient.data.l[1] = message; 3356 ev.xclient.data.l[2] = d1; 3357 ev.xclient.data.l[3] = d2; 3358 ev.xclient.data.l[4] = d3; 3359 3360 XSendEvent(XDisplayConnection.get, to, false, EventMask.NoEventMask, &ev); 3361 } 3362 3363 private static NotificationAreaIcon[] activeIcons; 3364 3365 // FIXME: possible leak with this stuff, should be able to clear it and stuff. 3366 private void newManager() { 3367 close(); 3368 createXWin(); 3369 3370 if(this.clippixmap) 3371 XFreePixmap(XDisplayConnection.get, clippixmap); 3372 if(this.originalMemoryImage) 3373 this.icon = this.originalMemoryImage; 3374 else if(this.img) 3375 this.icon = this.img; 3376 } 3377 3378 private void createXWin () { 3379 // create window 3380 auto display = XDisplayConnection.get; 3381 3382 // to check for MANAGER on root window to catch new/changed tray owners 3383 XDisplayConnection.addRootInput(EventMask.StructureNotifyMask); 3384 // so if a thing does appear, we can handle it 3385 foreach(ai; activeIcons) 3386 if(ai is this) 3387 goto alreadythere; 3388 activeIcons ~= this; 3389 alreadythere: 3390 3391 // and check for an existing tray 3392 auto trayOwner = getTrayOwner(); 3393 if(trayOwner == None) 3394 return; 3395 //throw new Exception("No notification area found"); 3396 3397 Visual* v = cast(Visual*) CopyFromParent; 3398 /+ 3399 auto visualProp = getX11PropertyData(trayOwner, GetAtom!("_NET_SYSTEM_TRAY_VISUAL", true)(display)); 3400 if(visualProp !is null) { 3401 c_ulong[] info = cast(c_ulong[]) visualProp; 3402 if(info.length == 1) { 3403 auto vid = info[0]; 3404 int returned; 3405 XVisualInfo t; 3406 t.visualid = vid; 3407 auto got = XGetVisualInfo(display, VisualIDMask, &t, &returned); 3408 if(got !is null) { 3409 if(returned == 1) { 3410 v = got.visual; 3411 import std.stdio; 3412 writeln("using special visual ", *got); 3413 } 3414 XFree(got); 3415 } 3416 } 3417 } 3418 +/ 3419 3420 auto nativeWindow = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 0, 0, 16, 16, 0, 24, InputOutput, v, 0, null); 3421 assert(nativeWindow); 3422 3423 XSetWindowBackgroundPixmap(display, nativeWindow, 1 /* ParentRelative */); 3424 3425 nativeHandle = nativeWindow; 3426 3427 ///+ 3428 arch_ulong[2] info; 3429 info[0] = 0; 3430 info[1] = 1; 3431 3432 string title = this.name is null ? "simpledisplay.d program" : this.name; 3433 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 3434 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 3435 XChangeProperty(display, nativeWindow, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 3436 3437 XChangeProperty( 3438 display, 3439 nativeWindow, 3440 GetAtom!("_XEMBED_INFO", true)(display), 3441 GetAtom!("_XEMBED_INFO", true)(display), 3442 32 /* bits */, 3443 0 /*PropModeReplace*/, 3444 info.ptr, 3445 2); 3446 3447 import core.sys.posix.unistd; 3448 arch_ulong pid = getpid(); 3449 3450 XChangeProperty( 3451 display, 3452 nativeWindow, 3453 GetAtom!("_NET_WM_PID", true)(display), 3454 XA_CARDINAL, 3455 32 /* bits */, 3456 0 /*PropModeReplace*/, 3457 &pid, 3458 1); 3459 3460 updateNetWmIcon(); 3461 3462 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 3463 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 3464 XClassHint klass; 3465 XWMHints wh; 3466 XSizeHints size; 3467 klass.res_name = sdpyWindowClassStr; 3468 klass.res_class = sdpyWindowClassStr; 3469 XSetWMProperties(display, nativeWindow, null, null, null, 0, &size, &wh, &klass); 3470 } 3471 3472 // believe it or not, THIS is what xfce needed for the 9999 issue 3473 XSizeHints sh; 3474 c_long spr; 3475 XGetWMNormalHints(display, nativeWindow, &sh, &spr); 3476 sh.flags |= PMaxSize | PMinSize; 3477 // FIXME maybe nicer resizing 3478 sh.min_width = 16; 3479 sh.min_height = 16; 3480 sh.max_width = 16; 3481 sh.max_height = 16; 3482 XSetWMNormalHints(display, nativeWindow, &sh); 3483 3484 3485 //+/ 3486 3487 3488 XSelectInput(display, nativeWindow, 3489 EventMask.ButtonPressMask | EventMask.ExposureMask | EventMask.StructureNotifyMask | EventMask.VisibilityChangeMask | 3490 EventMask.EnterWindowMask | EventMask.LeaveWindowMask); 3491 3492 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeWindow, 0, 0); 3493 CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; 3494 active = true; 3495 } 3496 3497 void updateNetWmIcon() { 3498 if(img is null) return; 3499 auto display = XDisplayConnection.get; 3500 // FIXME: ensure this is correct 3501 arch_ulong[] buffer; 3502 auto imgMi = img.toTrueColorImage; 3503 buffer ~= imgMi.width; 3504 buffer ~= imgMi.height; 3505 foreach(c; imgMi.imageData.colors) { 3506 arch_ulong b; 3507 b |= c.a << 24; 3508 b |= c.r << 16; 3509 b |= c.g << 8; 3510 b |= c.b; 3511 buffer ~= b; 3512 } 3513 3514 XChangeProperty( 3515 display, 3516 nativeHandle, 3517 GetAtom!"_NET_WM_ICON"(display), 3518 GetAtom!"CARDINAL"(display), 3519 32 /* bits */, 3520 0 /*PropModeReplace*/, 3521 buffer.ptr, 3522 cast(int) buffer.length); 3523 } 3524 3525 3526 3527 private SimpleWindow balloon; 3528 version(with_timer) 3529 private Timer timer; 3530 3531 private Window nativeHandle; 3532 private Pixmap clippixmap = None; 3533 private int width = 16; 3534 private int height = 16; 3535 private bool active = false; 3536 3537 void delegate (int x, int y, MouseButton button, ModifierState mods) onClickEx; /// x and y are globals (relative to root window). X11 only. 3538 void delegate (int x, int y, ModifierState mods) onEnter; /// x and y are global window coordinates. X11 only. 3539 void delegate () onLeave; /// X11 only. 3540 3541 @property bool closed () const pure nothrow @safe @nogc { return !active; } /// 3542 3543 /// X11 only. Get global window coordinates and size. This can be used to show various notifications. 3544 void getWindowRect (out int x, out int y, out int width, out int height) { 3545 if (!active) { width = 1; height = 1; return; } // 1: just in case 3546 Window dummyw; 3547 auto dpy = XDisplayConnection.get; 3548 //XWindowAttributes xwa; 3549 //XGetWindowAttributes(dpy, nativeHandle, &xwa); 3550 //XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), xwa.x, xwa.y, &x, &y, &dummyw); 3551 XTranslateCoordinates(dpy, nativeHandle, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw); 3552 width = this.width; 3553 height = this.height; 3554 } 3555 } 3556 3557 /+ 3558 What I actually want from this: 3559 3560 * set / change: icon, tooltip 3561 * handle: mouse click, right click 3562 * show: notification bubble. 3563 +/ 3564 3565 version(Windows) { 3566 WindowsIcon win32Icon; 3567 HWND hwnd; 3568 3569 NOTIFYICONDATAW data; 3570 3571 NativeEventHandler getNativeEventHandler() { 3572 return delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 3573 if(msg == WM_USER) { 3574 auto event = LOWORD(lParam); 3575 auto iconId = HIWORD(lParam); 3576 //auto x = GET_X_LPARAM(wParam); 3577 //auto y = GET_Y_LPARAM(wParam); 3578 switch(event) { 3579 case WM_LBUTTONDOWN: 3580 onClick()(MouseButton.left); 3581 break; 3582 case WM_RBUTTONDOWN: 3583 onClick()(MouseButton.right); 3584 break; 3585 case WM_MBUTTONDOWN: 3586 onClick()(MouseButton.middle); 3587 break; 3588 case WM_MOUSEMOVE: 3589 // sent, we could use it. 3590 break; 3591 case WM_MOUSEWHEEL: 3592 // NOT SENT 3593 break; 3594 //case NIN_KEYSELECT: 3595 //case NIN_SELECT: 3596 //break; 3597 default: {} 3598 } 3599 } 3600 return 0; 3601 }; 3602 } 3603 3604 enum NIF_SHOWTIP = 0x00000080; 3605 3606 private static struct NOTIFYICONDATAW { 3607 DWORD cbSize; 3608 HWND hWnd; 3609 UINT uID; 3610 UINT uFlags; 3611 UINT uCallbackMessage; 3612 HICON hIcon; 3613 WCHAR[128] szTip; 3614 DWORD dwState; 3615 DWORD dwStateMask; 3616 WCHAR[256] szInfo; 3617 union { 3618 UINT uTimeout; 3619 UINT uVersion; 3620 } 3621 WCHAR[64] szInfoTitle; 3622 DWORD dwInfoFlags; 3623 GUID guidItem; 3624 HICON hBalloonIcon; 3625 } 3626 3627 } 3628 3629 /++ 3630 Note that on Windows, only left, right, and middle buttons are sent. 3631 Mouse wheel buttons are NOT set, so don't rely on those events if your 3632 program is meant to be used on Windows too. 3633 +/ 3634 this(string name, MemoryImage icon, void delegate(MouseButton button) onClick) { 3635 // The canonical constructor for Windows needs the MemoryImage, so it is here, 3636 // but on X, we need an Image, so its canonical ctor is there. They should 3637 // forward to each other though. 3638 version(X11) { 3639 this.name = name; 3640 this.onClick = onClick; 3641 createXWin(); 3642 this.icon = icon; 3643 } else version(Windows) { 3644 this.onClick = onClick; 3645 this.win32Icon = new WindowsIcon(icon); 3646 3647 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 3648 3649 static bool registered = false; 3650 if(!registered) { 3651 WNDCLASSEX wc; 3652 wc.cbSize = wc.sizeof; 3653 wc.hInstance = hInstance; 3654 wc.lpfnWndProc = &WndProc; 3655 wc.lpszClassName = "arsd_simpledisplay_notification_icon"w.ptr; 3656 if(!RegisterClassExW(&wc)) 3657 throw new WindowsApiException("RegisterClass"); 3658 registered = true; 3659 } 3660 3661 this.hwnd = CreateWindowW("arsd_simpledisplay_notification_icon"w.ptr, "test"w.ptr /* name */, 0 /* dwStyle */, 0, 0, 0, 0, HWND_MESSAGE, null, hInstance, null); 3662 if(hwnd is null) 3663 throw new Exception("CreateWindow"); 3664 3665 data.cbSize = data.sizeof; 3666 data.hWnd = hwnd; 3667 data.uID = cast(uint) cast(void*) this; 3668 data.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_SHOWTIP /* use default tooltip, for now. */; 3669 // NIF_INFO means show balloon 3670 data.uCallbackMessage = WM_USER; 3671 data.hIcon = this.win32Icon.hIcon; 3672 data.szTip = ""; // FIXME 3673 data.dwState = 0; // NIS_HIDDEN; // windows vista 3674 data.dwStateMask = NIS_HIDDEN; // windows vista 3675 3676 data.uVersion = 4; // NOTIFYICON_VERSION_4; // Windows Vista and up 3677 3678 3679 Shell_NotifyIcon(NIM_ADD, cast(NOTIFYICONDATA*) &data); 3680 3681 CapableOfHandlingNativeEvent.nativeHandleMapping[this.hwnd] = this; 3682 } else version(OSXCocoa) { 3683 throw new NotYetImplementedException(); 3684 } else static assert(0); 3685 } 3686 3687 /// ditto 3688 this(string name, Image icon, void delegate(MouseButton button) onClick) { 3689 version(X11) { 3690 this.onClick = onClick; 3691 this.name = name; 3692 createXWin(); 3693 this.icon = icon; 3694 } else version(Windows) { 3695 this(name, icon is null ? null : icon.toTrueColorImage(), onClick); 3696 } else version(OSXCocoa) { 3697 throw new NotYetImplementedException(); 3698 } else static assert(0); 3699 } 3700 3701 version(X11) { 3702 /++ 3703 X-specific extension (for now at least) 3704 +/ 3705 this(string name, MemoryImage icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 3706 this.onClickEx = onClickEx; 3707 createXWin(); 3708 if (icon !is null) this.icon = icon; 3709 } 3710 3711 /// ditto 3712 this(string name, Image icon, void delegate(int x, int y, MouseButton button, ModifierState mods) onClickEx) { 3713 this.onClickEx = onClickEx; 3714 createXWin(); 3715 this.icon = icon; 3716 } 3717 } 3718 3719 private void delegate (MouseButton button) onClick_; 3720 3721 /// 3722 @property final void delegate(MouseButton) onClick() { 3723 if(onClick_ is null) 3724 onClick_ = delegate void(MouseButton) {}; 3725 return onClick_; 3726 } 3727 3728 /// ditto 3729 @property final void onClick(void delegate(MouseButton) handler) { 3730 // I made this a property setter so we can wrap smaller arg 3731 // delegates and just forward all to onClickEx or something. 3732 onClick_ = handler; 3733 } 3734 3735 3736 string name_; 3737 @property void name(string n) { 3738 name_ = n; 3739 } 3740 3741 @property string name() { 3742 return name_; 3743 } 3744 3745 private MemoryImage originalMemoryImage; 3746 3747 /// 3748 @property void icon(MemoryImage i) { 3749 version(X11) { 3750 this.originalMemoryImage = i; 3751 if (!active) return; 3752 if (i !is null) { 3753 this.img = Image.fromMemoryImage(i); 3754 this.clippixmap = transparencyMaskFromMemoryImage(i, nativeHandle); 3755 //import std.stdio; writeln("using pixmap ", clippixmap); 3756 updateNetWmIcon(); 3757 redraw(); 3758 } else { 3759 if (this.img !is null) { 3760 this.img = null; 3761 redraw(); 3762 } 3763 } 3764 } else version(Windows) { 3765 this.win32Icon = new WindowsIcon(i); 3766 3767 data.uFlags = NIF_ICON; 3768 data.hIcon = this.win32Icon.hIcon; 3769 3770 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 3771 } else version(OSXCocoa) { 3772 throw new NotYetImplementedException(); 3773 } else static assert(0); 3774 } 3775 3776 /// ditto 3777 @property void icon (Image i) { 3778 version(X11) { 3779 if (!active) return; 3780 if (i !is img) { 3781 originalMemoryImage = null; 3782 img = i; 3783 redraw(); 3784 } 3785 } else version(Windows) { 3786 this.icon(i is null ? null : i.toTrueColorImage()); 3787 } else version(OSXCocoa) { 3788 throw new NotYetImplementedException(); 3789 } else static assert(0); 3790 } 3791 3792 /++ 3793 Shows a balloon notification. You can only show one balloon at a time, if you call 3794 it twice while one is already up, the first balloon will be replaced. 3795 3796 3797 The user is free to block notifications and they will automatically disappear after 3798 a timeout period. 3799 3800 Params: 3801 title = Title of the notification. Must be 40 chars or less or the OS may truncate it. 3802 message = The message to pop up. Must be 220 chars or less or the OS may truncate it. 3803 icon = the icon to display with the notification. If null, it uses your existing icon. 3804 onclick = delegate called if the user clicks the balloon. (not yet implemented) 3805 timeout = your suggested timeout period. The operating system is free to ignore your suggestion. 3806 +/ 3807 void showBalloon(string title, string message, MemoryImage icon = null, void delegate() onclick = null, int timeout = 2_500) { 3808 bool useCustom = true; 3809 version(libnotify) { 3810 if(onclick is null) // libnotify impl doesn't support callbacks yet because it doesn't do a dbus message loop 3811 try { 3812 if(!active) return; 3813 3814 if(libnotify is null) { 3815 libnotify = new C_DynamicLibrary("libnotify.so"); 3816 libnotify.call!("notify_init", int, const char*)()((ApplicationName ~ "\0").ptr); 3817 } 3818 3819 auto n = libnotify.call!("notify_notification_new", void*, const char*, const char*, const char*)()((title~"\0").ptr, (message~"\0").ptr, null /* icon */); 3820 3821 libnotify.call!("notify_notification_set_timeout", void, void*, int)()(n, timeout); 3822 3823 if(onclick) { 3824 libnotify_action_delegates[libnotify_action_delegates_count] = onclick; 3825 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); 3826 libnotify_action_delegates_count++; 3827 } 3828 3829 // FIXME icon 3830 3831 // set hint image-data 3832 // set default action for onclick 3833 3834 void* error; 3835 libnotify.call!("notify_notification_show", bool, void*, void**)()(n, &error); 3836 3837 useCustom = false; 3838 } catch(Exception e) { 3839 3840 } 3841 } 3842 3843 version(X11) { 3844 if(useCustom) { 3845 if(!active) return; 3846 if(balloon) { 3847 hideBalloon(); 3848 } 3849 // I know there are two specs for this, but one is never 3850 // implemented by any window manager I have ever seen, and 3851 // the other is a bloated mess and too complicated for simpledisplay... 3852 // so doing my own little window instead. 3853 balloon = new SimpleWindow(380, 120, null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.notification, WindowFlags.dontAutoShow/*, window*/); 3854 3855 int x, y, width, height; 3856 getWindowRect(x, y, width, height); 3857 3858 int bx = x - balloon.width; 3859 int by = y - balloon.height; 3860 if(bx < 0) 3861 bx = x + width + balloon.width; 3862 if(by < 0) 3863 by = y + height; 3864 3865 // just in case, make sure it is actually on scren 3866 if(bx < 0) 3867 bx = 0; 3868 if(by < 0) 3869 by = 0; 3870 3871 balloon.move(bx, by); 3872 auto painter = balloon.draw(); 3873 painter.fillColor = Color(220, 220, 220); 3874 painter.outlineColor = Color.black; 3875 painter.drawRectangle(Point(0, 0), balloon.width, balloon.height); 3876 auto iconWidth = icon is null ? 0 : icon.width; 3877 if(icon) 3878 painter.drawImage(Point(4, 4), Image.fromMemoryImage(icon)); 3879 iconWidth += 6; // margin around the icon 3880 3881 // draw a close button 3882 painter.outlineColor = Color(44, 44, 44); 3883 painter.fillColor = Color(255, 255, 255); 3884 painter.drawRectangle(Point(balloon.width - 15, 3), 13, 13); 3885 painter.pen = Pen(Color.black, 3); 3886 painter.drawLine(Point(balloon.width - 14, 4), Point(balloon.width - 4, 14)); 3887 painter.drawLine(Point(balloon.width - 4, 4), Point(balloon.width - 14, 13)); 3888 painter.pen = Pen(Color.black, 1); 3889 painter.fillColor = Color(220, 220, 220); 3890 3891 // Draw the title and message 3892 painter.drawText(Point(4 + iconWidth, 4), title); 3893 painter.drawLine( 3894 Point(4 + iconWidth, 4 + painter.fontHeight + 1), 3895 Point(balloon.width - 4, 4 + painter.fontHeight + 1), 3896 ); 3897 painter.drawText(Point(4 + iconWidth, 4 + painter.fontHeight + 4), message); 3898 3899 balloon.setEventHandlers( 3900 (MouseEvent ev) { 3901 if(ev.type == MouseEventType.buttonPressed) { 3902 if(ev.x > balloon.width - 16 && ev.y < 16) 3903 hideBalloon(); 3904 else if(onclick) 3905 onclick(); 3906 } 3907 } 3908 ); 3909 balloon.show(); 3910 3911 version(with_timer) 3912 timer = new Timer(timeout, &hideBalloon); 3913 else {} // FIXME 3914 } 3915 } else version(Windows) { 3916 enum NIF_INFO = 0x00000010; 3917 3918 data.uFlags = NIF_INFO; 3919 3920 // FIXME: go back to the last valid unicode code point 3921 if(title.length > 40) 3922 title = title[0 .. 40]; 3923 if(message.length > 220) 3924 message = message[0 .. 220]; 3925 3926 enum NIIF_RESPECT_QUIET_TIME = 0x00000080; 3927 enum NIIF_LARGE_ICON = 0x00000020; 3928 enum NIIF_NOSOUND = 0x00000010; 3929 enum NIIF_USER = 0x00000004; 3930 enum NIIF_ERROR = 0x00000003; 3931 enum NIIF_WARNING = 0x00000002; 3932 enum NIIF_INFO = 0x00000001; 3933 enum NIIF_NONE = 0; 3934 3935 WCharzBuffer t = WCharzBuffer(title); 3936 WCharzBuffer m = WCharzBuffer(message); 3937 3938 t.copyInto(data.szInfoTitle); 3939 m.copyInto(data.szInfo); 3940 data.dwInfoFlags = NIIF_RESPECT_QUIET_TIME; 3941 3942 if(icon !is null) { 3943 auto i = new WindowsIcon(icon); 3944 data.hBalloonIcon = i.hIcon; 3945 data.dwInfoFlags |= NIIF_USER; 3946 } 3947 3948 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 3949 } else version(OSXCocoa) { 3950 throw new NotYetImplementedException(); 3951 } else static assert(0); 3952 } 3953 3954 /// 3955 //version(Windows) 3956 void show() { 3957 version(X11) { 3958 if(!hidden) 3959 return; 3960 sendTrayMessage(SYSTEM_TRAY_REQUEST_DOCK, nativeHandle, 0, 0); 3961 hidden = false; 3962 } else version(Windows) { 3963 data.uFlags = NIF_STATE; 3964 data.dwState = 0; // NIS_HIDDEN; // windows vista 3965 data.dwStateMask = NIS_HIDDEN; // windows vista 3966 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 3967 } else version(OSXCocoa) { 3968 throw new NotYetImplementedException(); 3969 } else static assert(0); 3970 } 3971 3972 version(X11) 3973 bool hidden = false; 3974 3975 /// 3976 //version(Windows) 3977 void hide() { 3978 version(X11) { 3979 if(hidden) 3980 return; 3981 hidden = true; 3982 XUnmapWindow(XDisplayConnection.get, nativeHandle); 3983 } else version(Windows) { 3984 data.uFlags = NIF_STATE; 3985 data.dwState = NIS_HIDDEN; // windows vista 3986 data.dwStateMask = NIS_HIDDEN; // windows vista 3987 Shell_NotifyIcon(NIM_MODIFY, cast(NOTIFYICONDATA*) &data); 3988 } else version(OSXCocoa) { 3989 throw new NotYetImplementedException(); 3990 } else static assert(0); 3991 } 3992 3993 /// 3994 void close () { 3995 version(X11) { 3996 if (active) { 3997 active = false; // event handler will set this too, but meh 3998 XUnmapWindow(XDisplayConnection.get, nativeHandle); // 'cause why not; let's be polite 3999 XDestroyWindow(XDisplayConnection.get, nativeHandle); 4000 flushGui(); 4001 } 4002 } else version(Windows) { 4003 Shell_NotifyIcon(NIM_DELETE, cast(NOTIFYICONDATA*) &data); 4004 } else version(OSXCocoa) { 4005 throw new NotYetImplementedException(); 4006 } else static assert(0); 4007 } 4008 4009 ~this() { 4010 version(X11) 4011 if(clippixmap != None) 4012 XFreePixmap(XDisplayConnection.get, clippixmap); 4013 close(); 4014 } 4015 } 4016 4017 version(X11) 4018 /// call XFreePixmap on the return value 4019 Pixmap transparencyMaskFromMemoryImage(MemoryImage i, Window window) { 4020 char[] data = new char[](i.width * i.height / 8 + 2); 4021 data[] = 0; 4022 4023 int bitOffset = 0; 4024 foreach(c; i.getAsTrueColorImage().imageData.colors) { // FIXME inefficient unnecessary conversion in palette cases 4025 ubyte v = c.a > 128 ? 1 : 0; 4026 data[bitOffset / 8] |= v << (bitOffset%8); 4027 bitOffset++; 4028 } 4029 auto handle = XCreateBitmapFromData(XDisplayConnection.get, cast(Drawable) window, data.ptr, i.width, i.height); 4030 return handle; 4031 } 4032 4033 4034 // basic functions to make timers 4035 /** 4036 A timer that will trigger your function on a given interval. 4037 4038 4039 You create a timer with an interval and a callback. It will continue 4040 to fire on the interval until it is destroyed. 4041 4042 There are currently no one-off timers (instead, just create one and 4043 destroy it when it is triggered) nor are there pause/resume functions - 4044 the timer must again be destroyed and recreated if you want to pause it. 4045 4046 auto timer = new Timer(50, { it happened!; }); 4047 timer.destroy(); 4048 4049 Timers can only be expected to fire when the event loop is running. 4050 */ 4051 version(with_timer) { 4052 class Timer { 4053 // FIXME: needs pause and unpause 4054 // FIXME: I might add overloads for ones that take a count of 4055 // how many elapsed since last time (on Windows, it will divide 4056 // the ticks thing given, on Linux it is just available) and 4057 // maybe one that takes an instance of the Timer itself too 4058 /// Create a timer with a callback when it triggers. 4059 this(int intervalInMilliseconds, void delegate() onPulse) { 4060 assert(onPulse !is null); 4061 4062 this.onPulse = onPulse; 4063 4064 version(Windows) { 4065 /* 4066 handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 4067 if(handle == 0) 4068 throw new Exception("SetTimer fail"); 4069 */ 4070 4071 // thanks to Archival 998 for the WaitableTimer blocks 4072 handle = CreateWaitableTimer(null, false, null); 4073 long initialTime = 0; 4074 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 4075 throw new Exception("SetWaitableTimer Failed"); 4076 4077 mapping[handle] = this; 4078 4079 } else version(linux) { 4080 static import ep = core.sys.linux.epoll; 4081 4082 import core.sys.linux.timerfd; 4083 4084 fd = timerfd_create(CLOCK_MONOTONIC, 0); 4085 if(fd == -1) 4086 throw new Exception("timer create failed"); 4087 4088 mapping[fd] = this; 4089 4090 itimerspec value; 4091 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 4092 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 4093 4094 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 4095 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 4096 4097 if(timerfd_settime(fd, 0, &value, null) == -1) 4098 throw new Exception("couldn't make pulse timer"); 4099 4100 version(with_eventloop) { 4101 import arsd.eventloop; 4102 addFileEventListeners(fd, &trigger, null, null); 4103 } else { 4104 prepareEventLoop(); 4105 4106 ep.epoll_event ev = void; 4107 ev.events = ep.EPOLLIN; 4108 ev.data.fd = fd; 4109 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 4110 } 4111 } else featureNotImplemented(); 4112 } 4113 4114 /// Stop and destroy the timer object. 4115 void destroy() { 4116 version(Windows) { 4117 if(handle) { 4118 // KillTimer(null, handle); 4119 CancelWaitableTimer(cast(void*)handle); 4120 mapping.remove(handle); 4121 CloseHandle(handle); 4122 handle = null; 4123 } 4124 } else version(linux) { 4125 if(fd != -1) { 4126 import unix = core.sys.posix.unistd; 4127 static import ep = core.sys.linux.epoll; 4128 4129 version(with_eventloop) { 4130 import arsd.eventloop; 4131 removeFileEventListeners(fd); 4132 } else { 4133 ep.epoll_event ev = void; 4134 ev.events = ep.EPOLLIN; 4135 ev.data.fd = fd; 4136 4137 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 4138 } 4139 unix.close(fd); 4140 mapping.remove(fd); 4141 fd = -1; 4142 } 4143 } else featureNotImplemented(); 4144 } 4145 4146 ~this() { 4147 destroy(); 4148 } 4149 4150 4151 void changeTime(int intervalInMilliseconds) 4152 { 4153 version(Windows) 4154 { 4155 if(handle) 4156 { 4157 //handle = SetTimer(null, handle, intervalInMilliseconds, &timerCallback); 4158 long initialTime = 0; 4159 if(handle is null || !SetWaitableTimer(handle, cast(LARGE_INTEGER*)&initialTime, intervalInMilliseconds, &timerCallback, handle, false)) 4160 throw new Exception("couldn't change pulse timer"); 4161 } 4162 } 4163 } 4164 4165 4166 private: 4167 4168 void delegate() onPulse; 4169 4170 void trigger() { 4171 version(linux) { 4172 import unix = core.sys.posix.unistd; 4173 long val; 4174 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 4175 } else version(Windows) { 4176 4177 } else featureNotImplemented(); 4178 4179 onPulse(); 4180 } 4181 4182 version(Windows) 4183 extern(Windows) 4184 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 4185 static void timerCallback(HANDLE timer, DWORD lowTime, DWORD hiTime) nothrow { 4186 if(Timer* t = timer in mapping) { 4187 try 4188 (*t).trigger(); 4189 catch(Exception e) { throw new Error(e.msg, e.file, e.line); } 4190 } 4191 } 4192 4193 version(Windows) { 4194 //UINT_PTR handle; 4195 //static Timer[UINT_PTR] mapping; 4196 HANDLE handle; 4197 __gshared Timer[HANDLE] mapping; 4198 } else version(linux) { 4199 int fd = -1; 4200 __gshared Timer[int] mapping; 4201 } else static assert(0, "timer not supported"); 4202 } 4203 } 4204 4205 version(Windows) 4206 /// 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.) 4207 class WindowsHandleReader { 4208 /// 4209 this(void delegate() onReady, HANDLE handle) { 4210 this.onReady = onReady; 4211 this.handle = handle; 4212 4213 mapping[handle] = this; 4214 4215 enable(); 4216 } 4217 4218 /// 4219 void enable() { 4220 auto el = EventLoop.get().impl; 4221 el.handles ~= handle; 4222 } 4223 4224 /// 4225 void disable() { 4226 auto el = EventLoop.get().impl; 4227 for(int i = 0; i < el.handles.length; i++) { 4228 if(el.handles[i] is handle) { 4229 el.handles[i] = el.handles[$-1]; 4230 el.handles = el.handles[0 .. $-1]; 4231 return; 4232 } 4233 } 4234 } 4235 4236 void dispose() { 4237 disable(); 4238 mapping.remove(handle); 4239 handle = null; 4240 } 4241 4242 void ready() { 4243 if(onReady) 4244 onReady(); 4245 } 4246 4247 HANDLE handle; 4248 void delegate() onReady; 4249 4250 __gshared WindowsHandleReader[HANDLE] mapping; 4251 } 4252 4253 version(linux) 4254 /// Lets you add files to the event loop for reading. Use at your own risk. 4255 class PosixFdReader { 4256 /// 4257 this(void delegate() onReady, int fd, bool captureReads = true, bool captureWrites = false) { 4258 this((int, bool, bool) { onReady(); }, fd, captureReads, captureWrites); 4259 } 4260 4261 /// 4262 this(void delegate(int) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 4263 this((int fd, bool, bool) { onReady(fd); }, fd, captureReads, captureWrites); 4264 } 4265 4266 /// 4267 this(void delegate(int fd, bool read, bool write) onReady, int fd, bool captureReads = true, bool captureWrites = false) { 4268 this.onReady = onReady; 4269 this.fd = fd; 4270 this.captureWrites = captureWrites; 4271 this.captureReads = captureReads; 4272 4273 mapping[fd] = this; 4274 4275 version(with_eventloop) { 4276 import arsd.eventloop; 4277 addFileEventListeners(fd, &readyel); 4278 } else { 4279 enable(); 4280 } 4281 } 4282 4283 bool captureReads; 4284 bool captureWrites; 4285 4286 version(with_eventloop) {} else 4287 /// 4288 void enable() { 4289 prepareEventLoop(); 4290 4291 static import ep = core.sys.linux.epoll; 4292 ep.epoll_event ev = void; 4293 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 4294 //import std.stdio; writeln("enable ", fd, " ", captureReads, " ", captureWrites); 4295 ev.data.fd = fd; 4296 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, fd, &ev); 4297 } 4298 4299 version(with_eventloop) {} else 4300 /// 4301 void disable() { 4302 prepareEventLoop(); 4303 4304 static import ep = core.sys.linux.epoll; 4305 ep.epoll_event ev = void; 4306 ev.events = (captureReads ? ep.EPOLLIN : 0) | (captureWrites ? ep.EPOLLOUT : 0); 4307 //import std.stdio; writeln("disable ", fd, " ", captureReads, " ", captureWrites); 4308 ev.data.fd = fd; 4309 ep.epoll_ctl(epollFd, ep.EPOLL_CTL_DEL, fd, &ev); 4310 } 4311 4312 version(with_eventloop) {} else 4313 /// 4314 void dispose() { 4315 disable(); 4316 mapping.remove(fd); 4317 fd = -1; 4318 } 4319 4320 void delegate(int, bool, bool) onReady; 4321 4322 version(with_eventloop) 4323 void readyel() { 4324 onReady(fd, true, true); 4325 } 4326 4327 void ready(uint flags) { 4328 static import ep = core.sys.linux.epoll; 4329 onReady(fd, (flags & ep.EPOLLIN) ? true : false, (flags & ep.EPOLLOUT) ? true : false); 4330 } 4331 4332 void hup(uint flags) { 4333 if(onHup) 4334 onHup(); 4335 } 4336 4337 void delegate() onHup; 4338 4339 int fd = -1; 4340 __gshared PosixFdReader[int] mapping; 4341 } 4342 4343 // basic functions to access the clipboard 4344 /+ 4345 4346 4347 http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168%28v=vs.85%29.aspx 4348 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649039%28v=vs.85%29.aspx 4349 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 4350 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx 4351 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649037%28v=vs.85%29.aspx 4352 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649035%28v=vs.85%29.aspx 4353 http://msdn.microsoft.com/en-us/library/windows/desktop/ms649016%28v=vs.85%29.aspx 4354 4355 +/ 4356 4357 /++ 4358 this does a delegate because it is actually an async call on X... 4359 the receiver may never be called if the clipboard is empty or unavailable 4360 gets plain text from the clipboard 4361 +/ 4362 void getClipboardText(SimpleWindow clipboardOwner, void delegate(in char[]) receiver) { 4363 version(Windows) { 4364 HWND hwndOwner = clipboardOwner ? clipboardOwner.impl.hwnd : null; 4365 if(OpenClipboard(hwndOwner) == 0) 4366 throw new Exception("OpenClipboard"); 4367 scope(exit) 4368 CloseClipboard(); 4369 if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) { 4370 4371 if(auto data = cast(wchar*) GlobalLock(dataHandle)) { 4372 scope(exit) 4373 GlobalUnlock(dataHandle); 4374 4375 // FIXME: CR/LF conversions 4376 // FIXME: I might not have to copy it now that the receiver is in char[] instead of string 4377 int len = 0; 4378 auto d = data; 4379 while(*d) { 4380 d++; 4381 len++; 4382 } 4383 string s; 4384 s.reserve(len); 4385 foreach(dchar ch; data[0 .. len]) { 4386 s ~= ch; 4387 } 4388 receiver(s); 4389 } 4390 } 4391 } else version(X11) { 4392 getX11Selection!"CLIPBOARD"(clipboardOwner, receiver); 4393 } else version(OSXCocoa) { 4394 throw new NotYetImplementedException(); 4395 } else static assert(0); 4396 } 4397 4398 version(Windows) 4399 struct WCharzBuffer { 4400 wchar[256] staticBuffer; 4401 wchar[] buffer; 4402 4403 size_t length() { 4404 return buffer.length; 4405 } 4406 4407 wchar* ptr() { 4408 return buffer.ptr; 4409 } 4410 4411 wchar[] slice() { 4412 return buffer; 4413 } 4414 4415 void copyInto(R)(ref R r) { 4416 static if(is(R == wchar[N], size_t N)) { 4417 r[0 .. this.length] = slice[]; 4418 r[this.length] = 0; 4419 } else static assert(0, "can only copy into wchar[n], not " ~ R.stringof); 4420 } 4421 4422 /++ 4423 conversionFlags = [WindowsStringConversionFlags] 4424 +/ 4425 this(in char[] data, int conversionFlags = 0) { 4426 conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name 4427 auto sz = sizeOfConvertedWstring(data, conversionFlags); 4428 if(sz > staticBuffer.length) 4429 buffer = new wchar[](sz); 4430 else 4431 buffer = staticBuffer[]; 4432 4433 buffer = makeWindowsString(data, buffer, conversionFlags); 4434 } 4435 } 4436 4437 version(Windows) 4438 int sizeOfConvertedWstring(in char[] s, int conversionFlags) { 4439 int size = 0; 4440 4441 if(conversionFlags & WindowsStringConversionFlags.convertNewLines) { 4442 // need to convert line endings, which means the length will get bigger. 4443 4444 // BTW I betcha this could be faster with some simd stuff. 4445 char last; 4446 foreach(char ch; s) { 4447 if(ch == 10 && last != 13) 4448 size++; // will add a 13 before it... 4449 size++; 4450 last = ch; 4451 } 4452 } else { 4453 // no conversion necessary, just estimate based on length 4454 /* 4455 I don't think there's any string with a longer length 4456 in code units when encoded in UTF-16 than it has in UTF-8. 4457 This will probably over allocate, but that's OK. 4458 */ 4459 size = cast(int) s.length; 4460 } 4461 4462 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) 4463 size++; 4464 4465 return size; 4466 } 4467 4468 version(Windows) 4469 enum WindowsStringConversionFlags : int { 4470 zeroTerminate = 1, 4471 convertNewLines = 2, 4472 } 4473 4474 version(Windows) 4475 class WindowsApiException : Exception { 4476 char[256] buffer; 4477 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 4478 assert(msg.length < 100); 4479 4480 auto error = GetLastError(); 4481 buffer[0 .. msg.length] = msg; 4482 buffer[msg.length] = ' '; 4483 4484 int pos = cast(int) msg.length + 1; 4485 4486 if(error == 0) 4487 buffer[pos++] = '0'; 4488 else { 4489 auto init = pos; 4490 while(error) { 4491 buffer[pos++] = (error % 10) + '0'; 4492 error /= 10; 4493 } 4494 for(int i = 0; i < (pos - init) / 2; i++) { 4495 char c = buffer[i + init]; 4496 buffer[i + init] = buffer[pos - (i + init) - 1]; 4497 buffer[pos - (i + init) - 1] = c; 4498 } 4499 } 4500 4501 4502 super(cast(string) buffer[0 .. pos], file, line, next); 4503 } 4504 } 4505 4506 class ErrnoApiException : Exception { 4507 char[256] buffer; 4508 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 4509 assert(msg.length < 100); 4510 4511 import core.stdc.errno; 4512 auto error = errno; 4513 buffer[0 .. msg.length] = msg; 4514 buffer[msg.length] = ' '; 4515 4516 int pos = cast(int) msg.length + 1; 4517 4518 if(error == 0) 4519 buffer[pos++] = '0'; 4520 else { 4521 auto init = pos; 4522 while(error) { 4523 buffer[pos++] = (error % 10) + '0'; 4524 error /= 10; 4525 } 4526 for(int i = 0; i < (pos - init) / 2; i++) { 4527 char c = buffer[i + init]; 4528 buffer[i + init] = buffer[pos - (i + init) - 1]; 4529 buffer[pos - (i + init) - 1] = c; 4530 } 4531 } 4532 4533 4534 super(cast(string) buffer[0 .. pos], file, line, next); 4535 } 4536 4537 } 4538 4539 version(Windows) 4540 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) { 4541 if(str.length == 0) 4542 return null; 4543 4544 int pos = 0; 4545 dchar last; 4546 foreach(dchar c; str) { 4547 if(c <= 0xFFFF) { 4548 if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13) 4549 buffer[pos++] = 13; 4550 buffer[pos++] = cast(wchar) c; 4551 } else if(c <= 0x10FFFF) { 4552 buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 4553 buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); 4554 } 4555 4556 last = c; 4557 } 4558 4559 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) { 4560 buffer[pos] = 0; 4561 } 4562 4563 return buffer[0 .. pos]; 4564 } 4565 4566 version(Windows) 4567 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) { 4568 if(str.length == 0) 4569 return null; 4570 4571 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null); 4572 if(got == 0) { 4573 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 4574 throw new Exception("not enough buffer"); 4575 else 4576 throw new Exception("conversion"); // FIXME: GetLastError 4577 } 4578 return buffer[0 .. got]; 4579 } 4580 4581 version(Windows) 4582 string makeUtf8StringFromWindowsString(in wchar[] str) { 4583 char[] buffer; 4584 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null); 4585 buffer.length = got; 4586 4587 // it is unique because we just allocated it above! 4588 return cast(string) makeUtf8StringFromWindowsString(str, buffer); 4589 } 4590 4591 version(Windows) 4592 string makeUtf8StringFromWindowsString(wchar* str) { 4593 char[] buffer; 4594 auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null); 4595 buffer.length = got; 4596 4597 got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null); 4598 if(got == 0) { 4599 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 4600 throw new Exception("not enough buffer"); 4601 else 4602 throw new Exception("conversion"); // FIXME: GetLastError 4603 } 4604 return cast(string) buffer[0 .. got]; 4605 } 4606 4607 int findIndexOfZero(in wchar[] str) { 4608 foreach(idx, wchar ch; str) 4609 if(ch == 0) 4610 return cast(int) idx; 4611 return cast(int) str.length; 4612 } 4613 int findIndexOfZero(in char[] str) { 4614 foreach(idx, char ch; str) 4615 if(ch == 0) 4616 return cast(int) idx; 4617 return cast(int) str.length; 4618 } 4619 4620 /// copies some text to the clipboard 4621 void setClipboardText(SimpleWindow clipboardOwner, string text) { 4622 assert(clipboardOwner !is null); 4623 version(Windows) { 4624 if(OpenClipboard(clipboardOwner.impl.hwnd) == 0) 4625 throw new Exception("OpenClipboard"); 4626 scope(exit) 4627 CloseClipboard(); 4628 EmptyClipboard(); 4629 auto sz = sizeOfConvertedWstring(text, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 4630 auto handle = GlobalAlloc(GMEM_MOVEABLE, sz * 2); // zero terminated wchars 4631 if(handle is null) throw new Exception("GlobalAlloc"); 4632 if(auto data = cast(wchar*) GlobalLock(handle)) { 4633 auto slice = data[0 .. sz]; 4634 scope(failure) 4635 GlobalUnlock(handle); 4636 4637 auto str = makeWindowsString(text, slice, WindowsStringConversionFlags.convertNewLines | WindowsStringConversionFlags.zeroTerminate); 4638 4639 GlobalUnlock(handle); 4640 SetClipboardData(CF_UNICODETEXT, handle); 4641 } 4642 } else version(X11) { 4643 setX11Selection!"CLIPBOARD"(clipboardOwner, text); 4644 } else version(OSXCocoa) { 4645 throw new NotYetImplementedException(); 4646 } else static assert(0); 4647 } 4648 4649 // 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. 4650 4651 version(X11) { 4652 // and the PRIMARY on X, be sure to put these in static if(UsingSimpledisplayX11) 4653 4654 private Atom*[] interredAtoms; // for discardAndRecreate 4655 4656 /// Platform specific for X11 4657 @property Atom GetAtom(string name, bool create = false)(Display* display) { 4658 static Atom a; 4659 if(!a) { 4660 a = XInternAtom(display, name, !create); 4661 interredAtoms ~= &a; 4662 } 4663 if(a == None) 4664 throw new Exception("XInternAtom " ~ name ~ " " ~ (create ? "true":"false")); 4665 return a; 4666 } 4667 4668 /// Platform specific for X11 - gets atom names as a string 4669 string getAtomName(Atom atom, Display* display) { 4670 auto got = XGetAtomName(display, atom); 4671 scope(exit) XFree(got); 4672 import core.stdc.string; 4673 string s = got[0 .. strlen(got)].idup; 4674 return s; 4675 } 4676 4677 /// Asserts ownership of PRIMARY and copies the text into a buffer that clients can request later 4678 void setPrimarySelection(SimpleWindow window, string text) { 4679 setX11Selection!"PRIMARY"(window, text); 4680 } 4681 4682 /// Asserts ownership of SECONDARY and copies the text into a buffer that clients can request later 4683 void setSecondarySelection(SimpleWindow window, string text) { 4684 setX11Selection!"SECONDARY"(window, text); 4685 } 4686 4687 /// The `after` delegate is called after a client requests the UTF8_STRING thing. it is a mega-hack right now! 4688 void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) { 4689 assert(window !is null); 4690 4691 auto display = XDisplayConnection.get(); 4692 static if (atomName == "PRIMARY") Atom a = XA_PRIMARY; 4693 else static if (atomName == "SECONDARY") Atom a = XA_SECONDARY; 4694 else Atom a = GetAtom!atomName(display); 4695 XSetSelectionOwner(display, a, window.impl.window, 0 /* CurrentTime */); 4696 window.impl.setSelectionHandler = (XEvent ev) { 4697 XSelectionRequestEvent* event = &ev.xselectionrequest; 4698 XSelectionEvent selectionEvent; 4699 selectionEvent.type = EventType.SelectionNotify; 4700 selectionEvent.display = event.display; 4701 selectionEvent.requestor = event.requestor; 4702 selectionEvent.selection = event.selection; 4703 selectionEvent.time = event.time; 4704 selectionEvent.target = event.target; 4705 4706 if(event.property == None) 4707 selectionEvent.property = event.target; 4708 if(event.target == GetAtom!"TARGETS"(display)) { 4709 /* respond with the supported types */ 4710 Atom[3] tlist;// = [XA_UTF8, XA_STRING, XA_TARGETS]; 4711 tlist[0] = GetAtom!"UTF8_STRING"(display); 4712 tlist[1] = XA_STRING; 4713 tlist[2] = GetAtom!"TARGETS"(display); 4714 XChangeProperty(display, event.requestor, event.property, XA_ATOM, 32, PropModeReplace, cast(void*)tlist.ptr, 3); 4715 selectionEvent.property = event.property; 4716 } else if(event.target == XA_STRING) { 4717 selectionEvent.property = event.property; 4718 XChangeProperty (display, 4719 selectionEvent.requestor, 4720 selectionEvent.property, 4721 event.target, 4722 8 /* bits */, 0 /* PropModeReplace */, 4723 text.ptr, cast(int) text.length); 4724 } else if(event.target == GetAtom!"UTF8_STRING"(display)) { 4725 selectionEvent.property = event.property; 4726 XChangeProperty (display, 4727 selectionEvent.requestor, 4728 selectionEvent.property, 4729 event.target, 4730 8 /* bits */, 0 /* PropModeReplace */, 4731 text.ptr, cast(int) text.length); 4732 4733 if(after) 4734 after(); 4735 } else { 4736 selectionEvent.property = None; // I don't know how to handle this type... 4737 } 4738 4739 XSendEvent(display, selectionEvent.requestor, false, 0, cast(XEvent*) &selectionEvent); 4740 }; 4741 } 4742 4743 /// 4744 void getPrimarySelection(SimpleWindow window, void delegate(in char[]) handler) { 4745 getX11Selection!"PRIMARY"(window, handler); 4746 } 4747 4748 /// 4749 void getX11Selection(string atomName)(SimpleWindow window, void delegate(in char[]) handler) { 4750 assert(window !is null); 4751 4752 auto display = XDisplayConnection.get(); 4753 auto atom = GetAtom!atomName(display); 4754 4755 window.impl.getSelectionHandler = handler; 4756 4757 auto target = GetAtom!"TARGETS"(display); 4758 4759 // SDD_DATA is "simpledisplay.d data" 4760 XConvertSelection(display, atom, target, GetAtom!("SDD_DATA", true)(display), window.impl.window, 0 /*CurrentTime*/); 4761 } 4762 4763 /// 4764 void[] getX11PropertyData(Window window, Atom property, Atom type = AnyPropertyType) { 4765 Atom actualType; 4766 int actualFormat; 4767 arch_ulong actualItems; 4768 arch_ulong bytesRemaining; 4769 void* data; 4770 4771 auto display = XDisplayConnection.get(); 4772 if(XGetWindowProperty(display, window, property, 0, 0x7fffffff, false, type, &actualType, &actualFormat, &actualItems, &bytesRemaining, &data) == Success) { 4773 if(actualFormat == 0) 4774 return null; 4775 else { 4776 int byteLength; 4777 if(actualFormat == 32) { 4778 // 32 means it is a C long... which is variable length 4779 actualFormat = cast(int) arch_long.sizeof * 8; 4780 } 4781 4782 // then it is just a bit count 4783 byteLength = cast(int) (actualItems * actualFormat / 8); 4784 4785 auto d = new ubyte[](byteLength); 4786 d[] = cast(ubyte[]) data[0 .. byteLength]; 4787 XFree(data); 4788 return d; 4789 } 4790 } 4791 return null; 4792 } 4793 4794 /* defined in the systray spec */ 4795 enum SYSTEM_TRAY_REQUEST_DOCK = 0; 4796 enum SYSTEM_TRAY_BEGIN_MESSAGE = 1; 4797 enum SYSTEM_TRAY_CANCEL_MESSAGE = 2; 4798 4799 4800 /** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing 4801 * instead of delegates, you can subclass this, and override `doHandle()` method. */ 4802 public class GlobalHotkey { 4803 KeyEvent key; 4804 void delegate () handler; 4805 4806 void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager 4807 4808 /// Create from initialzed KeyEvent object 4809 this (KeyEvent akey, void delegate () ahandler=null) { 4810 if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey"); 4811 key = akey; 4812 handler = ahandler; 4813 } 4814 4815 /// Create from emacs-like key name ("C-M-Y", etc.) 4816 this (const(char)[] akey, void delegate () ahandler=null) { 4817 key = KeyEvent.parse(akey); 4818 if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey"); 4819 handler = ahandler; 4820 } 4821 4822 } 4823 4824 private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc { 4825 //conwriteln("failed to grab key"); 4826 GlobalHotkeyManager.ghfailed = true; 4827 return 0; 4828 } 4829 4830 private extern(C) int adrlogger (Display* dpy, XErrorEvent* evt) nothrow @nogc { 4831 import core.stdc.stdio; 4832 char[265] buffer; 4833 XGetErrorText(dpy, evt.error_code, buffer.ptr, cast(int) buffer.length); 4834 printf("ERROR: %s\n", buffer.ptr); 4835 return 0; 4836 } 4837 4838 /++ 4839 Global hotkey manager. It contains static methods to manage global hotkeys. 4840 4841 --- 4842 try { 4843 GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); }); 4844 } catch (Exception e) { 4845 conwriteln("ERROR registering hotkey!"); 4846 } 4847 --- 4848 4849 The key strings are based on Emacs. In practical terms, 4850 `M` means `alt` and `H` means the Windows logo key. `C` 4851 is `ctrl`. 4852 4853 $(WARNING 4854 This is X-specific right now. If you are on 4855 Windows, try [registerHotKey] instead. 4856 4857 We will probably merge these into a single 4858 interface later. 4859 ) 4860 +/ 4861 public class GlobalHotkeyManager : CapableOfHandlingNativeEvent { 4862 version(X11) { 4863 void recreateAfterDisconnect() { 4864 throw new Exception("NOT IMPLEMENTED"); 4865 } 4866 void discardConnectionState() { 4867 throw new Exception("NOT IMPLEMENTED"); 4868 } 4869 } 4870 4871 private static immutable uint[8] masklist = [ 0, 4872 KeyOrButtonMask.LockMask, 4873 KeyOrButtonMask.Mod2Mask, 4874 KeyOrButtonMask.Mod3Mask, 4875 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask, 4876 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask, 4877 KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 4878 KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask, 4879 ]; 4880 private __gshared GlobalHotkeyManager ghmanager; 4881 private __gshared bool ghfailed = false; 4882 4883 private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc { 4884 if (modmask == 0) return false; 4885 if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false; 4886 if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false; 4887 return true; 4888 } 4889 4890 private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc { 4891 modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll 4892 modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers 4893 return modmask; 4894 } 4895 4896 private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) { 4897 uint keycode = cast(uint)ke.key; 4898 auto dpy = XDisplayConnection.get; 4899 return XKeysymToKeycode(dpy, keycode); 4900 } 4901 4902 private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; } 4903 4904 private __gshared GlobalHotkey[ulong] globalHotkeyList; 4905 4906 NativeEventHandler getNativeEventHandler () { 4907 return delegate int (XEvent e) { 4908 if (e.type != EventType.KeyPress) return 1; 4909 auto kev = cast(const(XKeyEvent)*)&e; 4910 auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state)); 4911 if (auto ghkp = hash in globalHotkeyList) { 4912 try { 4913 ghkp.doHandle(); 4914 } catch (Exception e) { 4915 import core.stdc.stdio : stderr, fprintf; 4916 stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr); 4917 } 4918 } 4919 return 1; 4920 }; 4921 } 4922 4923 private this () { 4924 auto dpy = XDisplayConnection.get; 4925 auto root = RootWindow(dpy, DefaultScreen(dpy)); 4926 CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this; 4927 XDisplayConnection.addRootInput(EventMask.KeyPressMask); 4928 } 4929 4930 /// Register new global hotkey with initialized `GlobalHotkey` object. 4931 /// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken). 4932 static void register (GlobalHotkey gh) { 4933 if (gh is null) return; 4934 if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey"); 4935 4936 auto dpy = XDisplayConnection.get; 4937 immutable keycode = keyEvent2KeyCode(gh.key); 4938 4939 auto hash = keyCode2Hash(keycode, gh.key.modifierState); 4940 if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey"); 4941 if (ghmanager is null) ghmanager = new GlobalHotkeyManager(); 4942 XSync(dpy, 0/*False*/); 4943 4944 Window root = RootWindow(dpy, DefaultScreen(dpy)); 4945 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 4946 ghfailed = false; 4947 foreach (immutable uint ormask; masklist[]) { 4948 XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync); 4949 } 4950 XSync(dpy, 0/*False*/); 4951 XSetErrorHandler(savedErrorHandler); 4952 4953 if (ghfailed) { 4954 savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 4955 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root); 4956 XSync(dpy, 0/*False*/); 4957 XSetErrorHandler(savedErrorHandler); 4958 throw new Exception("cannot register global hotkey"); 4959 } 4960 4961 globalHotkeyList[hash] = gh; 4962 } 4963 4964 /// Ditto 4965 static void register (const(char)[] akey, void delegate () ahandler) { 4966 register(new GlobalHotkey(akey, ahandler)); 4967 } 4968 4969 private static void removeByHash (ulong hash) { 4970 if (auto ghp = hash in globalHotkeyList) { 4971 auto dpy = XDisplayConnection.get; 4972 immutable keycode = keyEvent2KeyCode(ghp.key); 4973 Window root = RootWindow(dpy, DefaultScreen(dpy)); 4974 XSync(dpy, 0/*False*/); 4975 XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler); 4976 foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root); 4977 XSync(dpy, 0/*False*/); 4978 XSetErrorHandler(savedErrorHandler); 4979 globalHotkeyList.remove(hash); 4980 } 4981 } 4982 4983 /// Register new global hotkey with previously used `GlobalHotkey` object. 4984 /// It is safe to unregister unknown or invalid hotkey. 4985 static void unregister (GlobalHotkey gh) { 4986 //TODO: add second AA for faster search? prolly doesn't worth it. 4987 if (gh is null) return; 4988 foreach (const ref kv; globalHotkeyList.byKeyValue) { 4989 if (kv.value is gh) { 4990 removeByHash(kv.key); 4991 return; 4992 } 4993 } 4994 } 4995 4996 /// Ditto. 4997 static void unregister (const(char)[] key) { 4998 auto kev = KeyEvent.parse(key); 4999 immutable keycode = keyEvent2KeyCode(kev); 5000 removeByHash(keyCode2Hash(keycode, kev.modifierState)); 5001 } 5002 } 5003 } 5004 5005 version(Windows) { 5006 /// Platform-specific for Windows. Sends a string as key press and release events to the actively focused window (not necessarily your application) 5007 void sendSyntheticInput(wstring s) { 5008 INPUT[] inputs; 5009 inputs.reserve(s.length * 2); 5010 5011 foreach(wchar c; s) { 5012 INPUT input; 5013 input.type = INPUT_KEYBOARD; 5014 input.ki.wScan = c; 5015 input.ki.dwFlags = KEYEVENTF_UNICODE; 5016 inputs ~= input; 5017 5018 input.ki.dwFlags |= KEYEVENTF_KEYUP; 5019 inputs ~= input; 5020 } 5021 5022 if(SendInput(cast(int) inputs.length, inputs.ptr, INPUT.sizeof) != inputs.length) { 5023 throw new Exception("SendInput failed"); 5024 } 5025 } 5026 5027 5028 5029 5030 // global hotkey helper function 5031 5032 /// Platform-specific for Windows. Registers a global hotkey. Returns a registration ID. 5033 int registerHotKey(SimpleWindow window, UINT modifiers, UINT vk, void delegate() handler) { 5034 __gshared int hotkeyId = 0; 5035 int id = ++hotkeyId; 5036 if(!RegisterHotKey(window.impl.hwnd, id, modifiers, vk)) 5037 throw new Exception("RegisterHotKey failed"); 5038 5039 __gshared void delegate()[WPARAM][HWND] handlers; 5040 5041 handlers[window.impl.hwnd][id] = handler; 5042 5043 int delegate(HWND, UINT, WPARAM, LPARAM) oldHandler; 5044 5045 auto nativeEventHandler = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 5046 switch(msg) { 5047 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646279%28v=vs.85%29.aspx 5048 case WM_HOTKEY: 5049 if(auto list = hwnd in handlers) { 5050 if(auto h = wParam in *list) { 5051 (*h)(); 5052 return 0; 5053 } 5054 } 5055 goto default; 5056 default: 5057 } 5058 if(oldHandler) 5059 return oldHandler(hwnd, msg, wParam, lParam); 5060 return 1; // pass it on 5061 }; 5062 5063 if(window.handleNativeEvent.funcptr !is nativeEventHandler.funcptr) { 5064 oldHandler = window.handleNativeEvent; 5065 window.handleNativeEvent = nativeEventHandler; 5066 } 5067 5068 return id; 5069 } 5070 5071 /// Platform-specific for Windows. Unregisters a key. The id is the value returned by registerHotKey. 5072 void unregisterHotKey(SimpleWindow window, int id) { 5073 if(!UnregisterHotKey(window.impl.hwnd, id)) 5074 throw new Exception("UnregisterHotKey"); 5075 } 5076 } 5077 5078 5079 5080 /++ 5081 [ScreenPainter] operations can use different operations to combine the color with the color on screen. 5082 5083 See_Also: 5084 $(LIST 5085 *[ScreenPainter] 5086 *[ScreenPainter.rasterOp] 5087 ) 5088 +/ 5089 enum RasterOp { 5090 normal, /// Replaces the pixel. 5091 xor, /// Uses bitwise xor to draw. 5092 } 5093 5094 // being phobos-free keeps the size WAY down 5095 private const(char)* toStringz(string s) { return (s ~ '\0').ptr; } 5096 package(arsd) const(wchar)* toWStringz(wstring s) { return (s ~ '\0').ptr; } 5097 package(arsd) const(wchar)* toWStringz(string s) { 5098 wstring r; 5099 foreach(dchar c; s) 5100 r ~= c; 5101 r ~= '\0'; 5102 return r.ptr; 5103 } 5104 private string[] split(in void[] a, char c) { 5105 string[] ret; 5106 size_t previous = 0; 5107 foreach(i, char ch; cast(ubyte[]) a) { 5108 if(ch == c) { 5109 ret ~= cast(string) a[previous .. i]; 5110 previous = i + 1; 5111 } 5112 } 5113 if(previous != a.length) 5114 ret ~= cast(string) a[previous .. $]; 5115 return ret; 5116 } 5117 5118 version(without_opengl) { 5119 enum OpenGlOptions { 5120 no, 5121 } 5122 } else { 5123 /++ 5124 Determines if you want an OpenGL context created on the new window. 5125 5126 5127 See more: [#topics-3d|in the 3d topic]. 5128 5129 --- 5130 import arsd.simpledisplay; 5131 void main() { 5132 auto window = new SimpleWindow(500, 500, "OpenGL Test", OpenGlOptions.yes); 5133 5134 // Set up the matrix 5135 window.setAsCurrentOpenGlContext(); // make this window active 5136 5137 // This is called on each frame, we will draw our scene 5138 window.redrawOpenGlScene = delegate() { 5139 5140 }; 5141 5142 window.eventLoop(0); 5143 } 5144 --- 5145 +/ 5146 enum OpenGlOptions { 5147 no, /// No OpenGL context is created 5148 yes, /// Yes, create an OpenGL context 5149 } 5150 5151 version(X11) { 5152 static if (!SdpyIsUsingIVGLBinds) { 5153 pragma(lib, "GL"); 5154 pragma(lib, "GLU"); 5155 } 5156 } else version(Windows) { 5157 static if (!SdpyIsUsingIVGLBinds) { 5158 pragma(lib, "opengl32"); 5159 pragma(lib, "glu32"); 5160 } 5161 } else 5162 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."); 5163 } 5164 5165 deprecated("Sorry, I misspelled it in the first version! Use `Resizability` instead.") 5166 alias Resizablity = Resizability; 5167 5168 /// When you create a SimpleWindow, you can see its resizability to be one of these via the constructor... 5169 enum Resizability { 5170 fixedSize, /// the window cannot be resized 5171 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. 5172 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. 5173 5174 // FIXME: automaticallyScaleIfPossible should adjust the OpenGL viewport on resize events 5175 } 5176 5177 5178 /++ 5179 Alignment for $(ScreenPainter.drawText). Left, Center, or Right may be combined with VerticalTop, VerticalCenter, or VerticalBottom via bitwise or. 5180 +/ 5181 enum TextAlignment : uint { 5182 Left = 0, /// 5183 Center = 1, /// 5184 Right = 2, /// 5185 5186 VerticalTop = 0, /// 5187 VerticalCenter = 4, /// 5188 VerticalBottom = 8, /// 5189 } 5190 5191 public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily. 5192 alias Rectangle = arsd.color.Rectangle; 5193 5194 5195 /++ 5196 Keyboard press and release events 5197 +/ 5198 struct KeyEvent { 5199 /// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms. See [Key] 5200 Key key; 5201 ubyte hardwareCode; /// A platform and hardware specific code for the key 5202 bool pressed; /// true if the key was just pressed, false if it was just released. note: released events aren't always sent... 5203 5204 dchar character; /// 5205 5206 uint modifierState; /// see enum [ModifierState]. They are bitwise combined together. 5207 5208 SimpleWindow window; /// associated Window 5209 5210 // convert key event to simplified string representation a-la emacs 5211 const(char)[] toStrBuf(bool growdest=false) (char[] dest) const nothrow @trusted { 5212 uint dpos = 0; 5213 void put (const(char)[] s...) nothrow @trusted { 5214 static if (growdest) { 5215 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; else { dest ~= ch; ++dpos; } 5216 } else { 5217 foreach (char ch; s) if (dpos < dest.length) dest.ptr[dpos++] = ch; 5218 } 5219 } 5220 5221 void putMod (ModifierState mod, Key key, string text) nothrow @trusted { 5222 if ((this.modifierState&mod) != 0 && (this.pressed || this.key != key)) put(text); 5223 } 5224 5225 if (!this.key && !(this.modifierState&(ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows))) return null; 5226 5227 // put modifiers 5228 // releasing modifier keys can produce bizarre things like "Ctrl+Ctrl", so hack around it 5229 putMod(ModifierState.ctrl, Key.Ctrl, "Ctrl+"); 5230 putMod(ModifierState.alt, Key.Alt, "Alt+"); 5231 putMod(ModifierState.windows, Key.Shift, "Windows+"); 5232 putMod(ModifierState.shift, Key.Shift, "Shift+"); 5233 5234 if (this.key) { 5235 foreach (string kn; __traits(allMembers, Key)) { 5236 if (this.key == __traits(getMember, Key, kn)) { 5237 // HACK! 5238 static if (kn == "N0") put("0"); 5239 else static if (kn == "N1") put("1"); 5240 else static if (kn == "N2") put("2"); 5241 else static if (kn == "N3") put("3"); 5242 else static if (kn == "N4") put("4"); 5243 else static if (kn == "N5") put("5"); 5244 else static if (kn == "N6") put("6"); 5245 else static if (kn == "N7") put("7"); 5246 else static if (kn == "N8") put("8"); 5247 else static if (kn == "N9") put("9"); 5248 else put(kn); 5249 return dest[0..dpos]; 5250 } 5251 } 5252 put("Unknown"); 5253 } else { 5254 if (dpos && dest[dpos-1] == '+') --dpos; 5255 } 5256 return dest[0..dpos]; 5257 } 5258 5259 string toStr() () { return cast(string)toStrBuf!true(null); } // it is safe to cast here 5260 5261 /** Parse string into key name with modifiers. It accepts things like: 5262 * 5263 * C-H-1 -- emacs style (ctrl, and windows, and 1) 5264 * 5265 * Ctrl+Win+1 -- windows style 5266 * 5267 * Ctrl-Win-1 -- '-' is a valid delimiter too 5268 * 5269 * Ctrl Win 1 -- and space 5270 * 5271 * and even "Win + 1 + Ctrl". 5272 */ 5273 static KeyEvent parse (const(char)[] name, bool* ignoreModsOut=null, int* updown=null) nothrow @trusted @nogc { 5274 auto nanchor = name; // keep it anchored, 'cause `name` may have NO_INTERIOR set 5275 5276 // remove trailing spaces 5277 while (name.length && name[$-1] <= ' ') name = name[0..$-1]; 5278 5279 // tokens delimited by blank, '+', or '-' 5280 // null on eol 5281 const(char)[] getToken () nothrow @trusted @nogc { 5282 // remove leading spaces and delimiters 5283 while (name.length && (name[0] <= ' ' || name[0] == '+' || name[0] == '-')) name = name[1..$]; 5284 if (name.length == 0) return null; // oops, no more tokens 5285 // get token 5286 size_t epos = 0; 5287 while (epos < name.length && name[epos] > ' ' && name[epos] != '+' && name[epos] != '-') ++epos; 5288 assert(epos > 0 && epos <= name.length); 5289 auto res = name[0..epos]; 5290 name = name[epos..$]; 5291 return res; 5292 } 5293 5294 static bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 5295 if (s0.length != s1.length) return false; 5296 foreach (immutable ci, char c0; s0) { 5297 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man's tolower 5298 char c1 = s1[ci]; 5299 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's tolower 5300 if (c0 != c1) return false; 5301 } 5302 return true; 5303 } 5304 5305 if (ignoreModsOut !is null) *ignoreModsOut = false; 5306 if (updown !is null) *updown = -1; 5307 KeyEvent res; 5308 res.key = cast(Key)0; // just in case 5309 const(char)[] tk, tkn; // last token 5310 bool allowEmascStyle = true; 5311 bool ignoreModifiers = false; 5312 tokenloop: for (;;) { 5313 tk = tkn; 5314 tkn = getToken(); 5315 //k8: yay, i took "Bloody Mess" trait from Fallout! 5316 if (tkn.length != 0 && tk.length == 0) { tk = tkn; continue tokenloop; } 5317 if (tkn.length == 0 && tk.length == 0) break; // no more tokens 5318 if (allowEmascStyle && tkn.length != 0) { 5319 if (tk.length == 1) { 5320 char mdc = tk[0]; 5321 if (mdc >= 'a' && mdc <= 'z') mdc -= 32; // poor man's toupper() 5322 if (mdc == 'C' && (res.modifierState&ModifierState.ctrl) == 0) {res.modifierState |= ModifierState.ctrl; continue tokenloop; } 5323 if (mdc == 'M' && (res.modifierState&ModifierState.alt) == 0) { res.modifierState |= ModifierState.alt; continue tokenloop; } 5324 if (mdc == 'H' && (res.modifierState&ModifierState.windows) == 0) { res.modifierState |= ModifierState.windows; continue tokenloop; } 5325 if (mdc == 'S' && (res.modifierState&ModifierState.shift) == 0) { res.modifierState |= ModifierState.shift; continue tokenloop; } 5326 if (mdc == '*') { ignoreModifiers = true; continue tokenloop; } 5327 if (mdc == 'U' || mdc == 'R') { if (updown !is null) *updown = 0; continue tokenloop; } 5328 if (mdc == 'D' || mdc == 'P') { if (updown !is null) *updown = 1; continue tokenloop; } 5329 } 5330 } 5331 allowEmascStyle = false; 5332 if (strEquCI(tk, "Ctrl")) { res.modifierState |= ModifierState.ctrl; continue tokenloop; } 5333 if (strEquCI(tk, "Alt")) { res.modifierState |= ModifierState.alt; continue tokenloop; } 5334 if (strEquCI(tk, "Win") || strEquCI(tk, "Windows")) { res.modifierState |= ModifierState.windows; continue tokenloop; } 5335 if (strEquCI(tk, "Shift")) { res.modifierState |= ModifierState.shift; continue tokenloop; } 5336 if (strEquCI(tk, "Release")) { if (updown !is null) *updown = 0; continue tokenloop; } 5337 if (strEquCI(tk, "Press")) { if (updown !is null) *updown = 1; continue tokenloop; } 5338 if (tk == "*") { ignoreModifiers = true; continue tokenloop; } 5339 if (tk.length == 0) continue; 5340 // try key name 5341 if (res.key == 0) { 5342 // little hack 5343 if (tk.length == 1 && tk[0] >= '0' && tk[0] <= '9') { 5344 final switch (tk[0]) { 5345 case '0': tk = "N0"; break; 5346 case '1': tk = "N1"; break; 5347 case '2': tk = "N2"; break; 5348 case '3': tk = "N3"; break; 5349 case '4': tk = "N4"; break; 5350 case '5': tk = "N5"; break; 5351 case '6': tk = "N6"; break; 5352 case '7': tk = "N7"; break; 5353 case '8': tk = "N8"; break; 5354 case '9': tk = "N9"; break; 5355 } 5356 } 5357 foreach (string kn; __traits(allMembers, Key)) { 5358 if (strEquCI(tk, kn)) { res.key = __traits(getMember, Key, kn); continue tokenloop; } 5359 } 5360 } 5361 // unknown or duplicate key name, get out of here 5362 break; 5363 } 5364 if (ignoreModsOut !is null) *ignoreModsOut = ignoreModifiers; 5365 return res; // something 5366 } 5367 5368 bool opEquals() (const(char)[] name) const nothrow @trusted @nogc { 5369 enum modmask = (ModifierState.ctrl|ModifierState.alt|ModifierState.shift|ModifierState.windows); 5370 void doModKey (ref uint mask, ref Key kk, Key k, ModifierState mst) { 5371 if (kk == k) { mask |= mst; kk = cast(Key)0; } 5372 } 5373 bool ignoreMods; 5374 int updown; 5375 auto ke = KeyEvent.parse(name, &ignoreMods, &updown); 5376 if ((updown == 0 && this.pressed) || (updown == 1 && !this.pressed)) return false; 5377 if (this.key != ke.key) { 5378 // things like "ctrl+alt" are complicated 5379 uint tkm = this.modifierState&modmask; 5380 uint kkm = ke.modifierState&modmask; 5381 Key tk = this.key; 5382 // ke 5383 doModKey(kkm, ke.key, Key.Ctrl, ModifierState.ctrl); 5384 doModKey(kkm, ke.key, Key.Alt, ModifierState.alt); 5385 doModKey(kkm, ke.key, Key.Windows, ModifierState.windows); 5386 doModKey(kkm, ke.key, Key.Shift, ModifierState.shift); 5387 // this 5388 doModKey(tkm, tk, Key.Ctrl, ModifierState.ctrl); 5389 doModKey(tkm, tk, Key.Alt, ModifierState.alt); 5390 doModKey(tkm, tk, Key.Windows, ModifierState.windows); 5391 doModKey(tkm, tk, Key.Shift, ModifierState.shift); 5392 return (tk == ke.key && tkm == kkm); 5393 } 5394 return (ignoreMods || ((this.modifierState&modmask) == (ke.modifierState&modmask))); 5395 } 5396 } 5397 5398 /// sets the application name. 5399 @property string ApplicationName(string name) { 5400 return _applicationName = name; 5401 } 5402 5403 string _applicationName; 5404 5405 /// ditto 5406 @property string ApplicationName() { 5407 if(_applicationName is null) { 5408 import core.runtime; 5409 return Runtime.args[0]; 5410 } 5411 return _applicationName; 5412 } 5413 5414 5415 /// Type of a [MouseEvent] 5416 enum MouseEventType : int { 5417 motion = 0, /// The mouse moved inside the window 5418 buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 5419 buttonReleased = 2, /// A mouse button was released 5420 } 5421 5422 // FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily 5423 /++ 5424 Listen for this on your event listeners if you are interested in mouse action. 5425 5426 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. 5427 5428 Examples: 5429 5430 This will draw boxes on the window with the mouse as you hold the left button. 5431 --- 5432 import arsd.simpledisplay; 5433 5434 void main() { 5435 auto window = new SimpleWindow(); 5436 5437 window.eventLoop(0, 5438 (MouseEvent ev) { 5439 if(ev.modifierState & ModifierState.leftButtonDown) { 5440 auto painter = window.draw(); 5441 painter.fillColor = Color.red; 5442 painter.outlineColor = Color.black; 5443 painter.drawRectangle(Point(ev.x / 16 * 16, ev.y / 16 * 16), 16, 16); 5444 } 5445 } 5446 ); 5447 } 5448 --- 5449 +/ 5450 struct MouseEvent { 5451 MouseEventType type; /// movement, press, release, double click. See [MouseEventType] 5452 5453 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. 5454 int y; /// Current Y position of the cursor when the event fired. 5455 5456 int dx; /// Change in X position since last report 5457 int dy; /// Change in Y position since last report 5458 5459 MouseButton button; /// See [MouseButton] 5460 int modifierState; /// See [ModifierState] 5461 5462 /// Returns a linear representation of mouse button, 5463 /// for use with static arrays. Guaranteed to be >= 0 && <= 15 5464 /// 5465 /// Its implementation is based on range-limiting `core.bitop.bsf(button) + 1`. 5466 @property ubyte buttonLinear() const { 5467 import core.bitop; 5468 if(button == 0) 5469 return 0; 5470 return (bsf(button) + 1) & 0b1111; 5471 } 5472 5473 bool doubleClick; /// was it a double click? Only set on type == [MouseEventType.buttonPressed] 5474 5475 SimpleWindow window; /// The window in which the event happened. 5476 5477 Point globalCoordinates() { 5478 Point p; 5479 if(window is null) 5480 throw new Exception("wtf"); 5481 static if(UsingSimpledisplayX11) { 5482 Window child; 5483 XTranslateCoordinates( 5484 XDisplayConnection.get, 5485 window.impl.window, 5486 RootWindow(XDisplayConnection.get, DefaultScreen(XDisplayConnection.get)), 5487 x, y, &p.x, &p.y, &child); 5488 return p; 5489 } else version(Windows) { 5490 POINT[1] points; 5491 points[0].x = x; 5492 points[0].y = y; 5493 MapWindowPoints( 5494 window.impl.hwnd, 5495 null, 5496 points.ptr, 5497 points.length 5498 ); 5499 p.x = points[0].x; 5500 p.y = points[0].y; 5501 5502 return p; 5503 } else version(OSXCocoa) { 5504 throw new NotYetImplementedException(); 5505 } else static assert(0); 5506 } 5507 5508 bool opEquals() (const(char)[] str) pure nothrow @trusted @nogc { return equStr(this, str); } 5509 5510 /** 5511 can contain emacs-like modifier prefix 5512 case-insensitive names: 5513 lmbX/leftX 5514 rmbX/rightX 5515 mmbX/middleX 5516 wheelX 5517 motion (no prefix allowed) 5518 'X' is either "up" or "down" (or "-up"/"-down"); if omited, means "down" 5519 */ 5520 static bool equStr() (in auto ref MouseEvent event, const(char)[] str) pure nothrow @trusted @nogc { 5521 if (str.length == 0) return false; // just in case 5522 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln("str=<", str, ">"); } 5523 enum Flag : uint { Up = 0x8000_0000U, Down = 0x4000_0000U, Any = 0x1000_0000U } 5524 auto anchor = str; 5525 uint mods = 0; // uint.max == any 5526 // interesting bits in kmod 5527 uint kmodmask = 5528 ModifierState.shift| 5529 ModifierState.ctrl| 5530 ModifierState.alt| 5531 ModifierState.windows| 5532 ModifierState.leftButtonDown| 5533 ModifierState.middleButtonDown| 5534 ModifierState.rightButtonDown| 5535 0; 5536 uint lastButt = uint.max; // otherwise, bit 31 means "down" 5537 bool wasButtons = false; 5538 while (str.length) { 5539 if (str.ptr[0] <= ' ') { 5540 while (str.length && str.ptr[0] <= ' ') str = str[1..$]; 5541 continue; 5542 } 5543 // one-letter modifier? 5544 if (str.length >= 2 && str.ptr[1] == '-') { 5545 switch (str.ptr[0]) { 5546 case '*': // "any" modifier (cannot be undone) 5547 mods = mods.max; 5548 break; 5549 case 'C': case 'c': // emacs "ctrl" 5550 if (mods != mods.max) mods |= ModifierState.ctrl; 5551 break; 5552 case 'M': case 'm': // emacs "meta" 5553 if (mods != mods.max) mods |= ModifierState.alt; 5554 break; 5555 case 'S': case 's': // emacs "shift" 5556 if (mods != mods.max) mods |= ModifierState.shift; 5557 break; 5558 case 'H': case 'h': // emacs "hyper" (aka winkey) 5559 if (mods != mods.max) mods |= ModifierState.windows; 5560 break; 5561 default: 5562 return false; // unknown modifier 5563 } 5564 str = str[2..$]; 5565 continue; 5566 } 5567 // word 5568 char[16] buf = void; // locased 5569 auto wep = 0; 5570 while (str.length) { 5571 immutable char ch = str.ptr[0]; 5572 if (ch <= ' ' || ch == '-') break; 5573 str = str[1..$]; 5574 if (wep > buf.length) return false; // too long 5575 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 5576 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 5577 else return false; // invalid char 5578 } 5579 if (wep == 0) return false; // just in case 5580 uint bnum; 5581 enum UpDown { None = -1, Up, Down, Any } 5582 auto updown = UpDown.None; // 0: up; 1: down 5583 switch (buf[0..wep]) { 5584 // left button 5585 case "lmbup": case "leftup": updown = UpDown.Up; goto case "lmb"; 5586 case "lmbdown": case "leftdown": updown = UpDown.Down; goto case "lmb"; 5587 case "lmbany": case "leftany": updown = UpDown.Any; goto case "lmb"; 5588 case "lmb": case "left": bnum = 0; break; 5589 // middle button 5590 case "mmbup": case "middleup": updown = UpDown.Up; goto case "mmb"; 5591 case "mmbdown": case "middledown": updown = UpDown.Down; goto case "mmb"; 5592 case "mmbany": case "middleany": updown = UpDown.Any; goto case "mmb"; 5593 case "mmb": case "middle": bnum = 1; break; 5594 // right button 5595 case "rmbup": case "rightup": updown = UpDown.Up; goto case "rmb"; 5596 case "rmbdown": case "rightdown": updown = UpDown.Down; goto case "rmb"; 5597 case "rmbany": case "rightany": updown = UpDown.Any; goto case "rmb"; 5598 case "rmb": case "right": bnum = 2; break; 5599 // wheel 5600 case "wheelup": updown = UpDown.Up; goto case "wheel"; 5601 case "wheeldown": updown = UpDown.Down; goto case "wheel"; 5602 case "wheelany": updown = UpDown.Any; goto case "wheel"; 5603 case "wheel": bnum = 3; break; 5604 // motion 5605 case "motion": bnum = 7; break; 5606 // unknown 5607 default: return false; 5608 } 5609 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 0: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 5610 // parse possible "-up" or "-down" 5611 if (updown == UpDown.None && bnum < 7 && str.length > 0 && str.ptr[0] == '-') { 5612 wep = 0; 5613 foreach (immutable idx, immutable char ch; str[1..$]) { 5614 if (ch <= ' ' || ch == '-') break; 5615 assert(idx == wep); // for now; trick 5616 if (wep > buf.length) { wep = 0; break; } // too long 5617 if (ch >= 'A' && ch <= 'Z') buf.ptr[wep++] = cast(char)(ch+32); // poor man tolower 5618 else if (ch >= 'a' && ch <= 'z') buf.ptr[wep++] = ch; 5619 else { wep = 0; break; } // invalid char 5620 } 5621 if (wep == 2 && buf[0..wep] == "up") updown = UpDown.Up; 5622 else if (wep == 4 && buf[0..wep] == "down") updown = UpDown.Down; 5623 else if (wep == 3 && buf[0..wep] == "any") updown = UpDown.Any; 5624 // remove parsed part 5625 if (updown != UpDown.None) str = str[wep+1..$]; 5626 } 5627 if (updown == UpDown.None) { 5628 updown = UpDown.Down; 5629 } 5630 wasButtons = wasButtons || (bnum <= 2); 5631 //assert(updown != UpDown.None); 5632 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" 1: mods=0x%08x; bnum=%u; updown=%s [%s]", mods, bnum, updown, str); } 5633 // if we have a previous button, it goes to modifiers (unless it is a wheel or motion) 5634 if (lastButt != lastButt.max) { 5635 if ((lastButt&0xff) >= 3) return false; // wheel or motion 5636 if (mods != mods.max) { 5637 uint butbit = 0; 5638 final switch (lastButt&0x03) { 5639 case 0: butbit = ModifierState.leftButtonDown; break; 5640 case 1: butbit = ModifierState.middleButtonDown; break; 5641 case 2: butbit = ModifierState.rightButtonDown; break; 5642 } 5643 if (lastButt&Flag.Down) mods |= butbit; 5644 else if (lastButt&Flag.Up) mods &= ~butbit; 5645 else if (lastButt&Flag.Any) kmodmask &= ~butbit; 5646 } 5647 } 5648 // remember last button 5649 lastButt = bnum|(updown == UpDown.Up ? Flag.Up : updown == UpDown.Any ? Flag.Any : Flag.Down); 5650 } 5651 // no button -- nothing to do 5652 if (lastButt == lastButt.max) return false; 5653 // done parsing, check if something's left 5654 foreach (immutable char ch; str) if (ch > ' ') return false; // oops 5655 // remove action button from mask 5656 if ((lastButt&0xff) < 3) { 5657 final switch (lastButt&0x03) { 5658 case 0: kmodmask &= ~cast(uint)ModifierState.leftButtonDown; break; 5659 case 1: kmodmask &= ~cast(uint)ModifierState.middleButtonDown; break; 5660 case 2: kmodmask &= ~cast(uint)ModifierState.rightButtonDown; break; 5661 } 5662 } 5663 // special case: "Motion" means "ignore buttons" 5664 if ((lastButt&0xff) == 7 && !wasButtons) { 5665 debug(arsd_mevent_strcmp) { import iv.cmdcon; conwriteln(" *: special motion"); } 5666 kmodmask &= ~cast(uint)(ModifierState.leftButtonDown|ModifierState.middleButtonDown|ModifierState.rightButtonDown); 5667 } 5668 uint kmod = event.modifierState&kmodmask; 5669 debug(arsd_mevent_strcmp) { import iv.cmdcon; conprintfln(" *: mods=0x%08x; lastButt=0x%08x; kmod=0x%08x; type=%s", mods, lastButt, kmod, event.type); } 5670 // check modifier state 5671 if (mods != mods.max) { 5672 if (kmod != mods) return false; 5673 } 5674 // now check type 5675 if ((lastButt&0xff) == 7) { 5676 // motion 5677 if (event.type != MouseEventType.motion) return false; 5678 } else if ((lastButt&0xff) == 3) { 5679 // wheel 5680 if (lastButt&Flag.Up) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelUp); 5681 if (lastButt&Flag.Down) return (event.type == MouseEventType.buttonPressed && event.button == MouseButton.wheelDown); 5682 if (lastButt&Flag.Any) return (event.type == MouseEventType.buttonPressed && (event.button == MouseButton.wheelUp || event.button == MouseButton.wheelUp)); 5683 return false; 5684 } else { 5685 // buttons 5686 if (((lastButt&Flag.Down) != 0 && event.type != MouseEventType.buttonPressed) || 5687 ((lastButt&Flag.Up) != 0 && event.type != MouseEventType.buttonReleased)) 5688 { 5689 return false; 5690 } 5691 // button number 5692 switch (lastButt&0x03) { 5693 case 0: if (event.button != MouseButton.left) return false; break; 5694 case 1: if (event.button != MouseButton.middle) return false; break; 5695 case 2: if (event.button != MouseButton.right) return false; break; 5696 default: return false; 5697 } 5698 } 5699 return true; 5700 } 5701 } 5702 5703 version(arsd_mevent_strcmp_test) unittest { 5704 MouseEvent event; 5705 event.type = MouseEventType.buttonPressed; 5706 event.button = MouseButton.left; 5707 event.modifierState = ModifierState.ctrl; 5708 assert(event == "C-LMB"); 5709 assert(event != "C-LMBUP"); 5710 assert(event != "C-LMB-UP"); 5711 assert(event != "C-S-LMB"); 5712 assert(event == "*-LMB"); 5713 assert(event != "*-LMB-UP"); 5714 5715 event.type = MouseEventType.buttonReleased; 5716 assert(event != "C-LMB"); 5717 assert(event == "C-LMBUP"); 5718 assert(event == "C-LMB-UP"); 5719 assert(event != "C-S-LMB"); 5720 assert(event != "*-LMB"); 5721 assert(event == "*-LMB-UP"); 5722 5723 event.button = MouseButton.right; 5724 event.modifierState |= ModifierState.shift; 5725 event.type = MouseEventType.buttonPressed; 5726 assert(event != "C-LMB"); 5727 assert(event != "C-LMBUP"); 5728 assert(event != "C-LMB-UP"); 5729 assert(event != "C-S-LMB"); 5730 assert(event != "*-LMB"); 5731 assert(event != "*-LMB-UP"); 5732 5733 assert(event != "C-RMB"); 5734 assert(event != "C-RMBUP"); 5735 assert(event != "C-RMB-UP"); 5736 assert(event == "C-S-RMB"); 5737 assert(event == "*-RMB"); 5738 assert(event != "*-RMB-UP"); 5739 } 5740 5741 /// This gives a few more options to drawing lines and such 5742 struct Pen { 5743 Color color; /// the foreground color 5744 int width = 1; /// width of the line 5745 Style style; /// See [Style] 5746 /+ 5747 // From X.h 5748 5749 #define LineSolid 0 5750 #define LineOnOffDash 1 5751 #define LineDoubleDash 2 5752 LineDou- The full path of the line is drawn, but the 5753 bleDash even dashes are filled differently from the 5754 odd dashes (see fill-style) with CapButt 5755 style used where even and odd dashes meet. 5756 5757 5758 5759 /* capStyle */ 5760 5761 #define CapNotLast 0 5762 #define CapButt 1 5763 #define CapRound 2 5764 #define CapProjecting 3 5765 5766 /* joinStyle */ 5767 5768 #define JoinMiter 0 5769 #define JoinRound 1 5770 #define JoinBevel 2 5771 5772 /* fillStyle */ 5773 5774 #define FillSolid 0 5775 #define FillTiled 1 5776 #define FillStippled 2 5777 #define FillOpaqueStippled 3 5778 5779 5780 +/ 5781 /// Style of lines drawn 5782 enum Style { 5783 Solid, /// a solid line 5784 Dashed, /// a dashed line 5785 Dotted, /// a dotted line 5786 } 5787 } 5788 5789 5790 /++ 5791 Represents an in-memory image in the format that the GUI expects, but with its raw data available to your program. 5792 5793 5794 On Windows, this means a device-independent bitmap. On X11, it is an XImage. 5795 5796 $(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.) 5797 5798 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. 5799 5800 If you intend to draw an image to screen several times, you will want to convert it into a [Sprite]. 5801 5802 $(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. 5803 5804 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! 5805 5806 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!) 5807 5808 Please call `destroy(image);` when you are done with it. The easiest way to do this is with scope: 5809 5810 --- 5811 auto image = new Image(256, 256); 5812 scope(exit) destroy(image); 5813 --- 5814 5815 As long as you don't hold on to it outside the scope. 5816 5817 I might change it to be an owned pointer at some point in the future. 5818 5819 ) 5820 5821 Drawing pixels on the image may be simple, using the `opIndexAssign` function, but 5822 you can also often get a fair amount of speedup by getting the raw data format and 5823 writing some custom code. 5824 5825 FIXME INSERT EXAMPLES HERE 5826 5827 5828 +/ 5829 final class Image { 5830 /// 5831 this(int width, int height, bool forcexshm=false) { 5832 this.width = width; 5833 this.height = height; 5834 5835 impl.createImage(width, height, forcexshm); 5836 } 5837 5838 /// 5839 this(Size size, bool forcexshm=false) { 5840 this(size.width, size.height, forcexshm); 5841 } 5842 5843 ~this() { 5844 impl.dispose(); 5845 } 5846 5847 // these numbers are used for working with rawData itself, skipping putPixel and getPixel 5848 /// if you do the math yourself you might be able to optimize it. Call these functions only once and cache the value. 5849 pure const @system nothrow { 5850 /* 5851 To use these to draw a blue rectangle with size WxH at position X,Y... 5852 5853 // make certain that it will fit before we proceed 5854 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! 5855 5856 // gather all the values you'll need up front. These can be kept until the image changes size if you want 5857 // (though calculating them isn't really that expensive). 5858 auto nextLineAdjustment = img.adjustmentForNextLine(); 5859 auto offR = img.redByteOffset(); 5860 auto offB = img.blueByteOffset(); 5861 auto offG = img.greenByteOffset(); 5862 auto bpp = img.bytesPerPixel(); 5863 5864 auto data = img.getDataPointer(); 5865 5866 // figure out the starting byte offset 5867 auto offset = img.offsetForTopLeftPixel() + nextLineAdjustment*Y + bpp * X; 5868 5869 auto startOfLine = data + offset; // get our pointer lined up on the first pixel 5870 5871 // and now our drawing loop for the rectangle 5872 foreach(y; 0 .. H) { 5873 auto data = startOfLine; // we keep the start of line separately so moving to the next line is simple and portable 5874 foreach(x; 0 .. W) { 5875 // write our color 5876 data[offR] = 0; 5877 data[offG] = 0; 5878 data[offB] = 255; 5879 5880 data += bpp; // moving to the next pixel is just an addition... 5881 } 5882 startOfLine += nextLineAdjustment; 5883 } 5884 5885 5886 As you can see, the loop itself was very simple thanks to the calculations being moved outside. 5887 5888 FIXME: I wonder if I can make the pixel formats consistently 32 bit across platforms, so the color offsets 5889 can be made into a bitmask or something so we can write them as *uint... 5890 */ 5891 5892 /// 5893 int offsetForTopLeftPixel() { 5894 version(X11) { 5895 return 0; 5896 } else version(Windows) { 5897 return (((cast(int) width * 3 + 3) / 4) * 4) * (height - 1); 5898 } else version(OSXCocoa) { 5899 return 0 ; //throw new NotYetImplementedException(); 5900 } else static assert(0, "fill in this info for other OSes"); 5901 } 5902 5903 /// 5904 int offsetForPixel(int x, int y) { 5905 version(X11) { 5906 auto offset = (y * width + x) * 4; 5907 return offset; 5908 } else version(Windows) { 5909 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 5910 // remember, bmps are upside down 5911 auto offset = itemsPerLine * (height - y - 1) + x * 3; 5912 return offset; 5913 } else version(OSXCocoa) { 5914 return 0 ; //throw new NotYetImplementedException(); 5915 } else static assert(0, "fill in this info for other OSes"); 5916 } 5917 5918 /// 5919 int adjustmentForNextLine() { 5920 version(X11) { 5921 return width * 4; 5922 } else version(Windows) { 5923 // windows bmps are upside down, so the adjustment is actually negative 5924 return -((cast(int) width * 3 + 3) / 4) * 4; 5925 } else version(OSXCocoa) { 5926 return 0 ; //throw new NotYetImplementedException(); 5927 } else static assert(0, "fill in this info for other OSes"); 5928 } 5929 5930 /// once you have the position of a pixel, use these to get to the proper color 5931 int redByteOffset() { 5932 version(X11) { 5933 return 2; 5934 } else version(Windows) { 5935 return 2; 5936 } else version(OSXCocoa) { 5937 return 0 ; //throw new NotYetImplementedException(); 5938 } else static assert(0, "fill in this info for other OSes"); 5939 } 5940 5941 /// 5942 int greenByteOffset() { 5943 version(X11) { 5944 return 1; 5945 } else version(Windows) { 5946 return 1; 5947 } else version(OSXCocoa) { 5948 return 0 ; //throw new NotYetImplementedException(); 5949 } else static assert(0, "fill in this info for other OSes"); 5950 } 5951 5952 /// 5953 int blueByteOffset() { 5954 version(X11) { 5955 return 0; 5956 } else version(Windows) { 5957 return 0; 5958 } else version(OSXCocoa) { 5959 return 0 ; //throw new NotYetImplementedException(); 5960 } else static assert(0, "fill in this info for other OSes"); 5961 } 5962 } 5963 5964 /// 5965 final void putPixel(int x, int y, Color c) { 5966 if(x < 0 || x >= width) 5967 return; 5968 if(y < 0 || y >= height) 5969 return; 5970 5971 impl.setPixel(x, y, c); 5972 } 5973 5974 /// 5975 final Color getPixel(int x, int y) { 5976 if(x < 0 || x >= width) 5977 return Color.transparent; 5978 if(y < 0 || y >= height) 5979 return Color.transparent; 5980 5981 version(OSXCocoa) throw new NotYetImplementedException(); else 5982 return impl.getPixel(x, y); 5983 } 5984 5985 /// 5986 final void opIndexAssign(Color c, int x, int y) { 5987 putPixel(x, y, c); 5988 } 5989 5990 /// 5991 TrueColorImage toTrueColorImage() { 5992 auto tci = new TrueColorImage(width, height); 5993 convertToRgbaBytes(tci.imageData.bytes); 5994 return tci; 5995 } 5996 5997 /// 5998 static Image fromMemoryImage(MemoryImage i) { 5999 auto tci = i.getAsTrueColorImage(); 6000 auto img = new Image(tci.width, tci.height); 6001 img.setRgbaBytes(tci.imageData.bytes); 6002 return img; 6003 } 6004 6005 /// this is here for interop with arsd.image. where can be a TrueColorImage's data member 6006 /// if you pass in a buffer, it will put it right there. length must be width*height*4 already 6007 /// if you pass null, it will allocate a new one. 6008 ubyte[] getRgbaBytes(ubyte[] where = null) { 6009 if(where is null) 6010 where = new ubyte[this.width*this.height*4]; 6011 convertToRgbaBytes(where); 6012 return where; 6013 } 6014 6015 /// this is here for interop with arsd.image. from can be a TrueColorImage's data member 6016 void setRgbaBytes(in ubyte[] from ) { 6017 assert(from.length == this.width * this.height * 4); 6018 setFromRgbaBytes(from); 6019 } 6020 6021 // FIXME: make properly cross platform by getting rgba right 6022 6023 /// warning: this is not portable across platforms because the data format can change 6024 ubyte* getDataPointer() { 6025 return impl.rawData; 6026 } 6027 6028 /// for use with getDataPointer 6029 final int bytesPerLine() const pure @safe nothrow { 6030 version(Windows) 6031 return ((cast(int) width * 3 + 3) / 4) * 4; 6032 else version(X11) 6033 return 4 * width; 6034 else version(OSXCocoa) 6035 return 4 * width; 6036 else static assert(0); 6037 } 6038 6039 /// for use with getDataPointer 6040 final int bytesPerPixel() const pure @safe nothrow { 6041 version(Windows) 6042 return 3; 6043 else version(X11) 6044 return 4; 6045 else version(OSXCocoa) 6046 return 4; 6047 else static assert(0); 6048 } 6049 6050 /// 6051 immutable int width; 6052 6053 /// 6054 immutable int height; 6055 //private: 6056 mixin NativeImageImplementation!() impl; 6057 } 6058 6059 /// A convenience function to pop up a window displaying the image. 6060 /// If you pass a win, it will draw the image in it. Otherwise, it will 6061 /// create a window with the size of the image and run its event loop, closing 6062 /// when a key is pressed. 6063 void displayImage(Image image, SimpleWindow win = null) { 6064 if(win is null) { 6065 win = new SimpleWindow(image); 6066 { 6067 auto p = win.draw; 6068 p.drawImage(Point(0, 0), image); 6069 } 6070 win.eventLoop(0, 6071 (KeyEvent ev) { 6072 if (ev.pressed && (ev.key == Key.Escape || ev.key == Key.Space)) win.close(); 6073 } ); 6074 } else { 6075 win.image = image; 6076 } 6077 } 6078 6079 enum FontWeight : int { 6080 dontcare = 0, 6081 thin = 100, 6082 extralight = 200, 6083 light = 300, 6084 regular = 400, 6085 medium = 500, 6086 semibold = 600, 6087 bold = 700, 6088 extrabold = 800, 6089 heavy = 900 6090 } 6091 6092 /++ 6093 Represents a font loaded off the operating system or the X server. 6094 6095 6096 While the api here is unified cross platform, the fonts are not necessarily 6097 available, even across machines of the same platform, so be sure to always check 6098 for null (using [isNull]) and have a fallback plan. 6099 6100 When you have a font you like, use [ScreenPainter.setFont] to load it for drawing. 6101 6102 Worst case, a null font will automatically fall back to the default font loaded 6103 for your system. 6104 +/ 6105 class OperatingSystemFont { 6106 6107 version(X11) { 6108 XFontStruct* font; 6109 XFontSet fontset; 6110 } else version(Windows) { 6111 HFONT font; 6112 } else version(OSXCocoa) { 6113 // FIXME 6114 } else static assert(0); 6115 6116 /// 6117 this(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 6118 load(name, size, weight, italic); 6119 } 6120 6121 /// 6122 bool load(string name, int size = 0, FontWeight weight = FontWeight.dontcare, bool italic = false) { 6123 unload(); 6124 version(X11) { 6125 string weightstr; 6126 with(FontWeight) 6127 final switch(weight) { 6128 case dontcare: weightstr = "*"; break; 6129 case thin: weightstr = "extralight"; break; 6130 case extralight: weightstr = "extralight"; break; 6131 case light: weightstr = "light"; break; 6132 case regular: weightstr = "regular"; break; 6133 case medium: weightstr = "medium"; break; 6134 case semibold: weightstr = "demibold"; break; 6135 case bold: weightstr = "bold"; break; 6136 case extrabold: weightstr = "demibold"; break; 6137 case heavy: weightstr = "black"; break; 6138 } 6139 string sizestr; 6140 if(size == 0) 6141 sizestr = "*"; 6142 else if(size < 10) 6143 sizestr = "" ~ cast(char)(size % 10 + '0'); 6144 else 6145 sizestr = "" ~ cast(char)(size / 10 + '0') ~ cast(char)(size % 10 + '0'); 6146 auto xfontstr = "-*-"~name~"-"~weightstr~"-"~(italic ? "i" : "r")~"-*-*-"~sizestr~"-*-*-*-*-*-*-*\0"; 6147 6148 //import std.stdio; writeln(xfontstr); 6149 6150 auto display = XDisplayConnection.get; 6151 6152 font = XLoadQueryFont(display, xfontstr.ptr); 6153 if(font is null) 6154 return false; 6155 6156 char** lol; 6157 int lol2; 6158 char* lol3; 6159 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 6160 } else version(Windows) { 6161 WCharzBuffer buffer = WCharzBuffer(name); 6162 font = CreateFont(size, 0, 0, 0, cast(int) weight, italic, 0, 0, 0, 0, 0, 0, 0, buffer.ptr); 6163 } else version(OSXCocoa) { 6164 // FIXME 6165 } else static assert(0); 6166 6167 return !isNull(); 6168 } 6169 6170 /// 6171 void unload() { 6172 if(isNull()) 6173 return; 6174 6175 version(X11) { 6176 auto display = XDisplayConnection.display; 6177 6178 if(display is null) 6179 return; 6180 6181 if(font) 6182 XFreeFont(display, font); 6183 if(fontset) 6184 XFreeFontSet(display, fontset); 6185 6186 font = null; 6187 fontset = null; 6188 } else version(Windows) { 6189 DeleteObject(font); 6190 font = null; 6191 } else version(OSXCocoa) { 6192 // FIXME 6193 } else static assert(0); 6194 } 6195 6196 /// FIXME not implemented 6197 void loadDefault() { 6198 6199 } 6200 6201 /// 6202 bool isNull() { 6203 version(OSXCocoa) throw new NotYetImplementedException(); else 6204 return font is null; 6205 } 6206 6207 /* Metrics */ 6208 /+ 6209 GetFontMetrics 6210 GetABCWidth 6211 GetKerningPairs 6212 6213 XLoadQueryFont 6214 6215 if I do it right, I can size it all here, and match 6216 what happens when I draw the full string with the OS functions. 6217 6218 subclasses might do the same thing while getting the glyphs on images 6219 +/ 6220 struct GlyphInfo { 6221 int glyph; 6222 6223 size_t stringIdxStart; 6224 size_t stringIdxEnd; 6225 6226 Rectangle boundingBox; 6227 } 6228 GlyphInfo[] getCharBoxes() { 6229 return null; 6230 6231 } 6232 6233 ~this() { 6234 unload(); 6235 } 6236 } 6237 6238 /** 6239 The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather 6240 than constructing it directly. Then, it is reference counted so you can pass it 6241 at around and when the last ref goes out of scope, the buffered drawing activities 6242 are all carried out. 6243 6244 6245 Most functions use the outlineColor instead of taking a color themselves. 6246 ScreenPainter is reference counted and draws its buffer to the screen when its 6247 final reference goes out of scope. 6248 */ 6249 struct ScreenPainter { 6250 CapableOfBeingDrawnUpon window; 6251 this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle) { 6252 this.window = window; 6253 if(window.closed) 6254 return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway 6255 currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); 6256 if(window.activeScreenPainter !is null) { 6257 impl = window.activeScreenPainter; 6258 if(impl.referenceCount == 0) { 6259 impl.window = window; 6260 impl.create(handle); 6261 } 6262 impl.referenceCount++; 6263 // writeln("refcount ++ ", impl.referenceCount); 6264 } else { 6265 impl = new ScreenPainterImplementation; 6266 impl.window = window; 6267 impl.create(handle); 6268 impl.referenceCount = 1; 6269 window.activeScreenPainter = impl; 6270 // writeln("constructed"); 6271 } 6272 6273 copyActiveOriginals(); 6274 } 6275 6276 private Pen originalPen; 6277 private Color originalFillColor; 6278 private arsd.color.Rectangle originalClipRectangle; 6279 void copyActiveOriginals() { 6280 if(impl is null) return; 6281 originalPen = impl._activePen; 6282 originalFillColor = impl._fillColor; 6283 originalClipRectangle = impl._clipRectangle; 6284 } 6285 6286 ~this() { 6287 if(impl is null) return; 6288 impl.referenceCount--; 6289 //writeln("refcount -- ", impl.referenceCount); 6290 if(impl.referenceCount == 0) { 6291 //writeln("destructed"); 6292 impl.dispose(); 6293 *window.activeScreenPainter = ScreenPainterImplementation.init; 6294 //import std.stdio; writeln("paint finished"); 6295 } else { 6296 // there is still an active reference, reset stuff so the 6297 // next user doesn't get weirdness via the reference 6298 this.rasterOp = RasterOp.normal; 6299 pen = originalPen; 6300 fillColor = originalFillColor; 6301 impl.setClipRectangle(originalClipRectangle.left, originalClipRectangle.top, originalClipRectangle.width, originalClipRectangle.height); 6302 } 6303 } 6304 6305 this(this) { 6306 if(impl is null) return; 6307 impl.referenceCount++; 6308 //writeln("refcount ++ ", impl.referenceCount); 6309 6310 copyActiveOriginals(); 6311 } 6312 6313 private int _originX; 6314 private int _originY; 6315 @property int originX() { return _originX; } 6316 @property int originY() { return _originY; } 6317 @property int originX(int a) { 6318 //currentClipRectangle.left += a - _originX; 6319 //currentClipRectangle.right += a - _originX; 6320 _originX = a; 6321 return _originX; 6322 } 6323 @property int originY(int a) { 6324 //currentClipRectangle.top += a - _originY; 6325 //currentClipRectangle.bottom += a - _originY; 6326 _originY = a; 6327 return _originY; 6328 } 6329 arsd.color.Rectangle currentClipRectangle; // set BEFORE doing any transformations 6330 private void transform(ref Point p) { 6331 if(impl is null) return; 6332 p.x += _originX; 6333 p.y += _originY; 6334 } 6335 6336 // this needs to be checked BEFORE the originX/Y transformation 6337 private bool isClipped(Point p) { 6338 return !currentClipRectangle.contains(p); 6339 } 6340 private bool isClipped(Point p, int width, int height) { 6341 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(width + 1, height + 1))); 6342 } 6343 private bool isClipped(Point p, Size s) { 6344 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, Size(s.width + 1, s.height + 1))); 6345 } 6346 private bool isClipped(Point p, Point p2) { 6347 // need to ensure the end points are actually included inside, so the +1 does that 6348 return !currentClipRectangle.overlaps(arsd.color.Rectangle(p, p2 + Point(1, 1))); 6349 } 6350 6351 6352 /// Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. 6353 void setClipRectangle(Point pt, int width, int height) { 6354 if(impl is null) return; 6355 if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height) 6356 return; // no need to do anything 6357 currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); 6358 transform(pt); 6359 6360 impl.setClipRectangle(pt.x, pt.y, width, height); 6361 } 6362 6363 /// ditto 6364 void setClipRectangle(arsd.color.Rectangle rect) { 6365 if(impl is null) return; 6366 setClipRectangle(rect.upperLeft, rect.width, rect.height); 6367 } 6368 6369 /// 6370 void setFont(OperatingSystemFont font) { 6371 if(impl is null) return; 6372 impl.setFont(font); 6373 } 6374 6375 /// 6376 int fontHeight() { 6377 if(impl is null) return 0; 6378 return impl.fontHeight(); 6379 } 6380 6381 private Pen activePen; 6382 6383 /// 6384 @property void pen(Pen p) { 6385 if(impl is null) return; 6386 activePen = p; 6387 impl.pen(p); 6388 } 6389 6390 /// 6391 @property void outlineColor(Color c) { 6392 if(impl is null) return; 6393 if(activePen.color == c) 6394 return; 6395 activePen.color = c; 6396 impl.pen(activePen); 6397 } 6398 6399 /// 6400 @property void fillColor(Color c) { 6401 if(impl is null) return; 6402 impl.fillColor(c); 6403 } 6404 6405 /// 6406 @property void rasterOp(RasterOp op) { 6407 if(impl is null) return; 6408 impl.rasterOp(op); 6409 } 6410 6411 6412 void updateDisplay() { 6413 // FIXME this should do what the dtor does 6414 } 6415 6416 /// 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) 6417 void scrollArea(Point upperLeft, int width, int height, int dx, int dy) { 6418 if(impl is null) return; 6419 if(isClipped(upperLeft, width, height)) return; 6420 transform(upperLeft); 6421 version(Windows) { 6422 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb787589%28v=vs.85%29.aspx 6423 RECT scroll = RECT(upperLeft.x, upperLeft.y, upperLeft.x + width, upperLeft.y + height); 6424 RECT clip = scroll; 6425 RECT uncovered; 6426 HRGN hrgn; 6427 if(!ScrollDC(impl.hdc, -dx, -dy, &scroll, &clip, hrgn, &uncovered)) 6428 throw new Exception("ScrollDC"); 6429 6430 } else version(X11) { 6431 // FIXME: clip stuff outside this rectangle 6432 XCopyArea(impl.display, impl.d, impl.d, impl.gc, upperLeft.x, upperLeft.y, width, height, upperLeft.x - dx, upperLeft.y - dy); 6433 } else version(OSXCocoa) { 6434 throw new NotYetImplementedException(); 6435 } else static assert(0); 6436 } 6437 6438 /// 6439 void clear(Color color = Color.white()) { 6440 if(impl is null) return; 6441 fillColor = color; 6442 outlineColor = color; 6443 drawRectangle(Point(0, 0), window.width, window.height); 6444 } 6445 6446 /// 6447 version(OSXCocoa) {} else // NotYetImplementedException 6448 void drawPixmap(Sprite s, Point upperLeft) { 6449 if(impl is null) return; 6450 if(isClipped(upperLeft, s.width, s.height)) return; 6451 transform(upperLeft); 6452 impl.drawPixmap(s, upperLeft.x, upperLeft.y); 6453 } 6454 6455 /// 6456 void drawImage(Point upperLeft, Image i, Point upperLeftOfImage = Point(0, 0), int w = 0, int h = 0) { 6457 if(impl is null) return; 6458 //if(isClipped(upperLeft, w, h)) return; // FIXME 6459 transform(upperLeft); 6460 if(w == 0 || w > i.width) 6461 w = i.width; 6462 if(h == 0 || h > i.height) 6463 h = i.height; 6464 if(upperLeftOfImage.x < 0) 6465 upperLeftOfImage.x = 0; 6466 if(upperLeftOfImage.y < 0) 6467 upperLeftOfImage.y = 0; 6468 6469 impl.drawImage(upperLeft.x, upperLeft.y, i, upperLeftOfImage.x, upperLeftOfImage.y, w, h); 6470 } 6471 6472 /// 6473 Size textSize(in char[] text) { 6474 if(impl is null) return Size(0, 0); 6475 return impl.textSize(text); 6476 } 6477 6478 /// 6479 void drawText(Point upperLeft, in char[] text, Point lowerRight = Point(0, 0), uint alignment = 0) { 6480 if(impl is null) return; 6481 if(lowerRight.x != 0 || lowerRight.y != 0) { 6482 if(isClipped(upperLeft, lowerRight)) return; 6483 transform(lowerRight); 6484 } else { 6485 if(isClipped(upperLeft, textSize(text))) return; 6486 } 6487 transform(upperLeft); 6488 impl.drawText(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y, text, alignment); 6489 } 6490 6491 /++ 6492 Draws text using a custom font. 6493 6494 This is still MAJOR work in progress. 6495 6496 Creating a [DrawableFont] can be tricky and require additional dependencies. 6497 +/ 6498 void drawText(DrawableFont font, Point upperLeft, in char[] text) { 6499 if(impl is null) return; 6500 if(isClipped(upperLeft, Point(int.max, int.max))) return; 6501 transform(upperLeft); 6502 font.drawString(this, upperLeft, text); 6503 } 6504 6505 static struct TextDrawingContext { 6506 Point boundingBoxUpperLeft; 6507 Point boundingBoxLowerRight; 6508 6509 Point currentLocation; 6510 6511 Point lastDrewUpperLeft; 6512 Point lastDrewLowerRight; 6513 6514 // how do i do right aligned rich text? 6515 // i kinda want to do a pre-made drawing then right align 6516 // draw the whole block. 6517 // 6518 // That's exactly the diff: inline vs block stuff. 6519 6520 // I need to get coordinates of an inline section out too, 6521 // not just a bounding box, but a series of bounding boxes 6522 // should be ok. Consider what's needed to detect a click 6523 // on a link in the middle of a paragraph breaking a line. 6524 // 6525 // Generally, we should be able to get the rectangles of 6526 // any portion we draw. 6527 // 6528 // It also needs to tell what text is left if it overflows 6529 // out of the box, so we can do stuff like float images around 6530 // it. It should not attempt to draw a letter that would be 6531 // clipped. 6532 // 6533 // I might also turn off word wrap stuff. 6534 } 6535 6536 void drawText(TextDrawingContext context, in char[] text, uint alignment = 0) { 6537 if(impl is null) return; 6538 // FIXME 6539 } 6540 6541 /// Drawing an individual pixel is slow. Avoid it if possible. 6542 void drawPixel(Point where) { 6543 if(impl is null) return; 6544 if(isClipped(where)) return; 6545 transform(where); 6546 impl.drawPixel(where.x, where.y); 6547 } 6548 6549 6550 /// Draws a pen using the current pen / outlineColor 6551 void drawLine(Point starting, Point ending) { 6552 if(impl is null) return; 6553 if(isClipped(starting, ending)) return; 6554 transform(starting); 6555 transform(ending); 6556 impl.drawLine(starting.x, starting.y, ending.x, ending.y); 6557 } 6558 6559 /// Draws a rectangle using the current pen/outline color for the border and brush/fill color for the insides 6560 /// The outer lines, inclusive of x = 0, y = 0, x = width - 1, and y = height - 1 are drawn with the outlineColor 6561 /// The rest of the pixels are drawn with the fillColor. If fillColor is transparent, those pixels are not drawn. 6562 void drawRectangle(Point upperLeft, int width, int height) { 6563 if(impl is null) return; 6564 if(isClipped(upperLeft, width, height)) return; 6565 transform(upperLeft); 6566 impl.drawRectangle(upperLeft.x, upperLeft.y, width, height); 6567 } 6568 6569 /// ditto 6570 void drawRectangle(Point upperLeft, Size size) { 6571 if(impl is null) return; 6572 if(isClipped(upperLeft, size.width, size.height)) return; 6573 transform(upperLeft); 6574 impl.drawRectangle(upperLeft.x, upperLeft.y, size.width, size.height); 6575 } 6576 6577 /// ditto 6578 void drawRectangle(Point upperLeft, Point lowerRightInclusive) { 6579 if(impl is null) return; 6580 if(isClipped(upperLeft, lowerRightInclusive + Point(1, 1))) return; 6581 transform(upperLeft); 6582 transform(lowerRightInclusive); 6583 impl.drawRectangle(upperLeft.x, upperLeft.y, 6584 lowerRightInclusive.x - upperLeft.x + 1, lowerRightInclusive.y - upperLeft.y + 1); 6585 } 6586 6587 /// Arguments are the points of the bounding rectangle 6588 void drawEllipse(Point upperLeft, Point lowerRight) { 6589 if(impl is null) return; 6590 if(isClipped(upperLeft, lowerRight)) return; 6591 transform(upperLeft); 6592 transform(lowerRight); 6593 impl.drawEllipse(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 6594 } 6595 6596 /++ 6597 start and finish are units of degrees * 64 6598 +/ 6599 void drawArc(Point upperLeft, int width, int height, int start, int finish) { 6600 if(impl is null) return; 6601 // FIXME: not actually implemented 6602 if(isClipped(upperLeft, width, height)) return; 6603 transform(upperLeft); 6604 impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); 6605 } 6606 6607 //this function draws a circle using the drawArc() function above, it requires you to pass the point it 6608 //will be drawn at as a Point struct and the radius as an int 6609 void drawCircle(Point upperLeft, int radius) { 6610 this.drawArc(upperLeft, radius, radius, 0, 0); 6611 } 6612 6613 /// . 6614 void drawPolygon(Point[] vertexes) { 6615 if(impl is null) return; 6616 assert(vertexes.length); 6617 int minX = int.max, minY = int.max, maxX = int.min, maxY = int.min; 6618 foreach(ref vertex; vertexes) { 6619 if(vertex.x < minX) 6620 minX = vertex.x; 6621 if(vertex.y < minY) 6622 minY = vertex.y; 6623 if(vertex.x > maxX) 6624 maxX = vertex.x; 6625 if(vertex.y > maxY) 6626 maxY = vertex.y; 6627 transform(vertex); 6628 } 6629 if(isClipped(Point(minX, maxY), Point(maxX + 1, maxY + 1))) return; 6630 impl.drawPolygon(vertexes); 6631 } 6632 6633 /// ditto 6634 void drawPolygon(Point[] vertexes...) { 6635 if(impl is null) return; 6636 drawPolygon(vertexes); 6637 } 6638 6639 6640 // and do a draw/fill in a single call maybe. Windows can do it... but X can't, though it could do two calls. 6641 6642 //mixin NativeScreenPainterImplementation!() impl; 6643 6644 6645 // HACK: if I mixin the impl directly, it won't let me override the copy 6646 // constructor! The linker complains about there being multiple definitions. 6647 // I'll make the best of it and reference count it though. 6648 ScreenPainterImplementation* impl; 6649 } 6650 6651 // HACK: I need a pointer to the implementation so it's separate 6652 struct ScreenPainterImplementation { 6653 CapableOfBeingDrawnUpon window; 6654 int referenceCount; 6655 mixin NativeScreenPainterImplementation!(); 6656 } 6657 6658 // FIXME: i haven't actually tested the sprite class on MS Windows 6659 6660 /** 6661 Sprites are optimized for fast drawing on the screen, but slow for direct pixel 6662 access. They are best for drawing a relatively unchanging image repeatedly on the screen. 6663 6664 6665 On X11, this corresponds to an `XPixmap`. On Windows, it still uses a bitmap, 6666 though I'm not sure that's ideal and the implementation might change. 6667 6668 You create one by giving a window and an image. It optimizes for that window, 6669 and copies the image into it to use as the initial picture. Creating a sprite 6670 can be quite slow (especially over a network connection) so you should do it 6671 as little as possible and just hold on to your sprite handles after making them. 6672 simpledisplay does try to do its best though, using the XSHM extension if available, 6673 but you should still write your code as if it will always be slow. 6674 6675 Then you can use `sprite.drawAt(painter, point);` to draw it, which should be 6676 a fast operation - much faster than drawing the Image itself every time. 6677 6678 `Sprite` represents a scarce resource which should be freed when you 6679 are done with it. Use the `dispose` method to do this. Do not use a `Sprite` 6680 after it has been disposed. If you are unsure about this, don't take chances, 6681 just let the garbage collector do it for you. But ideally, you can manage its 6682 lifetime more efficiently. 6683 6684 $(NOTE `Sprite`, like the rest of simpledisplay's `ScreenPainter`, does not 6685 support alpha blending in its drawing at this time. That might change in the 6686 future, but if you need alpha blending right now, use OpenGL instead. See 6687 `gamehelpers.d` for a similar class to `Sprite` that uses OpenGL: `OpenGlTexture`.) 6688 6689 FIXME: you are supposed to be able to draw on these similarly to on windows. 6690 ScreenPainter needs to be refactored to allow that though. So until that is 6691 done, consider a `Sprite` to have const contents. 6692 */ 6693 version(OSXCocoa) {} else // NotYetImplementedException 6694 class Sprite : CapableOfBeingDrawnUpon { 6695 6696 /// 6697 ScreenPainter draw() { 6698 return ScreenPainter(this, handle); 6699 } 6700 6701 /// Be warned: this can be a very slow operation 6702 /// FIXME NOT IMPLEMENTED 6703 TrueColorImage takeScreenshot() { 6704 return trueColorImageFromNativeHandle(handle, width, height); 6705 } 6706 6707 void delegate() paintingFinishedDg() { return null; } 6708 bool closed() { return false; } 6709 ScreenPainterImplementation* activeScreenPainter_; 6710 protected ScreenPainterImplementation* activeScreenPainter() { return activeScreenPainter_; } 6711 protected void activeScreenPainter(ScreenPainterImplementation* i) { activeScreenPainter_ = i; } 6712 6713 version(Windows) 6714 private ubyte* rawData; 6715 // FIXME: sprites are lost when disconnecting from X! We need some way to invalidate them... 6716 6717 this(SimpleWindow win, int width, int height) { 6718 this._width = width; 6719 this._height = height; 6720 6721 version(X11) { 6722 auto display = XDisplayConnection.get(); 6723 handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, DefaultDepthOfDisplay(display)); 6724 } else version(Windows) { 6725 BITMAPINFO infoheader; 6726 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 6727 infoheader.bmiHeader.biWidth = width; 6728 infoheader.bmiHeader.biHeight = height; 6729 infoheader.bmiHeader.biPlanes = 1; 6730 infoheader.bmiHeader.biBitCount = 24; 6731 infoheader.bmiHeader.biCompression = BI_RGB; 6732 6733 // FIXME: this should prolly be a device dependent bitmap... 6734 handle = CreateDIBSection( 6735 null, 6736 &infoheader, 6737 DIB_RGB_COLORS, 6738 cast(void**) &rawData, 6739 null, 6740 0); 6741 6742 if(handle is null) 6743 throw new Exception("couldn't create pixmap"); 6744 } 6745 } 6746 6747 /// Makes a sprite based on the image with the initial contents from the Image 6748 this(SimpleWindow win, Image i) { 6749 this(win, i.width, i.height); 6750 6751 version(X11) { 6752 auto display = XDisplayConnection.get(); 6753 if(i.usingXshm) 6754 XShmPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height, false); 6755 else 6756 XPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height); 6757 } else version(Windows) { 6758 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 6759 auto arrLength = itemsPerLine * height; 6760 rawData[0..arrLength] = i.rawData[0..arrLength]; 6761 } else version(OSXCocoa) { 6762 // FIXME: I have no idea if this is even any good 6763 ubyte* rawData; 6764 6765 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 6766 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 6767 colorSpace, 6768 kCGImageAlphaPremultipliedLast 6769 |kCGBitmapByteOrder32Big); 6770 CGColorSpaceRelease(colorSpace); 6771 rawData = CGBitmapContextGetData(context); 6772 6773 auto rdl = (width * height * 4); 6774 rawData[0 .. rdl] = i.rawData[0 .. rdl]; 6775 } else static assert(0); 6776 } 6777 6778 /++ 6779 Draws the image on the specified painter at the specified point. The point is the upper-left point where the image will be drawn. 6780 +/ 6781 void drawAt(ScreenPainter painter, Point where) { 6782 painter.drawPixmap(this, where); 6783 } 6784 6785 6786 /// Call this when you're ready to get rid of it 6787 void dispose() { 6788 version(X11) { 6789 if(handle) 6790 XFreePixmap(XDisplayConnection.get(), handle); 6791 handle = None; 6792 } else version(Windows) { 6793 if(handle) 6794 DeleteObject(handle); 6795 handle = null; 6796 } else version(OSXCocoa) { 6797 if(context) 6798 CGContextRelease(context); 6799 context = null; 6800 } else static assert(0); 6801 6802 } 6803 6804 ~this() { 6805 dispose(); 6806 } 6807 6808 /// 6809 final @property int width() { return _width; } 6810 6811 /// 6812 final @property int height() { return _height; } 6813 6814 private: 6815 6816 int _width; 6817 int _height; 6818 version(X11) 6819 Pixmap handle; 6820 else version(Windows) 6821 HBITMAP handle; 6822 else version(OSXCocoa) 6823 CGContextRef context; 6824 else static assert(0); 6825 } 6826 6827 /// 6828 interface CapableOfBeingDrawnUpon { 6829 /// 6830 ScreenPainter draw(); 6831 /// 6832 int width(); 6833 /// 6834 int height(); 6835 protected ScreenPainterImplementation* activeScreenPainter(); 6836 protected void activeScreenPainter(ScreenPainterImplementation*); 6837 bool closed(); 6838 6839 void delegate() paintingFinishedDg(); 6840 6841 /// Be warned: this can be a very slow operation 6842 TrueColorImage takeScreenshot(); 6843 } 6844 6845 /// 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() 6846 void flushGui() { 6847 version(X11) { 6848 auto dpy = XDisplayConnection.get(); 6849 XLockDisplay(dpy); 6850 scope(exit) XUnlockDisplay(dpy); 6851 XFlush(dpy); 6852 } 6853 } 6854 6855 /// Used internal to dispatch events to various classes. 6856 interface CapableOfHandlingNativeEvent { 6857 NativeEventHandler getNativeEventHandler(); 6858 6859 /*private*//*protected*/ __gshared CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping; 6860 6861 version(X11) { 6862 // if this is impossible, you are allowed to just throw from it 6863 // Note: if you call it from another object, set a flag cuz the manger will call you again 6864 void recreateAfterDisconnect(); 6865 // discard any *connection specific* state, but keep enough that you 6866 // can be recreated if possible. discardConnectionState() is always called immediately 6867 // before recreateAfterDisconnect(), so you can set a flag there to decide if 6868 // you need initialization order 6869 void discardConnectionState(); 6870 } 6871 } 6872 6873 version(X11) 6874 /++ 6875 State of keys on mouse events, especially motion. 6876 6877 Do not trust the actual integer values in this, they are platform-specific. Always use the names. 6878 +/ 6879 enum ModifierState : uint { 6880 shift = 1, /// 6881 capsLock = 2, /// 6882 ctrl = 4, /// 6883 alt = 8, /// Not always available on Windows 6884 windows = 64, /// ditto 6885 numLock = 16, /// 6886 6887 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 6888 middleButtonDown = 512, /// ditto 6889 rightButtonDown = 1024, /// ditto 6890 } 6891 else version(Windows) 6892 /// ditto 6893 enum ModifierState : uint { 6894 shift = 4, /// 6895 ctrl = 8, /// 6896 6897 // i'm not sure if the next two are available 6898 alt = 256, /// not always available on Windows 6899 windows = 512, /// ditto 6900 6901 capsLock = 1024, /// 6902 numLock = 2048, /// 6903 6904 leftButtonDown = 1, /// not available on key events 6905 middleButtonDown = 16, /// ditto 6906 rightButtonDown = 2, /// ditto 6907 6908 backButtonDown = 0x20, /// not available on X 6909 forwardButtonDown = 0x40, /// ditto 6910 } 6911 else version(OSXCocoa) 6912 // FIXME FIXME NotYetImplementedException 6913 enum ModifierState : uint { 6914 shift = 1, /// 6915 capsLock = 2, /// 6916 ctrl = 4, /// 6917 alt = 8, /// Not always available on Windows 6918 windows = 64, /// ditto 6919 numLock = 16, /// 6920 6921 leftButtonDown = 256, /// these aren't available on Windows for key events, so don't use them for that unless your app is X only. 6922 middleButtonDown = 512, /// ditto 6923 rightButtonDown = 1024, /// ditto 6924 } 6925 6926 /// The names assume a right-handed mouse. These are bitwise combined on the events that use them 6927 enum MouseButton : int { 6928 none = 0, 6929 left = 1, /// 6930 right = 2, /// 6931 middle = 4, /// 6932 wheelUp = 8, /// 6933 wheelDown = 16, /// 6934 backButton = 32, /// often found on the thumb and used for back in browsers 6935 forwardButton = 64, /// often found on the thumb and used for forward in browsers 6936 } 6937 6938 version(X11) { 6939 // FIXME: match ASCII whenever we can. Most of it is already there, 6940 // but there's a few exceptions and mismatches with Windows 6941 6942 /// Do not trust the numeric values as they are platform-specific. Always use the symbolic name. 6943 enum Key { 6944 Escape = 0xff1b, /// 6945 F1 = 0xffbe, /// 6946 F2 = 0xffbf, /// 6947 F3 = 0xffc0, /// 6948 F4 = 0xffc1, /// 6949 F5 = 0xffc2, /// 6950 F6 = 0xffc3, /// 6951 F7 = 0xffc4, /// 6952 F8 = 0xffc5, /// 6953 F9 = 0xffc6, /// 6954 F10 = 0xffc7, /// 6955 F11 = 0xffc8, /// 6956 F12 = 0xffc9, /// 6957 PrintScreen = 0xff61, /// 6958 ScrollLock = 0xff14, /// 6959 Pause = 0xff13, /// 6960 Grave = 0x60, /// The $(BACKTICK) ~ key 6961 // number keys across the top of the keyboard 6962 N1 = 0x31, /// Number key atop the keyboard 6963 N2 = 0x32, /// 6964 N3 = 0x33, /// 6965 N4 = 0x34, /// 6966 N5 = 0x35, /// 6967 N6 = 0x36, /// 6968 N7 = 0x37, /// 6969 N8 = 0x38, /// 6970 N9 = 0x39, /// 6971 N0 = 0x30, /// 6972 Dash = 0x2d, /// 6973 Equals = 0x3d, /// 6974 Backslash = 0x5c, /// The \ | key 6975 Backspace = 0xff08, /// 6976 Insert = 0xff63, /// 6977 Home = 0xff50, /// 6978 PageUp = 0xff55, /// 6979 Delete = 0xffff, /// 6980 End = 0xff57, /// 6981 PageDown = 0xff56, /// 6982 Up = 0xff52, /// 6983 Down = 0xff54, /// 6984 Left = 0xff51, /// 6985 Right = 0xff53, /// 6986 6987 Tab = 0xff09, /// 6988 Q = 0x71, /// 6989 W = 0x77, /// 6990 E = 0x65, /// 6991 R = 0x72, /// 6992 T = 0x74, /// 6993 Y = 0x79, /// 6994 U = 0x75, /// 6995 I = 0x69, /// 6996 O = 0x6f, /// 6997 P = 0x70, /// 6998 LeftBracket = 0x5b, /// the [ { key 6999 RightBracket = 0x5d, /// the ] } key 7000 CapsLock = 0xffe5, /// 7001 A = 0x61, /// 7002 S = 0x73, /// 7003 D = 0x64, /// 7004 F = 0x66, /// 7005 G = 0x67, /// 7006 H = 0x68, /// 7007 J = 0x6a, /// 7008 K = 0x6b, /// 7009 L = 0x6c, /// 7010 Semicolon = 0x3b, /// 7011 Apostrophe = 0x27, /// 7012 Enter = 0xff0d, /// 7013 Shift = 0xffe1, /// 7014 Z = 0x7a, /// 7015 X = 0x78, /// 7016 C = 0x63, /// 7017 V = 0x76, /// 7018 B = 0x62, /// 7019 N = 0x6e, /// 7020 M = 0x6d, /// 7021 Comma = 0x2c, /// 7022 Period = 0x2e, /// 7023 Slash = 0x2f, /// the / ? key 7024 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 7025 Ctrl = 0xffe3, /// 7026 Windows = 0xffeb, /// 7027 Alt = 0xffe9, /// 7028 Space = 0x20, /// 7029 Alt_r = 0xffea, /// ditto of shift_r 7030 Windows_r = 0xffec, /// 7031 Menu = 0xff67, /// 7032 Ctrl_r = 0xffe4, /// 7033 7034 NumLock = 0xff7f, /// 7035 Divide = 0xffaf, /// The / key on the number pad 7036 Multiply = 0xffaa, /// The * key on the number pad 7037 Minus = 0xffad, /// The - key on the number pad 7038 Plus = 0xffab, /// The + key on the number pad 7039 PadEnter = 0xff8d, /// Numberpad enter key 7040 Pad1 = 0xff9c, /// Numberpad keys 7041 Pad2 = 0xff99, /// 7042 Pad3 = 0xff9b, /// 7043 Pad4 = 0xff96, /// 7044 Pad5 = 0xff9d, /// 7045 Pad6 = 0xff98, /// 7046 Pad7 = 0xff95, /// 7047 Pad8 = 0xff97, /// 7048 Pad9 = 0xff9a, /// 7049 Pad0 = 0xff9e, /// 7050 PadDot = 0xff9f, /// 7051 } 7052 } else version(Windows) { 7053 // the character here is for en-us layouts and for illustration only 7054 // if you actually want to get characters, wait for character events 7055 // (the argument to your event handler is simply a dchar) 7056 // those will be converted by the OS for the right locale. 7057 7058 enum Key { 7059 Escape = 0x1b, 7060 F1 = 0x70, 7061 F2 = 0x71, 7062 F3 = 0x72, 7063 F4 = 0x73, 7064 F5 = 0x74, 7065 F6 = 0x75, 7066 F7 = 0x76, 7067 F8 = 0x77, 7068 F9 = 0x78, 7069 F10 = 0x79, 7070 F11 = 0x7a, 7071 F12 = 0x7b, 7072 PrintScreen = 0x2c, 7073 ScrollLock = 0x91, 7074 Pause = 0x13, 7075 Grave = 0xc0, 7076 // number keys across the top of the keyboard 7077 N1 = 0x31, 7078 N2 = 0x32, 7079 N3 = 0x33, 7080 N4 = 0x34, 7081 N5 = 0x35, 7082 N6 = 0x36, 7083 N7 = 0x37, 7084 N8 = 0x38, 7085 N9 = 0x39, 7086 N0 = 0x30, 7087 Dash = 0xbd, 7088 Equals = 0xbb, 7089 Backslash = 0xdc, 7090 Backspace = 0x08, 7091 Insert = 0x2d, 7092 Home = 0x24, 7093 PageUp = 0x21, 7094 Delete = 0x2e, 7095 End = 0x23, 7096 PageDown = 0x22, 7097 Up = 0x26, 7098 Down = 0x28, 7099 Left = 0x25, 7100 Right = 0x27, 7101 7102 Tab = 0x09, 7103 Q = 0x51, 7104 W = 0x57, 7105 E = 0x45, 7106 R = 0x52, 7107 T = 0x54, 7108 Y = 0x59, 7109 U = 0x55, 7110 I = 0x49, 7111 O = 0x4f, 7112 P = 0x50, 7113 LeftBracket = 0xdb, 7114 RightBracket = 0xdd, 7115 CapsLock = 0x14, 7116 A = 0x41, 7117 S = 0x53, 7118 D = 0x44, 7119 F = 0x46, 7120 G = 0x47, 7121 H = 0x48, 7122 J = 0x4a, 7123 K = 0x4b, 7124 L = 0x4c, 7125 Semicolon = 0xba, 7126 Apostrophe = 0xde, 7127 Enter = 0x0d, 7128 Shift = 0x10, 7129 Z = 0x5a, 7130 X = 0x58, 7131 C = 0x43, 7132 V = 0x56, 7133 B = 0x42, 7134 N = 0x4e, 7135 M = 0x4d, 7136 Comma = 0xbc, 7137 Period = 0xbe, 7138 Slash = 0xbf, 7139 Shift_r = 0xa1, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 7140 Ctrl = 0x11, 7141 Windows = 0x5b, 7142 Alt = -5, // FIXME 7143 Space = 0x20, 7144 Alt_r = 0xffea, // ditto of shift_r 7145 Windows_r = 0x5c, // ditto of shift_r 7146 Menu = 0x5d, 7147 Ctrl_r = 0xa3, // ditto of shift_r 7148 7149 NumLock = 0x90, 7150 Divide = 0x6f, 7151 Multiply = 0x6a, 7152 Minus = 0x6d, 7153 Plus = 0x6b, 7154 PadEnter = -8, // FIXME 7155 Pad1 = 0x61, 7156 Pad2 = 0x62, 7157 Pad3 = 0x63, 7158 Pad4 = 0x64, 7159 Pad5 = 0x65, 7160 Pad6 = 0x66, 7161 Pad7 = 0x67, 7162 Pad8 = 0x68, 7163 Pad9 = 0x69, 7164 Pad0 = 0x60, 7165 PadDot = 0x6e, 7166 } 7167 7168 // I'm keeping this around for reference purposes 7169 // ideally all these buttons will be listed for all platforms, 7170 // but now now I'm just focusing on my US keyboard 7171 version(none) 7172 enum Key { 7173 LBUTTON = 0x01, 7174 RBUTTON = 0x02, 7175 CANCEL = 0x03, 7176 MBUTTON = 0x04, 7177 //static if (_WIN32_WINNT > = 0x500) { 7178 XBUTTON1 = 0x05, 7179 XBUTTON2 = 0x06, 7180 //} 7181 BACK = 0x08, 7182 TAB = 0x09, 7183 CLEAR = 0x0C, 7184 RETURN = 0x0D, 7185 SHIFT = 0x10, 7186 CONTROL = 0x11, 7187 MENU = 0x12, 7188 PAUSE = 0x13, 7189 CAPITAL = 0x14, 7190 KANA = 0x15, 7191 HANGEUL = 0x15, 7192 HANGUL = 0x15, 7193 JUNJA = 0x17, 7194 FINAL = 0x18, 7195 HANJA = 0x19, 7196 KANJI = 0x19, 7197 ESCAPE = 0x1B, 7198 CONVERT = 0x1C, 7199 NONCONVERT = 0x1D, 7200 ACCEPT = 0x1E, 7201 MODECHANGE = 0x1F, 7202 SPACE = 0x20, 7203 PRIOR = 0x21, 7204 NEXT = 0x22, 7205 END = 0x23, 7206 HOME = 0x24, 7207 LEFT = 0x25, 7208 UP = 0x26, 7209 RIGHT = 0x27, 7210 DOWN = 0x28, 7211 SELECT = 0x29, 7212 PRINT = 0x2A, 7213 EXECUTE = 0x2B, 7214 SNAPSHOT = 0x2C, 7215 INSERT = 0x2D, 7216 DELETE = 0x2E, 7217 HELP = 0x2F, 7218 LWIN = 0x5B, 7219 RWIN = 0x5C, 7220 APPS = 0x5D, 7221 SLEEP = 0x5F, 7222 NUMPAD0 = 0x60, 7223 NUMPAD1 = 0x61, 7224 NUMPAD2 = 0x62, 7225 NUMPAD3 = 0x63, 7226 NUMPAD4 = 0x64, 7227 NUMPAD5 = 0x65, 7228 NUMPAD6 = 0x66, 7229 NUMPAD7 = 0x67, 7230 NUMPAD8 = 0x68, 7231 NUMPAD9 = 0x69, 7232 MULTIPLY = 0x6A, 7233 ADD = 0x6B, 7234 SEPARATOR = 0x6C, 7235 SUBTRACT = 0x6D, 7236 DECIMAL = 0x6E, 7237 DIVIDE = 0x6F, 7238 F1 = 0x70, 7239 F2 = 0x71, 7240 F3 = 0x72, 7241 F4 = 0x73, 7242 F5 = 0x74, 7243 F6 = 0x75, 7244 F7 = 0x76, 7245 F8 = 0x77, 7246 F9 = 0x78, 7247 F10 = 0x79, 7248 F11 = 0x7A, 7249 F12 = 0x7B, 7250 F13 = 0x7C, 7251 F14 = 0x7D, 7252 F15 = 0x7E, 7253 F16 = 0x7F, 7254 F17 = 0x80, 7255 F18 = 0x81, 7256 F19 = 0x82, 7257 F20 = 0x83, 7258 F21 = 0x84, 7259 F22 = 0x85, 7260 F23 = 0x86, 7261 F24 = 0x87, 7262 NUMLOCK = 0x90, 7263 SCROLL = 0x91, 7264 LSHIFT = 0xA0, 7265 RSHIFT = 0xA1, 7266 LCONTROL = 0xA2, 7267 RCONTROL = 0xA3, 7268 LMENU = 0xA4, 7269 RMENU = 0xA5, 7270 //static if (_WIN32_WINNT > = 0x500) { 7271 BROWSER_BACK = 0xA6, 7272 BROWSER_FORWARD = 0xA7, 7273 BROWSER_REFRESH = 0xA8, 7274 BROWSER_STOP = 0xA9, 7275 BROWSER_SEARCH = 0xAA, 7276 BROWSER_FAVORITES = 0xAB, 7277 BROWSER_HOME = 0xAC, 7278 VOLUME_MUTE = 0xAD, 7279 VOLUME_DOWN = 0xAE, 7280 VOLUME_UP = 0xAF, 7281 MEDIA_NEXT_TRACK = 0xB0, 7282 MEDIA_PREV_TRACK = 0xB1, 7283 MEDIA_STOP = 0xB2, 7284 MEDIA_PLAY_PAUSE = 0xB3, 7285 LAUNCH_MAIL = 0xB4, 7286 LAUNCH_MEDIA_SELECT = 0xB5, 7287 LAUNCH_APP1 = 0xB6, 7288 LAUNCH_APP2 = 0xB7, 7289 //} 7290 OEM_1 = 0xBA, 7291 //static if (_WIN32_WINNT > = 0x500) { 7292 OEM_PLUS = 0xBB, 7293 OEM_COMMA = 0xBC, 7294 OEM_MINUS = 0xBD, 7295 OEM_PERIOD = 0xBE, 7296 //} 7297 OEM_2 = 0xBF, 7298 OEM_3 = 0xC0, 7299 OEM_4 = 0xDB, 7300 OEM_5 = 0xDC, 7301 OEM_6 = 0xDD, 7302 OEM_7 = 0xDE, 7303 OEM_8 = 0xDF, 7304 //static if (_WIN32_WINNT > = 0x500) { 7305 OEM_102 = 0xE2, 7306 //} 7307 PROCESSKEY = 0xE5, 7308 //static if (_WIN32_WINNT > = 0x500) { 7309 PACKET = 0xE7, 7310 //} 7311 ATTN = 0xF6, 7312 CRSEL = 0xF7, 7313 EXSEL = 0xF8, 7314 EREOF = 0xF9, 7315 PLAY = 0xFA, 7316 ZOOM = 0xFB, 7317 NONAME = 0xFC, 7318 PA1 = 0xFD, 7319 OEM_CLEAR = 0xFE, 7320 } 7321 7322 } else version(OSXCocoa) { 7323 // FIXME 7324 enum Key { 7325 Escape = 0x1b, 7326 F1 = 0x70, 7327 F2 = 0x71, 7328 F3 = 0x72, 7329 F4 = 0x73, 7330 F5 = 0x74, 7331 F6 = 0x75, 7332 F7 = 0x76, 7333 F8 = 0x77, 7334 F9 = 0x78, 7335 F10 = 0x79, 7336 F11 = 0x7a, 7337 F12 = 0x7b, 7338 PrintScreen = 0x2c, 7339 ScrollLock = -2, // FIXME 7340 Pause = -3, // FIXME 7341 Grave = 0xc0, 7342 // number keys across the top of the keyboard 7343 N1 = 0x31, 7344 N2 = 0x32, 7345 N3 = 0x33, 7346 N4 = 0x34, 7347 N5 = 0x35, 7348 N6 = 0x36, 7349 N7 = 0x37, 7350 N8 = 0x38, 7351 N9 = 0x39, 7352 N0 = 0x30, 7353 Dash = 0xbd, 7354 Equals = 0xbb, 7355 Backslash = 0xdc, 7356 Backspace = 0x08, 7357 Insert = 0x2d, 7358 Home = 0x24, 7359 PageUp = 0x21, 7360 Delete = 0x2e, 7361 End = 0x23, 7362 PageDown = 0x22, 7363 Up = 0x26, 7364 Down = 0x28, 7365 Left = 0x25, 7366 Right = 0x27, 7367 7368 Tab = 0x09, 7369 Q = 0x51, 7370 W = 0x57, 7371 E = 0x45, 7372 R = 0x52, 7373 T = 0x54, 7374 Y = 0x59, 7375 U = 0x55, 7376 I = 0x49, 7377 O = 0x4f, 7378 P = 0x50, 7379 LeftBracket = 0xdb, 7380 RightBracket = 0xdd, 7381 CapsLock = 0x14, 7382 A = 0x41, 7383 S = 0x53, 7384 D = 0x44, 7385 F = 0x46, 7386 G = 0x47, 7387 H = 0x48, 7388 J = 0x4a, 7389 K = 0x4b, 7390 L = 0x4c, 7391 Semicolon = 0xba, 7392 Apostrophe = 0xde, 7393 Enter = 0x0d, 7394 Shift = 0x10, 7395 Z = 0x5a, 7396 X = 0x58, 7397 C = 0x43, 7398 V = 0x56, 7399 B = 0x42, 7400 N = 0x4e, 7401 M = 0x4d, 7402 Comma = 0xbc, 7403 Period = 0xbe, 7404 Slash = 0xbf, 7405 Shift_r = -4, // FIXME Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it 7406 Ctrl = 0x11, 7407 Windows = 0x5b, 7408 Alt = -5, // FIXME 7409 Space = 0x20, 7410 Alt_r = 0xffea, // ditto of shift_r 7411 Windows_r = -6, // FIXME 7412 Menu = 0x5d, 7413 Ctrl_r = -7, // FIXME 7414 7415 NumLock = 0x90, 7416 Divide = 0x6f, 7417 Multiply = 0x6a, 7418 Minus = 0x6d, 7419 Plus = 0x6b, 7420 PadEnter = -8, // FIXME 7421 // FIXME for the rest of these: 7422 Pad1 = 0xff9c, 7423 Pad2 = 0xff99, 7424 Pad3 = 0xff9b, 7425 Pad4 = 0xff96, 7426 Pad5 = 0xff9d, 7427 Pad6 = 0xff98, 7428 Pad7 = 0xff95, 7429 Pad8 = 0xff97, 7430 Pad9 = 0xff9a, 7431 Pad0 = 0xff9e, 7432 PadDot = 0xff9f, 7433 } 7434 7435 } 7436 7437 /* Additional utilities */ 7438 7439 7440 Color fromHsl(real h, real s, real l) { 7441 return arsd.color.fromHsl([h,s,l]); 7442 } 7443 7444 7445 7446 /* ********** What follows is the system-specific implementations *********/ 7447 version(Windows) { 7448 7449 7450 // helpers for making HICONs from MemoryImages 7451 class WindowsIcon { 7452 struct Win32Icon(int colorCount) { 7453 align(1): 7454 uint biSize; 7455 int biWidth; 7456 int biHeight; 7457 ushort biPlanes; 7458 ushort biBitCount; 7459 uint biCompression; 7460 uint biSizeImage; 7461 int biXPelsPerMeter; 7462 int biYPelsPerMeter; 7463 uint biClrUsed; 7464 uint biClrImportant; 7465 RGBQUAD[colorCount] biColors; 7466 /* Pixels: 7467 Uint8 pixels[] 7468 */ 7469 /* Mask: 7470 Uint8 mask[] 7471 */ 7472 7473 ubyte[4096] data; 7474 7475 void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) { 7476 width = mi.width; 7477 height = mi.height; 7478 7479 auto indexedImage = cast(IndexedImage) mi; 7480 if(indexedImage is null) 7481 indexedImage = quantize(mi.getAsTrueColorImage()); 7482 7483 assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy 7484 assert(height %4 == 0); 7485 7486 int icon_plen = height*((width+3)&~3); 7487 int icon_mlen = height*((((width+7)/8)+3)&~3); 7488 icon_len = 40+icon_plen+icon_mlen + cast(int) RGBQUAD.sizeof * colorCount; 7489 7490 biSize = 40; 7491 biWidth = width; 7492 biHeight = height*2; 7493 biPlanes = 1; 7494 biBitCount = 8; 7495 biSizeImage = icon_plen+icon_mlen; 7496 7497 int offset = 0; 7498 int andOff = icon_plen * 8; // the and offset is in bits 7499 for(int y = height - 1; y >= 0; y--) { 7500 int off2 = y * width; 7501 foreach(x; 0 .. width) { 7502 const b = indexedImage.data[off2 + x]; 7503 data[offset] = b; 7504 offset++; 7505 7506 const andBit = andOff % 8; 7507 const andIdx = andOff / 8; 7508 assert(b < indexedImage.palette.length); 7509 // this is anded to the destination, since and 0 means erase, 7510 // we want that to be opaque, and 1 for transparent 7511 auto transparent = (indexedImage.palette[b].a <= 127); 7512 data[andIdx] |= (transparent ? (1 << (7-andBit)) : 0); 7513 7514 andOff++; 7515 } 7516 7517 andOff += andOff % 32; 7518 } 7519 7520 foreach(idx, entry; indexedImage.palette) { 7521 if(entry.a > 127) { 7522 biColors[idx].rgbBlue = entry.b; 7523 biColors[idx].rgbGreen = entry.g; 7524 biColors[idx].rgbRed = entry.r; 7525 } else { 7526 biColors[idx].rgbBlue = 255; 7527 biColors[idx].rgbGreen = 255; 7528 biColors[idx].rgbRed = 255; 7529 } 7530 } 7531 7532 /* 7533 data[0..icon_plen] = getFlippedUnfilteredDatastream(png); 7534 data[icon_plen..icon_plen+icon_mlen] = getANDMask(png); 7535 //icon_win32.biColors[1] = Win32Icon.RGBQUAD(0,255,0,0); 7536 auto pngMap = fetchPaletteWin32(png); 7537 biColors[0..pngMap.length] = pngMap[]; 7538 */ 7539 } 7540 } 7541 7542 7543 Win32Icon!(256) icon_win32; 7544 7545 7546 this(MemoryImage mi) { 7547 int icon_len, width, height; 7548 7549 icon_win32.fromMemoryImage(mi, icon_len, width, height); 7550 7551 /* 7552 PNG* png = readPnpngData); 7553 PNGHeader pngh = getHeader(png); 7554 void* icon_win32; 7555 if(pngh.depth == 4) { 7556 auto i = new Win32Icon!(16); 7557 i.fromPNG(png, pngh, icon_len, width, height); 7558 icon_win32 = i; 7559 } 7560 else if(pngh.depth == 8) { 7561 auto i = new Win32Icon!(256); 7562 i.fromPNG(png, pngh, icon_len, width, height); 7563 icon_win32 = i; 7564 } else assert(0); 7565 */ 7566 7567 hIcon = CreateIconFromResourceEx(cast(ubyte*) &icon_win32, icon_len, true, 0x00030000, width, height, 0); 7568 7569 if(hIcon is null) throw new Exception("CreateIconFromResourceEx"); 7570 } 7571 7572 ~this() { 7573 DestroyIcon(hIcon); 7574 } 7575 7576 HICON hIcon; 7577 } 7578 7579 7580 7581 7582 7583 7584 alias int delegate(HWND, UINT, WPARAM, LPARAM) NativeEventHandler; 7585 alias HWND NativeWindowHandle; 7586 7587 extern(Windows) 7588 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { 7589 try { 7590 if(SimpleWindow.handleNativeGlobalEvent !is null) { 7591 // it returns zero if the message is handled, so we won't do anything more there 7592 // do I like that though? 7593 auto ret = SimpleWindow.handleNativeGlobalEvent(hWnd, iMessage, wParam, lParam); 7594 if(ret == 0) 7595 return ret; 7596 } 7597 7598 if(auto window = hWnd in CapableOfHandlingNativeEvent.nativeHandleMapping) { 7599 if(window.getNativeEventHandler !is null) { 7600 auto ret = window.getNativeEventHandler()(hWnd, iMessage, wParam, lParam); 7601 if(ret == 0) 7602 return ret; 7603 } 7604 if(auto w = cast(SimpleWindow) (*window)) 7605 return w.windowProcedure(hWnd, iMessage, wParam, lParam); 7606 else 7607 return DefWindowProc(hWnd, iMessage, wParam, lParam); 7608 } else { 7609 return DefWindowProc(hWnd, iMessage, wParam, lParam); 7610 } 7611 } catch (Exception e) { 7612 assert(false, "Exception caught in WndProc " ~ e.toString()); 7613 } 7614 } 7615 7616 mixin template NativeScreenPainterImplementation() { 7617 HDC hdc; 7618 HWND hwnd; 7619 //HDC windowHdc; 7620 HBITMAP oldBmp; 7621 7622 void create(NativeWindowHandle window) { 7623 hwnd = window; 7624 7625 if(auto sw = cast(SimpleWindow) this.window) { 7626 // drawing on a window, double buffer 7627 auto windowHdc = GetDC(hwnd); 7628 7629 auto buffer = sw.impl.buffer; 7630 hdc = CreateCompatibleDC(windowHdc); 7631 7632 ReleaseDC(hwnd, windowHdc); 7633 7634 oldBmp = SelectObject(hdc, buffer); 7635 } else { 7636 // drawing on something else, draw directly 7637 hdc = CreateCompatibleDC(null); 7638 SelectObject(hdc, window); 7639 7640 } 7641 7642 // X doesn't draw a text background, so neither should we 7643 SetBkMode(hdc, TRANSPARENT); 7644 7645 7646 static bool triedDefaultGuiFont = false; 7647 if(!triedDefaultGuiFont) { 7648 NONCLIENTMETRICS params; 7649 params.cbSize = params.sizeof; 7650 if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, ¶ms, 0)) { 7651 defaultGuiFont = CreateFontIndirect(¶ms.lfMessageFont); 7652 } 7653 triedDefaultGuiFont = true; 7654 } 7655 7656 if(defaultGuiFont) { 7657 SelectObject(hdc, defaultGuiFont); 7658 // DeleteObject(defaultGuiFont); 7659 } 7660 } 7661 7662 static HFONT defaultGuiFont; 7663 7664 void setFont(OperatingSystemFont font) { 7665 if(font && font.font) 7666 SelectObject(hdc, font.font); 7667 else if(defaultGuiFont) 7668 SelectObject(hdc, defaultGuiFont); 7669 } 7670 7671 arsd.color.Rectangle _clipRectangle; 7672 7673 void setClipRectangle(int x, int y, int width, int height) { 7674 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 7675 7676 if(width == 0 || height == 0) { 7677 SelectClipRgn(hdc, null); 7678 } else { 7679 auto region = CreateRectRgn(x, y, x + width, y + height); 7680 SelectClipRgn(hdc, region); 7681 DeleteObject(region); 7682 } 7683 } 7684 7685 7686 // just because we can on Windows... 7687 //void create(Image image); 7688 7689 void dispose() { 7690 // FIXME: this.window.width/height is probably wrong 7691 // BitBlt(windowHdc, 0, 0, this.window.width, this.window.height, hdc, 0, 0, SRCCOPY); 7692 // ReleaseDC(hwnd, windowHdc); 7693 7694 // FIXME: it shouldn't invalidate the whole thing in all cases... it would be ideal to do this right 7695 if(cast(SimpleWindow) this.window) 7696 InvalidateRect(hwnd, cast(RECT*)null, false); // no need to erase bg as the whole thing gets bitblt'd ove 7697 7698 if(originalPen !is null) 7699 SelectObject(hdc, originalPen); 7700 if(currentPen !is null) 7701 DeleteObject(currentPen); 7702 if(originalBrush !is null) 7703 SelectObject(hdc, originalBrush); 7704 if(currentBrush !is null) 7705 DeleteObject(currentBrush); 7706 7707 SelectObject(hdc, oldBmp); 7708 7709 DeleteDC(hdc); 7710 7711 if(window.paintingFinishedDg !is null) 7712 window.paintingFinishedDg(); 7713 } 7714 7715 HPEN originalPen; 7716 HPEN currentPen; 7717 7718 Pen _activePen; 7719 7720 @property void pen(Pen p) { 7721 _activePen = p; 7722 7723 HPEN pen; 7724 if(p.color.a == 0) { 7725 pen = GetStockObject(NULL_PEN); 7726 } else { 7727 int style = PS_SOLID; 7728 final switch(p.style) { 7729 case Pen.Style.Solid: 7730 style = PS_SOLID; 7731 break; 7732 case Pen.Style.Dashed: 7733 style = PS_DASH; 7734 break; 7735 case Pen.Style.Dotted: 7736 style = PS_DOT; 7737 break; 7738 } 7739 pen = CreatePen(style, p.width, RGB(p.color.r, p.color.g, p.color.b)); 7740 } 7741 auto orig = SelectObject(hdc, pen); 7742 if(originalPen is null) 7743 originalPen = orig; 7744 7745 if(currentPen !is null) 7746 DeleteObject(currentPen); 7747 7748 currentPen = pen; 7749 7750 // the outline is like a foreground since it's done that way on X 7751 SetTextColor(hdc, RGB(p.color.r, p.color.g, p.color.b)); 7752 7753 } 7754 7755 @property void rasterOp(RasterOp op) { 7756 int mode; 7757 final switch(op) { 7758 case RasterOp.normal: 7759 mode = R2_COPYPEN; 7760 break; 7761 case RasterOp.xor: 7762 mode = R2_XORPEN; 7763 break; 7764 } 7765 SetROP2(hdc, mode); 7766 } 7767 7768 HBRUSH originalBrush; 7769 HBRUSH currentBrush; 7770 Color _fillColor = Color(1, 1, 1, 1); // what are the odds that they'd set this?? 7771 @property void fillColor(Color c) { 7772 if(c == _fillColor) 7773 return; 7774 _fillColor = c; 7775 HBRUSH brush; 7776 if(c.a == 0) { 7777 brush = GetStockObject(HOLLOW_BRUSH); 7778 } else { 7779 brush = CreateSolidBrush(RGB(c.r, c.g, c.b)); 7780 } 7781 auto orig = SelectObject(hdc, brush); 7782 if(originalBrush is null) 7783 originalBrush = orig; 7784 7785 if(currentBrush !is null) 7786 DeleteObject(currentBrush); 7787 7788 currentBrush = brush; 7789 7790 // background color is NOT set because X doesn't draw text backgrounds 7791 // SetBkColor(hdc, RGB(255, 255, 255)); 7792 } 7793 7794 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 7795 BITMAP bm; 7796 7797 HDC hdcMem = CreateCompatibleDC(hdc); 7798 HBITMAP hbmOld = SelectObject(hdcMem, i.handle); 7799 7800 GetObject(i.handle, bm.sizeof, &bm); 7801 7802 BitBlt(hdc, x, y, w /* bm.bmWidth */, /*bm.bmHeight*/ h, hdcMem, ix, iy, SRCCOPY); 7803 7804 SelectObject(hdcMem, hbmOld); 7805 DeleteDC(hdcMem); 7806 } 7807 7808 void drawPixmap(Sprite s, int x, int y) { 7809 BITMAP bm; 7810 7811 HDC hdcMem = CreateCompatibleDC(hdc); 7812 HBITMAP hbmOld = SelectObject(hdcMem, s.handle); 7813 7814 GetObject(s.handle, bm.sizeof, &bm); 7815 7816 BitBlt(hdc, x, y, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 7817 7818 SelectObject(hdcMem, hbmOld); 7819 DeleteDC(hdcMem); 7820 } 7821 7822 Size textSize(scope const(char)[] text) { 7823 bool dummyX; 7824 if(text.length == 0) { 7825 text = " "; 7826 dummyX = true; 7827 } 7828 RECT rect; 7829 WCharzBuffer buffer = WCharzBuffer(text); 7830 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, DT_CALCRECT); 7831 return Size(dummyX ? 0 : rect.right, rect.bottom); 7832 } 7833 7834 void drawText(int x, int y, int x2, int y2, scope const(char)[] text, uint alignment) { 7835 if(text.length && text[$-1] == '\n') 7836 text = text[0 .. $-1]; // tailing newlines are weird on windows... 7837 7838 WCharzBuffer buffer = WCharzBuffer(text); 7839 if(x2 == 0 && y2 == 0) 7840 TextOutW(hdc, x, y, buffer.ptr, cast(int) buffer.length); 7841 else { 7842 RECT rect; 7843 rect.left = x; 7844 rect.top = y; 7845 rect.right = x2; 7846 rect.bottom = y2; 7847 7848 uint mode = DT_LEFT; 7849 if(alignment & TextAlignment.Right) 7850 mode = DT_RIGHT; 7851 else if(alignment & TextAlignment.Center) 7852 mode = DT_CENTER; 7853 7854 // FIXME: vcenter on windows only works with single line, but I want it to work in all cases 7855 if(alignment & TextAlignment.VerticalCenter) 7856 mode |= DT_VCENTER | DT_SINGLELINE; 7857 7858 DrawTextW(hdc, buffer.ptr, cast(int) buffer.length, &rect, mode); 7859 } 7860 7861 /* 7862 uint mode; 7863 7864 if(alignment & TextAlignment.Center) 7865 mode = TA_CENTER; 7866 7867 SetTextAlign(hdc, mode); 7868 */ 7869 } 7870 7871 int fontHeight() { 7872 TEXTMETRIC metric; 7873 if(GetTextMetricsW(hdc, &metric)) { 7874 return metric.tmHeight; 7875 } 7876 7877 return 16; // idk just guessing here, maybe we should throw 7878 } 7879 7880 void drawPixel(int x, int y) { 7881 SetPixel(hdc, x, y, RGB(_activePen.color.r, _activePen.color.g, _activePen.color.b)); 7882 } 7883 7884 // The basic shapes, outlined 7885 7886 void drawLine(int x1, int y1, int x2, int y2) { 7887 MoveToEx(hdc, x1, y1, null); 7888 LineTo(hdc, x2, y2); 7889 } 7890 7891 void drawRectangle(int x, int y, int width, int height) { 7892 gdi.Rectangle(hdc, x, y, x + width, y + height); 7893 } 7894 7895 /// Arguments are the points of the bounding rectangle 7896 void drawEllipse(int x1, int y1, int x2, int y2) { 7897 Ellipse(hdc, x1, y1, x2, y2); 7898 } 7899 7900 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 7901 if((start % (360*64)) == (finish % (360*64))) 7902 drawEllipse(x1, y1, x1 + width, y1 + height); 7903 else { 7904 import core.stdc.math; 7905 float startAngle = start * 64 * 180 / 3.14159265; 7906 float endAngle = finish * 64 * 180 / 3.14159265; 7907 Arc(hdc, x1, y1, x1 + width, y1 + height, 7908 cast(int)(cos(startAngle) * width / 2 + x1), 7909 cast(int)(sin(startAngle) * height / 2 + y1), 7910 cast(int)(cos(endAngle) * width / 2 + x1), 7911 cast(int)(sin(endAngle) * height / 2 + y1), 7912 ); 7913 } 7914 } 7915 7916 void drawPolygon(Point[] vertexes) { 7917 POINT[] points; 7918 points.length = vertexes.length; 7919 7920 foreach(i, p; vertexes) { 7921 points[i].x = p.x; 7922 points[i].y = p.y; 7923 } 7924 7925 Polygon(hdc, points.ptr, cast(int) points.length); 7926 } 7927 } 7928 7929 7930 // Mix this into the SimpleWindow class 7931 mixin template NativeSimpleWindowImplementation() { 7932 int curHidden = 0; // counter 7933 __gshared static bool[string] knownWinClasses; 7934 static bool altPressed = false; 7935 7936 HANDLE oldCursor; 7937 7938 void hideCursor () { 7939 if(curHidden == 0) 7940 oldCursor = SetCursor(null); 7941 ++curHidden; 7942 } 7943 7944 void showCursor () { 7945 --curHidden; 7946 if(curHidden == 0) { 7947 SetCursor(currentCursor is null ? oldCursor : currentCursor); // show it immediately without waiting for mouse movement 7948 } 7949 } 7950 7951 7952 int minWidth = 0, minHeight = 0, maxWidth = int.max, maxHeight = int.max; 7953 7954 void setMinSize (int minwidth, int minheight) { 7955 minWidth = minwidth; 7956 minHeight = minheight; 7957 } 7958 void setMaxSize (int maxwidth, int maxheight) { 7959 maxWidth = maxwidth; 7960 maxHeight = maxheight; 7961 } 7962 7963 // FIXME i'm not sure that Windows has this functionality 7964 // though it is nonessential anyway. 7965 void setResizeGranularity (int granx, int grany) {} 7966 7967 ScreenPainter getPainter() { 7968 return ScreenPainter(this, hwnd); 7969 } 7970 7971 HBITMAP buffer; 7972 7973 void setTitle(string title) { 7974 WCharzBuffer bfr = WCharzBuffer(title); 7975 SetWindowTextW(hwnd, bfr.ptr); 7976 } 7977 7978 string getTitle() { 7979 auto len = GetWindowTextLengthW(hwnd); 7980 if (!len) 7981 return null; 7982 wchar[256] tmpBuffer; 7983 wchar[] buffer = (len <= tmpBuffer.length) ? tmpBuffer[] : new wchar[len]; 7984 auto len2 = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length); 7985 auto str = buffer[0 .. len2]; 7986 return makeUtf8StringFromWindowsString(str); 7987 } 7988 7989 void move(int x, int y) { 7990 RECT rect; 7991 GetWindowRect(hwnd, &rect); 7992 // move it while maintaining the same size... 7993 MoveWindow(hwnd, x, y, rect.right - rect.left, rect.bottom - rect.top, true); 7994 } 7995 7996 void resize(int w, int h) { 7997 RECT rect; 7998 GetWindowRect(hwnd, &rect); 7999 8000 RECT client; 8001 GetClientRect(hwnd, &client); 8002 8003 rect.right = rect.right - client.right + w; 8004 rect.bottom = rect.bottom - client.bottom + h; 8005 8006 // same position, new size for the client rectangle 8007 MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, true); 8008 8009 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 8010 } 8011 8012 void moveResize (int x, int y, int w, int h) { 8013 // what's given is the client rectangle, we need to adjust 8014 8015 RECT rect; 8016 rect.left = x; 8017 rect.top = y; 8018 rect.right = w + x; 8019 rect.bottom = h + y; 8020 if(!AdjustWindowRect(&rect, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) !is null)) 8021 throw new Exception("AdjustWindowRect"); 8022 8023 MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, true); 8024 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 8025 if (windowResized !is null) windowResized(w, h); 8026 } 8027 8028 version(without_opengl) {} else { 8029 HGLRC ghRC; 8030 HDC ghDC; 8031 } 8032 8033 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 8034 import std.conv : to; 8035 string cnamec; 8036 wstring cn;// = "DSimpleWindow\0"w.dup; 8037 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 8038 if (sdpyWindowClassStr is null || sdpyWindowClassStr[0] == 0) { 8039 cnamec = "DSimpleWindow"; 8040 } else { 8041 cnamec = sdpyWindowClass; 8042 } 8043 cn = cnamec.to!wstring ~ "\0"; // just in case, lol 8044 8045 HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null); 8046 8047 if(cnamec !in knownWinClasses) { 8048 WNDCLASSEX wc; 8049 8050 // FIXME: I might be able to use cbWndExtra to hold the pointer back 8051 // to the object. Maybe. 8052 wc.cbSize = wc.sizeof; 8053 wc.cbClsExtra = 0; 8054 wc.cbWndExtra = 0; 8055 wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW+1); // GetStockObject(WHITE_BRUSH); 8056 wc.hCursor = LoadCursorW(null, IDC_ARROW); 8057 wc.hIcon = LoadIcon(hInstance, null); 8058 wc.hInstance = hInstance; 8059 wc.lpfnWndProc = &WndProc; 8060 wc.lpszClassName = cn.ptr; 8061 wc.hIconSm = null; 8062 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 8063 if(!RegisterClassExW(&wc)) 8064 throw new WindowsApiException("RegisterClassExW"); 8065 knownWinClasses[cnamec] = true; 8066 } 8067 8068 int style; 8069 8070 // FIXME: windowType and customizationFlags 8071 final switch(windowType) { 8072 case WindowTypes.normal: 8073 style = WS_OVERLAPPEDWINDOW; 8074 break; 8075 case WindowTypes.undecorated: 8076 style = WS_POPUP | WS_SYSMENU; 8077 break; 8078 case WindowTypes.eventOnly: 8079 _hidden = true; 8080 break; 8081 case WindowTypes.dropdownMenu: 8082 case WindowTypes.popupMenu: 8083 case WindowTypes.notification: 8084 style = WS_POPUP; 8085 break; 8086 case WindowTypes.nestedChild: 8087 style = WS_CHILD; 8088 break; 8089 } 8090 8091 uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files 8092 if ((customizationFlags & WindowFlags.extraComposite) != 0) 8093 flags |= WS_EX_LAYERED; // composite window for better performance and effects support 8094 8095 hwnd = CreateWindowEx(flags, cn.ptr, toWStringz(title), style | WS_CLIPCHILDREN, // 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 8096 CW_USEDEFAULT, CW_USEDEFAULT, width, height, 8097 parent is null ? null : parent.impl.hwnd, null, hInstance, null); 8098 8099 if ((customizationFlags & WindowFlags.extraComposite) != 0) 8100 setOpacity(255); 8101 8102 SimpleWindow.nativeMapping[hwnd] = this; 8103 CapableOfHandlingNativeEvent.nativeHandleMapping[hwnd] = this; 8104 8105 if(windowType == WindowTypes.eventOnly) 8106 return; 8107 8108 HDC hdc = GetDC(hwnd); 8109 8110 8111 version(without_opengl) {} 8112 else { 8113 if(opengl == OpenGlOptions.yes) { 8114 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 8115 static if (SdpyIsUsingIVGLBinds) import iv.glbinds; // override druntime windows imports 8116 ghDC = hdc; 8117 PIXELFORMATDESCRIPTOR pfd; 8118 8119 pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; 8120 pfd.nVersion = 1; 8121 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; 8122 pfd.dwLayerMask = PFD_MAIN_PLANE; 8123 pfd.iPixelType = PFD_TYPE_RGBA; 8124 pfd.cColorBits = 24; 8125 pfd.cDepthBits = 24; 8126 pfd.cAccumBits = 0; 8127 pfd.cStencilBits = 8; // any reasonable OpenGL implementation should support this anyway 8128 8129 auto pixelformat = ChoosePixelFormat(hdc, &pfd); 8130 8131 if ((pixelformat = ChoosePixelFormat(hdc, &pfd)) == 0) 8132 throw new WindowsApiException("ChoosePixelFormat"); 8133 8134 if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) 8135 throw new WindowsApiException("SetPixelFormat"); 8136 8137 if (sdpyOpenGLContextVersion && wglCreateContextAttribsARB is null) { 8138 // windoze is idiotic: we have to have OpenGL context to get function addresses 8139 // so we will create fake context to get that stupid address 8140 auto tmpcc = wglCreateContext(ghDC); 8141 if (tmpcc !is null) { 8142 scope(exit) { wglMakeCurrent(ghDC, null); wglDeleteContext(tmpcc); } 8143 wglMakeCurrent(ghDC, tmpcc); 8144 wglInitOtherFunctions(); 8145 } 8146 } 8147 8148 if (wglCreateContextAttribsARB !is null && sdpyOpenGLContextVersion) { 8149 int[9] contextAttribs = [ 8150 WGL_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 8151 WGL_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 8152 WGL_CONTEXT_PROFILE_MASK_ARB, (sdpyOpenGLContextCompatible ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), 8153 // for modern context, set "forward compatibility" flag too 8154 (sdpyOpenGLContextCompatible ? 0/*None*/ : WGL_CONTEXT_FLAGS_ARB), WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, 8155 0/*None*/, 8156 ]; 8157 ghRC = wglCreateContextAttribsARB(ghDC, null, contextAttribs.ptr); 8158 if (ghRC is null && sdpyOpenGLContextAllowFallback) { 8159 // activate fallback mode 8160 // 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; 8161 ghRC = wglCreateContext(ghDC); 8162 } 8163 if (ghRC is null) 8164 throw new WindowsApiException("wglCreateContextAttribsARB"); 8165 } else { 8166 // try to do at least something 8167 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 8168 sdpyOpenGLContextVersion = 0; 8169 ghRC = wglCreateContext(ghDC); 8170 } 8171 if (ghRC is null) 8172 throw new WindowsApiException("wglCreateContext"); 8173 } 8174 } 8175 } 8176 8177 if(opengl == OpenGlOptions.no) { 8178 buffer = CreateCompatibleBitmap(hdc, width, height); 8179 8180 auto hdcBmp = CreateCompatibleDC(hdc); 8181 // make sure it's filled with a blank slate 8182 auto oldBmp = SelectObject(hdcBmp, buffer); 8183 auto oldBrush = SelectObject(hdcBmp, GetStockObject(WHITE_BRUSH)); 8184 auto oldPen = SelectObject(hdcBmp, GetStockObject(WHITE_PEN)); 8185 gdi.Rectangle(hdcBmp, 0, 0, width, height); 8186 SelectObject(hdcBmp, oldBmp); 8187 SelectObject(hdcBmp, oldBrush); 8188 SelectObject(hdcBmp, oldPen); 8189 DeleteDC(hdcBmp); 8190 8191 ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now 8192 } 8193 8194 // We want the window's client area to match the image size 8195 RECT rcClient, rcWindow; 8196 POINT ptDiff; 8197 GetClientRect(hwnd, &rcClient); 8198 GetWindowRect(hwnd, &rcWindow); 8199 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right; 8200 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom; 8201 MoveWindow(hwnd,rcWindow.left, rcWindow.top, width + ptDiff.x, height + ptDiff.y, true); 8202 8203 if ((customizationFlags&WindowFlags.dontAutoShow) == 0) { 8204 ShowWindow(hwnd, SW_SHOWNORMAL); 8205 } else { 8206 _hidden = true; 8207 } 8208 this._visibleForTheFirstTimeCalled = false; // hack! 8209 } 8210 8211 8212 void dispose() { 8213 if(buffer) 8214 DeleteObject(buffer); 8215 } 8216 8217 void closeWindow() { 8218 DestroyWindow(hwnd); 8219 } 8220 8221 bool setOpacity(ubyte alpha) { 8222 return SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == TRUE; 8223 } 8224 8225 HANDLE currentCursor; 8226 8227 // returns zero if it recognized the event 8228 static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { 8229 MouseEvent mouse; 8230 8231 void mouseEvent() { 8232 mouse.x = LOWORD(lParam) + offsetX; 8233 mouse.y = HIWORD(lParam) + offsetY; 8234 wind.mdx(mouse); 8235 mouse.modifierState = cast(int) wParam; 8236 mouse.window = wind; 8237 8238 if(wind.handleMouseEvent) 8239 wind.handleMouseEvent(mouse); 8240 } 8241 8242 switch(msg) { 8243 case WM_GETMINMAXINFO: 8244 MINMAXINFO* mmi = cast(MINMAXINFO*) lParam; 8245 8246 if(wind.minWidth > 0) { 8247 RECT rect; 8248 rect.left = 100; 8249 rect.top = 100; 8250 rect.right = wind.minWidth + 100; 8251 rect.bottom = wind.minHeight + 100; 8252 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 8253 throw new WindowsApiException("AdjustWindowRect"); 8254 8255 mmi.ptMinTrackSize.x = rect.right - rect.left; 8256 mmi.ptMinTrackSize.y = rect.bottom - rect.top; 8257 } 8258 8259 if(wind.maxWidth < int.max) { 8260 RECT rect; 8261 rect.left = 100; 8262 rect.top = 100; 8263 rect.right = wind.maxWidth + 100; 8264 rect.bottom = wind.maxHeight + 100; 8265 if(!AdjustWindowRect(&rect, GetWindowLong(wind.hwnd, GWL_STYLE), GetMenu(wind.hwnd) !is null)) 8266 throw new WindowsApiException("AdjustWindowRect"); 8267 8268 mmi.ptMaxTrackSize.x = rect.right - rect.left; 8269 mmi.ptMaxTrackSize.y = rect.bottom - rect.top; 8270 } 8271 break; 8272 case WM_CHAR: 8273 wchar c = cast(wchar) wParam; 8274 if(wind.handleCharEvent) 8275 wind.handleCharEvent(cast(dchar) c); 8276 break; 8277 case WM_SETFOCUS: 8278 case WM_KILLFOCUS: 8279 wind._focused = (msg == WM_SETFOCUS); 8280 if (msg == WM_SETFOCUS) altPressed = false; //k8: reset alt state on defocus (it is better than nothing...) 8281 if(wind.onFocusChange) 8282 wind.onFocusChange(msg == WM_SETFOCUS); 8283 break; 8284 case WM_SYSKEYDOWN: 8285 case WM_SYSKEYUP: 8286 case WM_KEYDOWN: 8287 case WM_KEYUP: 8288 KeyEvent ev; 8289 ev.key = cast(Key) wParam; 8290 ev.pressed = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); 8291 if ((msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP) && wParam == 0x12) ev.key = Key.Alt; // windows does it this way 8292 8293 ev.hardwareCode = (lParam & 0xff0000) >> 16; 8294 8295 if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000) 8296 ev.modifierState |= ModifierState.shift; 8297 //k8: this doesn't work; thanks for nothing, windows 8298 /*if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000) 8299 ev.modifierState |= ModifierState.alt;*/ 8300 if ((msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP) && wParam == 0x12) altPressed = (msg == WM_SYSKEYDOWN); 8301 if (altPressed) ev.modifierState |= ModifierState.alt; else ev.modifierState &= ~ModifierState.alt; 8302 if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000) 8303 ev.modifierState |= ModifierState.ctrl; 8304 if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000) 8305 ev.modifierState |= ModifierState.windows; 8306 if(GetKeyState(Key.NumLock)) 8307 ev.modifierState |= ModifierState.numLock; 8308 if(GetKeyState(Key.CapsLock)) 8309 ev.modifierState |= ModifierState.capsLock; 8310 8311 /+ 8312 // we always want to send the character too, so let's convert it 8313 ubyte[256] state; 8314 wchar[16] buffer; 8315 GetKeyboardState(state.ptr); 8316 ToUnicodeEx(wParam, lParam, state.ptr, buffer.ptr, buffer.length, 0, null); 8317 8318 foreach(dchar d; buffer) { 8319 ev.character = d; 8320 break; 8321 } 8322 +/ 8323 8324 ev.window = wind; 8325 if(wind.handleKeyEvent) 8326 wind.handleKeyEvent(ev); 8327 break; 8328 case 0x020a /*WM_MOUSEWHEEL*/: 8329 mouse.type = cast(MouseEventType) 1; 8330 mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp); 8331 mouseEvent(); 8332 break; 8333 case WM_MOUSEMOVE: 8334 mouse.type = cast(MouseEventType) 0; 8335 mouseEvent(); 8336 break; 8337 case WM_LBUTTONDOWN: 8338 case WM_LBUTTONDBLCLK: 8339 mouse.type = cast(MouseEventType) 1; 8340 mouse.button = MouseButton.left; 8341 mouse.doubleClick = msg == WM_LBUTTONDBLCLK; 8342 mouseEvent(); 8343 break; 8344 case WM_LBUTTONUP: 8345 mouse.type = cast(MouseEventType) 2; 8346 mouse.button = MouseButton.left; 8347 mouseEvent(); 8348 break; 8349 case WM_RBUTTONDOWN: 8350 case WM_RBUTTONDBLCLK: 8351 mouse.type = cast(MouseEventType) 1; 8352 mouse.button = MouseButton.right; 8353 mouse.doubleClick = msg == WM_RBUTTONDBLCLK; 8354 mouseEvent(); 8355 break; 8356 case WM_RBUTTONUP: 8357 mouse.type = cast(MouseEventType) 2; 8358 mouse.button = MouseButton.right; 8359 mouseEvent(); 8360 break; 8361 case WM_MBUTTONDOWN: 8362 case WM_MBUTTONDBLCLK: 8363 mouse.type = cast(MouseEventType) 1; 8364 mouse.button = MouseButton.middle; 8365 mouse.doubleClick = msg == WM_MBUTTONDBLCLK; 8366 mouseEvent(); 8367 break; 8368 case WM_MBUTTONUP: 8369 mouse.type = cast(MouseEventType) 2; 8370 mouse.button = MouseButton.middle; 8371 mouseEvent(); 8372 break; 8373 case WM_XBUTTONDOWN: 8374 case WM_XBUTTONDBLCLK: 8375 mouse.type = cast(MouseEventType) 1; 8376 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 8377 mouse.doubleClick = msg == WM_XBUTTONDBLCLK; 8378 mouseEvent(); 8379 return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs 8380 case WM_XBUTTONUP: 8381 mouse.type = cast(MouseEventType) 2; 8382 mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; 8383 mouseEvent(); 8384 return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx 8385 8386 default: return 1; 8387 } 8388 return 0; 8389 } 8390 8391 HWND hwnd; 8392 int oldWidth; 8393 int oldHeight; 8394 bool inSizeMove; 8395 8396 // the extern(Windows) wndproc should just forward to this 8397 LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { 8398 assert(hwnd is this.hwnd); 8399 8400 if(triggerEvents(hwnd, msg, wParam, lParam, 0, 0, this)) 8401 switch(msg) { 8402 case WM_SETCURSOR: 8403 if(cast(HWND) wParam !is hwnd) 8404 return 0; // further processing elsewhere 8405 8406 if(LOWORD(lParam) == HTCLIENT && (this.curHidden > 0 || currentCursor !is null)) { 8407 SetCursor(this.curHidden > 0 ? null : currentCursor); 8408 return 1; 8409 } else { 8410 return DefWindowProc(hwnd, msg, wParam, lParam); 8411 } 8412 //break; 8413 8414 case WM_CLOSE: 8415 if (this.closeQuery !is null) this.closeQuery(); else this.close(); 8416 break; 8417 case WM_DESTROY: 8418 if (this.onDestroyed !is null) try { this.onDestroyed(); } catch (Exception e) {} // sorry 8419 SimpleWindow.nativeMapping.remove(hwnd); 8420 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(hwnd); 8421 8422 bool anyImportant = false; 8423 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 8424 if(w.beingOpenKeepsAppOpen) { 8425 anyImportant = true; 8426 break; 8427 } 8428 if(!anyImportant) { 8429 PostQuitMessage(0); 8430 } 8431 break; 8432 case WM_SIZE: 8433 if(wParam == 1 /* SIZE_MINIMIZED */) 8434 break; 8435 _width = LOWORD(lParam); 8436 _height = HIWORD(lParam); 8437 8438 // I want to avoid tearing in the windows (my code is inefficient 8439 // so this is a hack around that) so while sizing, we don't trigger, 8440 // but we do want to trigger on events like mazimize. 8441 if(!inSizeMove) 8442 goto size_changed; 8443 break; 8444 // I don't like the tearing I get when redrawing on WM_SIZE 8445 // (I know there's other ways to fix that but I don't like that behavior anyway) 8446 // so instead it is going to redraw only at the end of a size. 8447 case 0x0231: /* WM_ENTERSIZEMOVE */ 8448 oldWidth = this.width; 8449 oldHeight = this.height; 8450 inSizeMove = true; 8451 break; 8452 case 0x0232: /* WM_EXITSIZEMOVE */ 8453 inSizeMove = false; 8454 // nothing relevant changed, don't bother redrawing 8455 if(oldWidth == width && oldHeight == height) 8456 break; 8457 8458 size_changed: 8459 8460 // note: OpenGL windows don't use a backing bmp, so no need to change them 8461 // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing 8462 if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { 8463 // gotta get the double buffer bmp to match the window 8464 // FIXME: could this be more efficient? It isn't really necessary to make 8465 // a new buffer if we're sizing down at least. 8466 auto hdc = GetDC(hwnd); 8467 auto oldBuffer = buffer; 8468 buffer = CreateCompatibleBitmap(hdc, width, height); 8469 8470 auto hdcBmp = CreateCompatibleDC(hdc); 8471 auto oldBmp = SelectObject(hdcBmp, buffer); 8472 8473 auto hdcOldBmp = CreateCompatibleDC(hdc); 8474 auto oldOldBmp = SelectObject(hdcOldBmp, oldBmp); 8475 8476 BitBlt(hdcBmp, 0, 0, width, height, hdcOldBmp, oldWidth, oldHeight, SRCCOPY); 8477 8478 SelectObject(hdcOldBmp, oldOldBmp); 8479 DeleteDC(hdcOldBmp); 8480 8481 SelectObject(hdcBmp, oldBmp); 8482 DeleteDC(hdcBmp); 8483 8484 ReleaseDC(hwnd, hdc); 8485 8486 DeleteObject(oldBuffer); 8487 } 8488 8489 version(without_opengl) {} else 8490 if(openglMode == OpenGlOptions.yes && resizability == Resizability.automaticallyScaleIfPossible) { 8491 glViewport(0, 0, width, height); 8492 } 8493 8494 if(windowResized !is null) 8495 windowResized(width, height); 8496 break; 8497 case WM_ERASEBKGND: 8498 // call `visibleForTheFirstTime` here, so we can do initialization as early as possible 8499 if (!this._visibleForTheFirstTimeCalled) { 8500 this._visibleForTheFirstTimeCalled = true; 8501 if (this.visibleForTheFirstTime !is null) { 8502 version(without_opengl) {} else { 8503 if(openglMode == OpenGlOptions.yes) { 8504 this.setAsCurrentOpenGlContextNT(); 8505 glViewport(0, 0, width, height); 8506 } 8507 } 8508 this.visibleForTheFirstTime(); 8509 } 8510 } 8511 // block it in OpenGL mode, 'cause no sane person will (or should) draw windows controls over OpenGL scene 8512 version(without_opengl) {} else { 8513 if (openglMode == OpenGlOptions.yes) return 1; 8514 } 8515 // call windows default handler, so it can paint standard controls 8516 goto default; 8517 case WM_CTLCOLORBTN: 8518 case WM_CTLCOLORSTATIC: 8519 SetBkMode(cast(HDC) wParam, TRANSPARENT); 8520 return cast(typeof(return)) //GetStockObject(NULL_BRUSH); 8521 GetSysColorBrush(COLOR_3DFACE); 8522 //break; 8523 case WM_SHOWWINDOW: 8524 this._visible = (wParam != 0); 8525 if (!this._visibleForTheFirstTimeCalled && this._visible) { 8526 this._visibleForTheFirstTimeCalled = true; 8527 if (this.visibleForTheFirstTime !is null) { 8528 version(without_opengl) {} else { 8529 if(openglMode == OpenGlOptions.yes) { 8530 this.setAsCurrentOpenGlContextNT(); 8531 glViewport(0, 0, width, height); 8532 } 8533 } 8534 this.visibleForTheFirstTime(); 8535 } 8536 } 8537 if (this.visibilityChanged !is null) this.visibilityChanged(this._visible); 8538 break; 8539 case WM_PAINT: { 8540 if (!this._visibleForTheFirstTimeCalled) { 8541 this._visibleForTheFirstTimeCalled = true; 8542 if (this.visibleForTheFirstTime !is null) { 8543 version(without_opengl) {} else { 8544 if(openglMode == OpenGlOptions.yes) { 8545 this.setAsCurrentOpenGlContextNT(); 8546 glViewport(0, 0, width, height); 8547 } 8548 } 8549 this.visibleForTheFirstTime(); 8550 } 8551 } 8552 8553 BITMAP bm; 8554 PAINTSTRUCT ps; 8555 8556 HDC hdc = BeginPaint(hwnd, &ps); 8557 8558 if(openglMode == OpenGlOptions.no) { 8559 8560 HDC hdcMem = CreateCompatibleDC(hdc); 8561 HBITMAP hbmOld = SelectObject(hdcMem, buffer); 8562 8563 GetObject(buffer, bm.sizeof, &bm); 8564 8565 // FIXME: only BitBlt the invalidated rectangle, not the whole thing 8566 if(resizability == Resizability.automaticallyScaleIfPossible) 8567 StretchBlt(hdc, 0, 0, this.width, this.height, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 8568 else 8569 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); 8570 8571 SelectObject(hdcMem, hbmOld); 8572 DeleteDC(hdcMem); 8573 EndPaint(hwnd, &ps); 8574 } else { 8575 EndPaint(hwnd, &ps); 8576 version(without_opengl) {} else 8577 redrawOpenGlSceneNow(); 8578 } 8579 } break; 8580 default: 8581 return DefWindowProc(hwnd, msg, wParam, lParam); 8582 } 8583 return 0; 8584 8585 } 8586 } 8587 8588 mixin template NativeImageImplementation() { 8589 HBITMAP handle; 8590 ubyte* rawData; 8591 8592 final: 8593 8594 Color getPixel(int x, int y) { 8595 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8596 // remember, bmps are upside down 8597 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8598 8599 Color c; 8600 c.a = 255; 8601 c.b = rawData[offset + 0]; 8602 c.g = rawData[offset + 1]; 8603 c.r = rawData[offset + 2]; 8604 return c; 8605 } 8606 8607 void setPixel(int x, int y, Color c) { 8608 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8609 // remember, bmps are upside down 8610 auto offset = itemsPerLine * (height - y - 1) + x * 3; 8611 8612 rawData[offset + 0] = c.b; 8613 rawData[offset + 1] = c.g; 8614 rawData[offset + 2] = c.r; 8615 } 8616 8617 void convertToRgbaBytes(ubyte[] where) { 8618 assert(where.length == this.width * this.height * 4); 8619 8620 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8621 int idx = 0; 8622 int offset = itemsPerLine * (height - 1); 8623 // remember, bmps are upside down 8624 for(int y = height - 1; y >= 0; y--) { 8625 auto offsetStart = offset; 8626 for(int x = 0; x < width; x++) { 8627 where[idx + 0] = rawData[offset + 2]; // r 8628 where[idx + 1] = rawData[offset + 1]; // g 8629 where[idx + 2] = rawData[offset + 0]; // b 8630 where[idx + 3] = 255; // a 8631 idx += 4; 8632 offset += 3; 8633 } 8634 8635 offset = offsetStart - itemsPerLine; 8636 } 8637 } 8638 8639 void setFromRgbaBytes(in ubyte[] what) { 8640 assert(what.length == this.width * this.height * 4); 8641 8642 auto itemsPerLine = ((cast(int) width * 3 + 3) / 4) * 4; 8643 int idx = 0; 8644 int offset = itemsPerLine * (height - 1); 8645 // remember, bmps are upside down 8646 for(int y = height - 1; y >= 0; y--) { 8647 auto offsetStart = offset; 8648 for(int x = 0; x < width; x++) { 8649 rawData[offset + 2] = what[idx + 0]; // r 8650 rawData[offset + 1] = what[idx + 1]; // g 8651 rawData[offset + 0] = what[idx + 2]; // b 8652 //where[idx + 3] = 255; // a 8653 idx += 4; 8654 offset += 3; 8655 } 8656 8657 offset = offsetStart - itemsPerLine; 8658 } 8659 } 8660 8661 8662 void createImage(int width, int height, bool forcexshm=false) { 8663 BITMAPINFO infoheader; 8664 infoheader.bmiHeader.biSize = infoheader.bmiHeader.sizeof; 8665 infoheader.bmiHeader.biWidth = width; 8666 infoheader.bmiHeader.biHeight = height; 8667 infoheader.bmiHeader.biPlanes = 1; 8668 infoheader.bmiHeader.biBitCount = 24; 8669 infoheader.bmiHeader.biCompression = BI_RGB; 8670 8671 handle = CreateDIBSection( 8672 null, 8673 &infoheader, 8674 DIB_RGB_COLORS, 8675 cast(void**) &rawData, 8676 null, 8677 0); 8678 if(handle is null) 8679 throw new WindowsApiException("create image failed"); 8680 8681 } 8682 8683 void dispose() { 8684 DeleteObject(handle); 8685 } 8686 } 8687 8688 enum KEY_ESCAPE = 27; 8689 } 8690 version(X11) { 8691 /// This is the default font used. You might change this before doing anything else with 8692 /// the library if you want to try something else. Surround that in `static if(UsingSimpledisplayX11)` 8693 /// for cross-platform compatibility. 8694 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 8695 //__gshared string xfontstr = "-*-dejavu sans-medium-r-*-*-12-*-*-*-*-*-*-*"; 8696 __gshared string xfontstr = "-*-lucida-medium-r-normal-sans-12-*-*-*-*-*-*-*"; 8697 //__gshared string xfontstr = "-*-fixed-medium-r-*-*-14-*-*-*-*-*-*-*"; 8698 8699 alias int delegate(XEvent) NativeEventHandler; 8700 alias Window NativeWindowHandle; 8701 8702 enum KEY_ESCAPE = 9; 8703 8704 mixin template NativeScreenPainterImplementation() { 8705 Display* display; 8706 Drawable d; 8707 Drawable destiny; 8708 8709 // FIXME: should the gc be static too so it isn't recreated every time draw is called? 8710 GC gc; 8711 8712 __gshared bool fontAttempted; 8713 8714 __gshared XFontStruct* defaultfont; 8715 __gshared XFontSet defaultfontset; 8716 8717 XFontStruct* font; 8718 XFontSet fontset; 8719 8720 void create(NativeWindowHandle window) { 8721 this.display = XDisplayConnection.get(); 8722 8723 Drawable buffer = None; 8724 if(auto sw = cast(SimpleWindow) this.window) { 8725 buffer = sw.impl.buffer; 8726 this.destiny = cast(Drawable) window; 8727 } else { 8728 buffer = cast(Drawable) window; 8729 this.destiny = None; 8730 } 8731 8732 this.d = cast(Drawable) buffer; 8733 8734 auto dgc = DefaultGC(display, DefaultScreen(display)); 8735 8736 this.gc = XCreateGC(display, d, 0, null); 8737 8738 XCopyGC(display, dgc, 0xffffffff, this.gc); 8739 8740 if(!fontAttempted) { 8741 font = XLoadQueryFont(display, xfontstr.ptr); 8742 // if the user font choice fails, fixed is pretty reliable (required by X to start!) and not bad either 8743 if(font is null) 8744 font = XLoadQueryFont(display, "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*".ptr); 8745 8746 char** lol; 8747 int lol2; 8748 char* lol3; 8749 fontset = XCreateFontSet(display, xfontstr.ptr, &lol, &lol2, &lol3); 8750 8751 fontAttempted = true; 8752 8753 defaultfont = font; 8754 defaultfontset = fontset; 8755 } 8756 8757 font = defaultfont; 8758 fontset = defaultfontset; 8759 8760 if(font) { 8761 XSetFont(display, gc, font.fid); 8762 } 8763 } 8764 8765 arsd.color.Rectangle _clipRectangle; 8766 void setClipRectangle(int x, int y, int width, int height) { 8767 _clipRectangle = arsd.color.Rectangle(Point(x, y), Size(width, height)); 8768 if(width == 0 || height == 0) 8769 XSetClipMask(display, gc, None); 8770 else { 8771 XRectangle[1] rects; 8772 rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height); 8773 XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0); 8774 } 8775 } 8776 8777 8778 void setFont(OperatingSystemFont font) { 8779 if(font && font.font) { 8780 this.font = font.font; 8781 this.fontset = font.fontset; 8782 XSetFont(display, gc, font.font.fid); 8783 } else { 8784 this.font = defaultfont; 8785 this.fontset = defaultfontset; 8786 } 8787 8788 } 8789 8790 void dispose() { 8791 this.rasterOp = RasterOp.normal; 8792 8793 // FIXME: this.window.width/height is probably wrong 8794 8795 // src x,y then dest x, y 8796 if(destiny != None) { 8797 XSetClipMask(display, gc, None); 8798 XCopyArea(display, d, destiny, gc, 0, 0, this.window.width, this.window.height, 0, 0); 8799 } 8800 8801 XFreeGC(display, gc); 8802 8803 version(none) // we don't want to free it because we can use it later 8804 if(font) 8805 XFreeFont(display, font); 8806 version(none) // we don't want to free it because we can use it later 8807 if(fontset) 8808 XFreeFontSet(display, fontset); 8809 XFlush(display); 8810 8811 if(window.paintingFinishedDg !is null) 8812 window.paintingFinishedDg(); 8813 } 8814 8815 bool backgroundIsNotTransparent = true; 8816 bool foregroundIsNotTransparent = true; 8817 8818 bool _penInitialized = false; 8819 Pen _activePen; 8820 8821 Color _outlineColor; 8822 Color _fillColor; 8823 8824 @property void pen(Pen p) { 8825 if(_penInitialized && p == _activePen) { 8826 return; 8827 } 8828 _penInitialized = true; 8829 _activePen = p; 8830 _outlineColor = p.color; 8831 8832 int style; 8833 8834 byte dashLength; 8835 8836 final switch(p.style) { 8837 case Pen.Style.Solid: 8838 style = 0 /*LineSolid*/; 8839 break; 8840 case Pen.Style.Dashed: 8841 style = 1 /*LineOnOffDash*/; 8842 dashLength = 4; 8843 break; 8844 case Pen.Style.Dotted: 8845 style = 1 /*LineOnOffDash*/; 8846 dashLength = 1; 8847 break; 8848 } 8849 8850 XSetLineAttributes(display, gc, p.width, style, 0, 0); 8851 if(dashLength) 8852 XSetDashes(display, gc, 0, &dashLength, 1); 8853 8854 if(p.color.a == 0) { 8855 foregroundIsNotTransparent = false; 8856 return; 8857 } 8858 8859 foregroundIsNotTransparent = true; 8860 8861 XSetForeground(display, gc, colorToX(p.color, display)); 8862 } 8863 8864 RasterOp _currentRasterOp; 8865 bool _currentRasterOpInitialized = false; 8866 @property void rasterOp(RasterOp op) { 8867 if(_currentRasterOpInitialized && _currentRasterOp == op) 8868 return; 8869 _currentRasterOp = op; 8870 _currentRasterOpInitialized = true; 8871 int mode; 8872 final switch(op) { 8873 case RasterOp.normal: 8874 mode = GXcopy; 8875 break; 8876 case RasterOp.xor: 8877 mode = GXxor; 8878 break; 8879 } 8880 XSetFunction(display, gc, mode); 8881 } 8882 8883 8884 bool _fillColorInitialized = false; 8885 8886 @property void fillColor(Color c) { 8887 if(_fillColorInitialized && _fillColor == c) 8888 return; // already good, no need to waste time calling it 8889 _fillColor = c; 8890 _fillColorInitialized = true; 8891 if(c.a == 0) { 8892 backgroundIsNotTransparent = false; 8893 return; 8894 } 8895 8896 backgroundIsNotTransparent = true; 8897 8898 XSetBackground(display, gc, colorToX(c, display)); 8899 8900 } 8901 8902 void swapColors() { 8903 auto tmp = _fillColor; 8904 fillColor = _outlineColor; 8905 auto newPen = _activePen; 8906 newPen.color = tmp; 8907 pen(newPen); 8908 } 8909 8910 uint colorToX(Color c, Display* display) { 8911 auto visual = DefaultVisual(display, DefaultScreen(display)); 8912 import core.bitop; 8913 uint color = 0; 8914 { 8915 auto startBit = bsf(visual.red_mask); 8916 auto lastBit = bsr(visual.red_mask); 8917 auto r = cast(uint) c.r; 8918 r >>= 7 - (lastBit - startBit); 8919 r <<= startBit; 8920 color |= r; 8921 } 8922 { 8923 auto startBit = bsf(visual.green_mask); 8924 auto lastBit = bsr(visual.green_mask); 8925 auto g = cast(uint) c.g; 8926 g >>= 7 - (lastBit - startBit); 8927 g <<= startBit; 8928 color |= g; 8929 } 8930 { 8931 auto startBit = bsf(visual.blue_mask); 8932 auto lastBit = bsr(visual.blue_mask); 8933 auto b = cast(uint) c.b; 8934 b >>= 7 - (lastBit - startBit); 8935 b <<= startBit; 8936 color |= b; 8937 } 8938 8939 8940 8941 return color; 8942 } 8943 8944 void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { 8945 // source x, source y 8946 if(i.usingXshm) 8947 XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false); 8948 else 8949 XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h); 8950 } 8951 8952 void drawPixmap(Sprite s, int x, int y) { 8953 XCopyArea(display, s.handle, d, gc, 0, 0, s.width, s.height, x, y); 8954 } 8955 8956 int fontHeight() { 8957 if(font) 8958 return font.max_bounds.ascent + font.max_bounds.descent; 8959 return 12; // pretty common default... 8960 } 8961 8962 Size textSize(in char[] text) { 8963 auto maxWidth = 0; 8964 auto lineHeight = fontHeight; 8965 int h = text.length ? 0 : lineHeight + 4; // if text is empty, it still gives the line height 8966 foreach(line; text.split('\n')) { 8967 int textWidth; 8968 if(font) 8969 // FIXME: unicode 8970 textWidth = XTextWidth( font, line.ptr, cast(int) line.length); 8971 else 8972 textWidth = fontHeight / 2 * cast(int) line.length; // if no font is loaded, it is prolly Fixed, which is a 2:1 ratio 8973 8974 if(textWidth > maxWidth) 8975 maxWidth = textWidth; 8976 h += lineHeight + 4; 8977 } 8978 return Size(maxWidth, h); 8979 } 8980 8981 void drawText(in int x, in int y, in int x2, in int y2, in char[] originalText, in uint alignment) { 8982 // FIXME: we should actually draw unicode.. but until then, I'm going to strip out multibyte chars 8983 const(char)[] text; 8984 if(fontset) 8985 text = originalText; 8986 else { 8987 text.reserve(originalText.length); 8988 // the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those 8989 // then strip the rest so there isn't garbage 8990 foreach(dchar ch; originalText) 8991 if(ch < 256) 8992 text ~= cast(ubyte) ch; 8993 else 8994 text ~= 191; // FIXME: using a random character to fill the space 8995 } 8996 if(text.length == 0) 8997 return; 8998 8999 9000 int textHeight = 12; 9001 9002 // FIXME: should we clip it to the bounding box? 9003 9004 if(font) { 9005 textHeight = font.max_bounds.ascent + font.max_bounds.descent; 9006 } 9007 9008 auto lines = text.split('\n'); 9009 9010 auto lineHeight = textHeight; 9011 textHeight *= lines.length; 9012 9013 int cy = y; 9014 9015 if(alignment & TextAlignment.VerticalBottom) { 9016 assert(y2); 9017 auto h = y2 - y; 9018 if(h > textHeight) { 9019 cy += h - textHeight; 9020 cy -= lineHeight / 2; 9021 } 9022 } else if(alignment & TextAlignment.VerticalCenter) { 9023 assert(y2); 9024 auto h = y2 - y; 9025 if(textHeight < h) { 9026 cy += (h - textHeight) / 2; 9027 //cy -= lineHeight / 4; 9028 } 9029 } 9030 9031 foreach(line; text.split('\n')) { 9032 int textWidth; 9033 if(font) 9034 // FIXME: unicode 9035 textWidth = XTextWidth( font, line.ptr, cast(int) line.length); 9036 else 9037 textWidth = 12 * cast(int) line.length; 9038 9039 int px = x, py = cy; 9040 9041 if(alignment & TextAlignment.Center) { 9042 assert(x2); 9043 auto w = x2 - x; 9044 if(w > textWidth) 9045 px += (w - textWidth) / 2; 9046 } else if(alignment & TextAlignment.Right) { 9047 assert(x2); 9048 auto pos = x2 - textWidth; 9049 if(pos > x) 9050 px = pos; 9051 } 9052 9053 if(fontset) 9054 Xutf8DrawString(display, d, fontset, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 9055 9056 else 9057 XDrawString(display, d, gc, px, py + (font ? font.max_bounds.ascent : lineHeight), line.ptr, cast(int) line.length); 9058 cy += lineHeight + 4; 9059 } 9060 } 9061 9062 void drawPixel(int x, int y) { 9063 XDrawPoint(display, d, gc, x, y); 9064 } 9065 9066 // The basic shapes, outlined 9067 9068 void drawLine(int x1, int y1, int x2, int y2) { 9069 if(foregroundIsNotTransparent) 9070 XDrawLine(display, d, gc, x1, y1, x2, y2); 9071 } 9072 9073 void drawRectangle(int x, int y, int width, int height) { 9074 if(backgroundIsNotTransparent) { 9075 swapColors(); 9076 XFillRectangle(display, d, gc, x+1, y+1, width-2, height-2); // Need to ensure pixels are only drawn once... 9077 swapColors(); 9078 } 9079 if(foregroundIsNotTransparent) 9080 XDrawRectangle(display, d, gc, x, y, width - 1, height - 1); 9081 } 9082 9083 /// Arguments are the points of the bounding rectangle 9084 void drawEllipse(int x1, int y1, int x2, int y2) { 9085 drawArc(x1, y1, x2 - x1, y2 - y1, 0, 360 * 64); 9086 } 9087 9088 // NOTE: start and finish are in units of degrees * 64 9089 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 9090 if(backgroundIsNotTransparent) { 9091 swapColors(); 9092 XFillArc(display, d, gc, x1, y1, width, height, start, finish); 9093 swapColors(); 9094 } 9095 if(foregroundIsNotTransparent) 9096 XDrawArc(display, d, gc, x1, y1, width, height, start, finish); 9097 } 9098 9099 void drawPolygon(Point[] vertexes) { 9100 XPoint[16] pointsBuffer; 9101 XPoint[] points; 9102 if(vertexes.length <= pointsBuffer.length) 9103 points = pointsBuffer[0 .. vertexes.length]; 9104 else 9105 points.length = vertexes.length; 9106 9107 foreach(i, p; vertexes) { 9108 points[i].x = cast(short) p.x; 9109 points[i].y = cast(short) p.y; 9110 } 9111 9112 if(backgroundIsNotTransparent) { 9113 swapColors(); 9114 XFillPolygon(display, d, gc, points.ptr, cast(int) points.length, PolygonShape.Complex, CoordMode.CoordModeOrigin); 9115 swapColors(); 9116 } 9117 if(foregroundIsNotTransparent) { 9118 XDrawLines(display, d, gc, points.ptr, cast(int) points.length, CoordMode.CoordModeOrigin); 9119 } 9120 } 9121 } 9122 9123 class XDisconnectException : Exception { 9124 bool userRequested; 9125 this(bool userRequested = true) { 9126 this.userRequested = userRequested; 9127 super("X disconnected"); 9128 } 9129 } 9130 9131 /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a Display* 9132 class XDisplayConnection { 9133 private __gshared Display* display; 9134 private __gshared XIM xim; 9135 private __gshared char* displayName; 9136 9137 private __gshared int connectionSequence_; 9138 9139 /// use this for lazy caching when reconnection 9140 static int connectionSequenceNumber() { return connectionSequence_; } 9141 9142 /// Attempts recreation of state, may require application assistance 9143 /// You MUST call this OUTSIDE the event loop. Let the exception kill the loop, 9144 /// then call this, and if successful, reenter the loop. 9145 static void discardAndRecreate(string newDisplayString = null) { 9146 if(insideXEventLoop) 9147 throw new Error("You MUST call discardAndRecreate from OUTSIDE the event loop"); 9148 9149 // 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 9150 auto chnenhm = CapableOfHandlingNativeEvent.nativeHandleMapping.dup; 9151 9152 foreach(handle; chnenhm) { 9153 handle.discardConnectionState(); 9154 } 9155 9156 discardState(); 9157 9158 if(newDisplayString !is null) 9159 setDisplayName(newDisplayString); 9160 9161 auto display = get(); 9162 9163 foreach(handle; chnenhm) { 9164 handle.recreateAfterDisconnect(); 9165 } 9166 } 9167 9168 private __gshared EventMask rootEventMask; 9169 9170 /++ 9171 Requests the specified input from the root window on the connection, in addition to any other request. 9172 9173 9174 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. 9175 9176 $(WARNING it calls XSelectInput itself, which will override any other root window input you have!) 9177 +/ 9178 static void addRootInput(EventMask mask) { 9179 auto old = rootEventMask; 9180 rootEventMask |= mask; 9181 get(); // to ensure display connected 9182 if(display !is null && rootEventMask != old) 9183 XSelectInput(display, RootWindow(display, DefaultScreen(display)), rootEventMask); 9184 } 9185 9186 static void discardState() { 9187 freeImages(); 9188 9189 foreach(atomPtr; interredAtoms) 9190 *atomPtr = 0; 9191 interredAtoms = null; 9192 interredAtoms.assumeSafeAppend(); 9193 9194 ScreenPainterImplementation.fontAttempted = false; 9195 ScreenPainterImplementation.defaultfont = null; 9196 ScreenPainterImplementation.defaultfontset = null; 9197 9198 Image.impl.xshmQueryCompleted = false; 9199 Image.impl._xshmAvailable = false; 9200 9201 SimpleWindow.nativeMapping = null; 9202 CapableOfHandlingNativeEvent.nativeHandleMapping = null; 9203 // GlobalHotkeyManager 9204 9205 display = null; 9206 xim = null; 9207 } 9208 9209 // Do you want to know why do we need all this horrible-looking code? See comment at the bottom. 9210 private static void createXIM () { 9211 import core.stdc.locale : setlocale, LC_ALL; 9212 import core.stdc.stdio : stderr, fprintf; 9213 import core.stdc.stdlib : free; 9214 import core.stdc.string : strdup; 9215 9216 static immutable string[3] mtry = [ null, "@im=local", "@im=" ]; 9217 9218 auto olocale = strdup(setlocale(LC_ALL, null)); 9219 setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8")); 9220 scope(exit) { setlocale(LC_ALL, olocale); free(olocale); } 9221 9222 //fprintf(stderr, "opening IM...\n"); 9223 foreach (string s; mtry) { 9224 if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal 9225 if ((xim = XOpenIM(display, null, null, null)) !is null) return; 9226 } 9227 fprintf(stderr, "createXIM: XOpenIM failed!\n"); 9228 } 9229 9230 // for X11 we will keep all XShm-allocated images in this list, so we can free 'em on connection closing. 9231 // we'll use glibc malloc()/free(), 'cause `unregisterImage()` can be called from object dtor. 9232 static struct ImgList { 9233 size_t img; // class; hide it from GC 9234 ImgList* next; 9235 } 9236 9237 static __gshared ImgList* imglist = null; 9238 static __gshared bool imglistLocked = false; // true: don't register and unregister images 9239 9240 static void registerImage (Image img) { 9241 if (!imglistLocked && img !is null) { 9242 import core.stdc.stdlib : malloc; 9243 auto it = cast(ImgList*)malloc(ImgList.sizeof); 9244 assert(it !is null); // do proper checks 9245 it.img = cast(size_t)cast(void*)img; 9246 it.next = imglist; 9247 imglist = it; 9248 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("registering image %p\n", cast(void*)img); } 9249 } 9250 } 9251 9252 static void unregisterImage (Image img) { 9253 if (!imglistLocked && img !is null) { 9254 import core.stdc.stdlib : free; 9255 ImgList* prev = null; 9256 ImgList* cur = imglist; 9257 while (cur !is null) { 9258 if (cur.img == cast(size_t)cast(void*)img) break; // i found her! 9259 prev = cur; 9260 cur = cur.next; 9261 } 9262 if (cur !is null) { 9263 if (prev is null) imglist = cur.next; else prev.next = cur.next; 9264 free(cur); 9265 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("unregistering image %p\n", cast(void*)img); } 9266 } else { 9267 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("trying to unregister unknown image %p\n", cast(void*)img); } 9268 } 9269 } 9270 } 9271 9272 static void freeImages () { // needed for discardAndRecreate 9273 imglistLocked = true; 9274 scope(exit) imglistLocked = false; 9275 ImgList* cur = imglist; 9276 ImgList* next = null; 9277 while (cur !is null) { 9278 import core.stdc.stdlib : free; 9279 next = cur.next; 9280 version(sdpy_debug_xshm) { import core.stdc.stdio : printf; printf("disposing image %p\n", cast(void*)cur.img); } 9281 (cast(Image)cast(void*)cur.img).dispose(); 9282 free(cur); 9283 cur = next; 9284 } 9285 imglist = null; 9286 } 9287 9288 /// can be used to override normal handling of display name 9289 /// from environment and/or command line 9290 static setDisplayName(string newDisplayName) { 9291 displayName = cast(char*) (newDisplayName ~ '\0'); 9292 } 9293 9294 /// resets to the default display string 9295 static resetDisplayName() { 9296 displayName = null; 9297 } 9298 9299 /// 9300 static Display* get() { 9301 if(display is null) { 9302 display = XOpenDisplay(displayName); 9303 connectionSequence_++; 9304 if(display is null) 9305 throw new Exception("Unable to open X display"); 9306 XSetIOErrorHandler(&x11ioerrCB); 9307 Bool sup; 9308 XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released 9309 createXIM(); 9310 version(with_eventloop) { 9311 import arsd.eventloop; 9312 addFileEventListeners(display.fd, &eventListener, null, null); 9313 } 9314 } 9315 9316 return display; 9317 } 9318 9319 extern(C) 9320 static int x11ioerrCB(Display* dpy) { 9321 throw new XDisconnectException(false); 9322 } 9323 9324 version(with_eventloop) { 9325 import arsd.eventloop; 9326 static void eventListener(OsFileHandle fd) { 9327 //this.mtLock(); 9328 //scope(exit) this.mtUnlock(); 9329 while(XPending(display)) 9330 doXNextEvent(display); 9331 } 9332 } 9333 9334 // close connection on program exit -- we need this to properly free all images 9335 shared static ~this () { close(); } 9336 9337 /// 9338 static void close() { 9339 if(display is null) 9340 return; 9341 9342 version(with_eventloop) { 9343 import arsd.eventloop; 9344 removeFileEventListeners(display.fd); 9345 } 9346 9347 // now remove all registered images to prevent shared memory leaks 9348 freeImages(); 9349 9350 XCloseDisplay(display); 9351 display = null; 9352 } 9353 } 9354 9355 mixin template NativeImageImplementation() { 9356 XImage* handle; 9357 ubyte* rawData; 9358 9359 XShmSegmentInfo shminfo; 9360 9361 __gshared bool xshmQueryCompleted; 9362 __gshared bool _xshmAvailable; 9363 public static @property bool xshmAvailable() { 9364 if(!xshmQueryCompleted) { 9365 int i1, i2, i3; 9366 xshmQueryCompleted = true; 9367 _xshmAvailable = XQueryExtension(XDisplayConnection.get(), "MIT-SHM", &i1, &i2, &i3) != 0; 9368 } 9369 return _xshmAvailable; 9370 } 9371 9372 bool usingXshm; 9373 final: 9374 9375 void createImage(int width, int height, bool forcexshm=false) { 9376 auto display = XDisplayConnection.get(); 9377 assert(display !is null); 9378 auto screen = DefaultScreen(display); 9379 9380 // it will only use shared memory for somewhat largish images, 9381 // since otherwise we risk wasting shared memory handles on a lot of little ones 9382 if (xshmAvailable && (forcexshm || (width > 100 && height > 100))) { 9383 usingXshm = true; 9384 handle = XShmCreateImage( 9385 display, 9386 DefaultVisual(display, screen), 9387 24, 9388 ImageFormat.ZPixmap, 9389 null, 9390 &shminfo, 9391 width, height); 9392 assert(handle !is null); 9393 9394 assert(handle.bytes_per_line == 4 * width); 9395 shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */); 9396 //import std.conv; import core.stdc.errno; 9397 assert(shminfo.shmid >= 0);//, to!string(errno)); 9398 handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0); 9399 assert(rawData != cast(ubyte*) -1); 9400 shminfo.readOnly = 0; 9401 XShmAttach(display, &shminfo); 9402 XDisplayConnection.registerImage(this); 9403 } else { 9404 if (forcexshm) throw new Exception("can't create XShm Image"); 9405 // This actually needs to be malloc to avoid a double free error when XDestroyImage is called 9406 import core.stdc.stdlib : malloc; 9407 rawData = cast(ubyte*) malloc(width * height * 4); 9408 9409 handle = XCreateImage( 9410 display, 9411 DefaultVisual(display, screen), 9412 24, // bpp 9413 ImageFormat.ZPixmap, 9414 0, // offset 9415 rawData, 9416 width, height, 9417 8 /* FIXME */, 4 * width); // padding, bytes per line 9418 } 9419 } 9420 9421 void dispose() { 9422 // note: this calls free(rawData) for us 9423 if(handle) { 9424 if (usingXshm) { 9425 XDisplayConnection.unregisterImage(this); 9426 if (XDisplayConnection.get()) XShmDetach(XDisplayConnection.get(), &shminfo); 9427 } 9428 XDestroyImage(handle); 9429 if(usingXshm) { 9430 shmdt(shminfo.shmaddr); 9431 shmctl(shminfo.shmid, IPC_RMID, null); 9432 } 9433 handle = null; 9434 } 9435 } 9436 9437 Color getPixel(int x, int y) { 9438 auto offset = (y * width + x) * 4; 9439 Color c; 9440 c.a = 255; 9441 c.b = rawData[offset + 0]; 9442 c.g = rawData[offset + 1]; 9443 c.r = rawData[offset + 2]; 9444 return c; 9445 } 9446 9447 void setPixel(int x, int y, Color c) { 9448 auto offset = (y * width + x) * 4; 9449 rawData[offset + 0] = c.b; 9450 rawData[offset + 1] = c.g; 9451 rawData[offset + 2] = c.r; 9452 } 9453 9454 void convertToRgbaBytes(ubyte[] where) { 9455 assert(where.length == this.width * this.height * 4); 9456 9457 // if rawData had a length.... 9458 //assert(rawData.length == where.length); 9459 for(int idx = 0; idx < where.length; idx += 4) { 9460 where[idx + 0] = rawData[idx + 2]; // r 9461 where[idx + 1] = rawData[idx + 1]; // g 9462 where[idx + 2] = rawData[idx + 0]; // b 9463 where[idx + 3] = 255; // a 9464 } 9465 } 9466 9467 void setFromRgbaBytes(in ubyte[] where) { 9468 assert(where.length == this.width * this.height * 4); 9469 9470 // if rawData had a length.... 9471 //assert(rawData.length == where.length); 9472 for(int idx = 0; idx < where.length; idx += 4) { 9473 rawData[idx + 2] = where[idx + 0]; // r 9474 rawData[idx + 1] = where[idx + 1]; // g 9475 rawData[idx + 0] = where[idx + 2]; // b 9476 //rawData[idx + 3] = 255; // a 9477 } 9478 } 9479 9480 } 9481 9482 mixin template NativeSimpleWindowImplementation() { 9483 GC gc; 9484 Window window; 9485 Display* display; 9486 9487 Pixmap buffer; 9488 int bufferw, bufferh; // size of the buffer; can be bigger than window 9489 XIC xic; // input context 9490 int curHidden = 0; // counter 9491 Cursor blankCurPtr = 0; 9492 int cursorSequenceNumber = 0; 9493 int warpEventCount = 0; // number of mouse movement events to eat 9494 9495 void delegate(XEvent) setSelectionHandler; 9496 void delegate(in char[]) getSelectionHandler; 9497 9498 version(without_opengl) {} else 9499 GLXContext glc; 9500 9501 private void fixFixedSize(bool forced=false) (int width, int height) { 9502 if (forced || this.resizability == Resizability.fixedSize) { 9503 //{ import core.stdc.stdio; printf("fixing size to: %dx%d\n", width, height); } 9504 XSizeHints sh; 9505 static if (!forced) { 9506 c_long spr; 9507 XGetWMNormalHints(display, window, &sh, &spr); 9508 sh.flags |= PMaxSize | PMinSize; 9509 } else { 9510 sh.flags = PMaxSize | PMinSize; 9511 } 9512 sh.min_width = width; 9513 sh.min_height = height; 9514 sh.max_width = width; 9515 sh.max_height = height; 9516 XSetWMNormalHints(display, window, &sh); 9517 //XFlush(display); 9518 } 9519 } 9520 9521 ScreenPainter getPainter() { 9522 return ScreenPainter(this, window); 9523 } 9524 9525 void move(int x, int y) { 9526 XMoveWindow(display, window, x, y); 9527 } 9528 9529 void resize(int w, int h) { 9530 if (w < 1) w = 1; 9531 if (h < 1) h = 1; 9532 XResizeWindow(display, window, w, h); 9533 // FIXME: do we need to set this as the opengl context to do the glViewport change? 9534 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 9535 } 9536 9537 void moveResize (int x, int y, int w, int h) { 9538 if (w < 1) w = 1; 9539 if (h < 1) h = 1; 9540 XMoveResizeWindow(display, window, x, y, w, h); 9541 version(without_opengl) {} else if (openglMode == OpenGlOptions.yes) glViewport(0, 0, w, h); 9542 } 9543 9544 void hideCursor () { 9545 if (curHidden++ == 0) { 9546 if (!blankCurPtr || cursorSequenceNumber != XDisplayConnection.connectionSequenceNumber) { 9547 static const(char)[1] cmbmp = 0; 9548 XColor blackcolor = { 0, 0, 0, 0, 0, 0 }; 9549 Pixmap pm = XCreateBitmapFromData(display, window, cmbmp.ptr, 1, 1); 9550 blankCurPtr = XCreatePixmapCursor(display, pm, pm, &blackcolor, &blackcolor, 0, 0); 9551 cursorSequenceNumber = XDisplayConnection.connectionSequenceNumber; 9552 XFreePixmap(display, pm); 9553 } 9554 XDefineCursor(display, window, blankCurPtr); 9555 } 9556 } 9557 9558 void showCursor () { 9559 if (--curHidden == 0) XUndefineCursor(display, window); 9560 } 9561 9562 void warpMouse (int x, int y) { 9563 // here i will send dummy "ignore next mouse motion" event, 9564 // 'cause `XWarpPointer()` sends synthesised mouse motion, 9565 // and we don't need to report it to the user (as warping is 9566 // used when the user needs movement deltas). 9567 //XClientMessageEvent xclient; 9568 XEvent e; 9569 e.xclient.type = EventType.ClientMessage; 9570 e.xclient.window = window; 9571 e.xclient.message_type = GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 9572 e.xclient.format = 32; 9573 e.xclient.data.l[0] = 0; 9574 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"INSMME\"...\n"); } 9575 //{ 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]); } 9576 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 9577 // now warp pointer... 9578 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: sending \"warp\"...\n"); } 9579 XWarpPointer(display, None, window, 0, 0, 0, 0, x, y); 9580 // ...and flush 9581 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: flushing...\n"); } 9582 XFlush(display); 9583 } 9584 9585 void sendDummyEvent () { 9586 // here i will send dummy event to ping event queue 9587 XEvent e; 9588 e.xclient.type = EventType.ClientMessage; 9589 e.xclient.window = window; 9590 e.xclient.message_type = GetAtom!("_X11SDPY_DUMMY_EVENT_", true)(display); // let's hope nobody else will use such stupid name ;-) 9591 e.xclient.format = 32; 9592 e.xclient.data.l[0] = 0; 9593 XSendEvent(display, window, false, EventMask.NoEventMask, /*cast(XEvent*)&xclient*/&e); 9594 XFlush(display); 9595 } 9596 9597 void setTitle(string title) { 9598 if (title.ptr is null) title = ""; 9599 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 9600 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 9601 XTextProperty windowName; 9602 windowName.value = title.ptr; 9603 windowName.encoding = XA_UTF8; //XA_STRING; 9604 windowName.format = 8; 9605 windowName.nitems = cast(uint)title.length; 9606 XSetWMName(display, window, &windowName); 9607 char[1024] namebuf = 0; 9608 auto maxlen = namebuf.length-1; 9609 if (maxlen > title.length) maxlen = title.length; 9610 namebuf[0..maxlen] = title[0..maxlen]; 9611 XStoreName(display, window, namebuf.ptr); 9612 XChangeProperty(display, window, XA_NETWM_NAME, XA_UTF8, 8, PropModeReplace, title.ptr, cast(uint)title.length); 9613 flushGui(); // without this OpenGL windows has a LONG delay before changing title 9614 } 9615 9616 string[] getTitles() { 9617 auto XA_UTF8 = XInternAtom(display, "UTF8_STRING".ptr, false); 9618 auto XA_NETWM_NAME = XInternAtom(display, "_NET_WM_NAME".ptr, false); 9619 XTextProperty textProp; 9620 if (XGetTextProperty(display, window, &textProp, XA_NETWM_NAME) != 0 || XGetWMName(display, window, &textProp) != 0) { 9621 if ((textProp.encoding == XA_UTF8 || textProp.encoding == XA_STRING) && textProp.format == 8) { 9622 return textProp.value[0 .. textProp.nitems].idup.split('\0'); 9623 } else 9624 return []; 9625 } else 9626 return null; 9627 } 9628 9629 string getTitle() { 9630 auto titles = getTitles(); 9631 return titles.length ? titles[0] : null; 9632 } 9633 9634 void setMinSize (int minwidth, int minheight) { 9635 import core.stdc.config : c_long; 9636 if (minwidth < 1) minwidth = 1; 9637 if (minheight < 1) minheight = 1; 9638 XSizeHints sh; 9639 c_long spr; 9640 XGetWMNormalHints(display, window, &sh, &spr); 9641 sh.min_width = minwidth; 9642 sh.min_height = minheight; 9643 sh.flags |= PMinSize; 9644 XSetWMNormalHints(display, window, &sh); 9645 flushGui(); 9646 } 9647 9648 void setMaxSize (int maxwidth, int maxheight) { 9649 import core.stdc.config : c_long; 9650 if (maxwidth < 1) maxwidth = 1; 9651 if (maxheight < 1) maxheight = 1; 9652 XSizeHints sh; 9653 c_long spr; 9654 XGetWMNormalHints(display, window, &sh, &spr); 9655 sh.max_width = maxwidth; 9656 sh.max_height = maxheight; 9657 sh.flags |= PMaxSize; 9658 XSetWMNormalHints(display, window, &sh); 9659 flushGui(); 9660 } 9661 9662 void setResizeGranularity (int granx, int grany) { 9663 import core.stdc.config : c_long; 9664 if (granx < 1) granx = 1; 9665 if (grany < 1) grany = 1; 9666 XSizeHints sh; 9667 c_long spr; 9668 XGetWMNormalHints(display, window, &sh, &spr); 9669 sh.width_inc = granx; 9670 sh.height_inc = grany; 9671 sh.flags |= PResizeInc; 9672 XSetWMNormalHints(display, window, &sh); 9673 flushGui(); 9674 } 9675 9676 void setOpacity (uint opacity) { 9677 if (opacity == uint.max) 9678 XDeleteProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false)); 9679 else 9680 XChangeProperty(display, window, XInternAtom(display, "_NET_WM_WINDOW_OPACITY".ptr, false), 9681 XA_CARDINAL, 32, PropModeReplace, &opacity, 1); 9682 } 9683 9684 void createWindow(int width, int height, string title, in OpenGlOptions opengl, SimpleWindow parent) { 9685 display = XDisplayConnection.get(); 9686 auto screen = DefaultScreen(display); 9687 9688 version(without_opengl) {} 9689 else { 9690 if(opengl == OpenGlOptions.yes) { 9691 GLXFBConfig fbconf = null; 9692 XVisualInfo* vi = null; 9693 bool useLegacy = false; 9694 static if (SdpyIsUsingIVGLBinds) {if (glbindGetProcAddress("glHint") is null) assert(0, "GL: error loading OpenGL"); } // loads all necessary functions 9695 if (sdpyOpenGLContextVersion != 0 && glXCreateContextAttribsARB_present()) { 9696 int[23] visualAttribs = [ 9697 GLX_X_RENDERABLE , 1/*True*/, 9698 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, 9699 GLX_RENDER_TYPE , GLX_RGBA_BIT, 9700 GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, 9701 GLX_RED_SIZE , 8, 9702 GLX_GREEN_SIZE , 8, 9703 GLX_BLUE_SIZE , 8, 9704 GLX_ALPHA_SIZE , 8, 9705 GLX_DEPTH_SIZE , 24, 9706 GLX_STENCIL_SIZE , 8, 9707 GLX_DOUBLEBUFFER , 1/*True*/, 9708 0/*None*/, 9709 ]; 9710 int fbcount; 9711 GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visualAttribs.ptr, &fbcount); 9712 if (fbcount == 0) { 9713 useLegacy = true; // try to do at least something 9714 } else { 9715 // pick the FB config/visual with the most samples per pixel 9716 int bestidx = -1, bestns = -1; 9717 foreach (int fbi; 0..fbcount) { 9718 int sb, samples; 9719 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLE_BUFFERS, &sb); 9720 glXGetFBConfigAttrib(display, fbc[fbi], GLX_SAMPLES, &samples); 9721 if (bestidx < 0 || sb && samples > bestns) { bestidx = fbi; bestns = samples; } 9722 } 9723 //{ import core.stdc.stdio; printf("found gl visual with %d samples\n", bestns); } 9724 fbconf = fbc[bestidx]; 9725 // Be sure to free the FBConfig list allocated by glXChooseFBConfig() 9726 XFree(fbc); 9727 vi = cast(XVisualInfo*)glXGetVisualFromFBConfig(display, fbconf); 9728 } 9729 } 9730 if (vi is null || useLegacy) { 9731 static immutable GLint[5] attrs = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 9732 vi = cast(XVisualInfo*)glXChooseVisual(display, 0, attrs.ptr); 9733 useLegacy = true; 9734 } 9735 if (vi is null) throw new Exception("no open gl visual found"); 9736 9737 XSetWindowAttributes swa; 9738 auto root = RootWindow(display, screen); 9739 swa.colormap = XCreateColormap(display, root, vi.visual, AllocNone); 9740 9741 window = XCreateWindow(display, parent is null ? root : parent.impl.window, 9742 0, 0, width, height, 9743 0, vi.depth, 1 /* InputOutput */, vi.visual, CWColormap, &swa); 9744 9745 // now try to use `glXCreateContextAttribsARB()` if it's here 9746 if (!useLegacy) { 9747 // request fairly advanced context, even with stencil buffer! 9748 int[9] contextAttribs = [ 9749 GLX_CONTEXT_MAJOR_VERSION_ARB, (sdpyOpenGLContextVersion>>8), 9750 GLX_CONTEXT_MINOR_VERSION_ARB, (sdpyOpenGLContextVersion&0xff), 9751 /*GLX_CONTEXT_PROFILE_MASK_ARB*/0x9126, (sdpyOpenGLContextCompatible ? /*GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB*/0x02 : /*GLX_CONTEXT_CORE_PROFILE_BIT_ARB*/ 0x01), 9752 // for modern context, set "forward compatibility" flag too 9753 (sdpyOpenGLContextCompatible ? None : /*GLX_CONTEXT_FLAGS_ARB*/ 0x2094), /*GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB*/ 0x02, 9754 0/*None*/, 9755 ]; 9756 glc = glXCreateContextAttribsARB(display, fbconf, null, 1/*True*/, contextAttribs.ptr); 9757 if (glc is null && sdpyOpenGLContextAllowFallback) { 9758 sdpyOpenGLContextVersion = 0; 9759 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 9760 } 9761 //{ import core.stdc.stdio; printf("using modern ogl v%d.%d\n", contextAttribs[1], contextAttribs[3]); } 9762 } else { 9763 // fallback to old GLX call 9764 if (sdpyOpenGLContextAllowFallback || sdpyOpenGLContextVersion == 0) { 9765 sdpyOpenGLContextVersion = 0; 9766 glc = glXCreateContext(display, vi, null, /*GL_TRUE*/1); 9767 } 9768 } 9769 // sync to ensure any errors generated are processed 9770 XSync(display, 0/*False*/); 9771 //{ import core.stdc.stdio; printf("ogl is here\n"); } 9772 if(glc is null) 9773 throw new Exception("glc"); 9774 } 9775 } 9776 9777 if(opengl == OpenGlOptions.no) { 9778 9779 bool overrideRedirect = false; 9780 if(windowType == WindowTypes.dropdownMenu || windowType == WindowTypes.popupMenu || windowType == WindowTypes.notification) 9781 overrideRedirect = true; 9782 9783 XSetWindowAttributes swa; 9784 swa.background_pixel = WhitePixel(display, screen); 9785 swa.border_pixel = BlackPixel(display, screen); 9786 swa.override_redirect = overrideRedirect; 9787 auto root = RootWindow(display, screen); 9788 swa.colormap = XCreateColormap(display, root, DefaultVisual(display, screen), AllocNone); 9789 9790 window = XCreateWindow(display, parent is null ? root : parent.impl.window, 9791 0, 0, width, height, 9792 0, CopyFromParent, 1 /* InputOutput */, cast(Visual*) CopyFromParent, CWColormap | CWBackPixel | CWBorderPixel | CWOverrideRedirect, &swa); 9793 9794 9795 9796 /* 9797 window = XCreateSimpleWindow( 9798 display, 9799 parent is null ? RootWindow(display, screen) : parent.impl.window, 9800 0, 0, // x, y 9801 width, height, 9802 1, // border width 9803 BlackPixel(display, screen), // border 9804 WhitePixel(display, screen)); // background 9805 */ 9806 9807 buffer = XCreatePixmap(display, cast(Drawable) window, width, height, DefaultDepthOfDisplay(display)); 9808 bufferw = width; 9809 bufferh = height; 9810 9811 gc = DefaultGC(display, screen); 9812 9813 // clear out the buffer to get us started... 9814 XSetForeground(display, gc, WhitePixel(display, screen)); 9815 XFillRectangle(display, cast(Drawable) buffer, gc, 0, 0, width, height); 9816 XSetForeground(display, gc, BlackPixel(display, screen)); 9817 } 9818 9819 // input context 9820 //TODO: create this only for top-level windows, and reuse that? 9821 if (XDisplayConnection.xim !is null) { 9822 xic = XCreateIC(XDisplayConnection.xim, 9823 /*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing, 9824 /*XNClientWindow*/"clientWindow".ptr, window, 9825 /*XNFocusWindow*/"focusWindow".ptr, window, 9826 null); 9827 if (xic is null) { 9828 import core.stdc.stdio : stderr, fprintf; 9829 fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window); 9830 } 9831 } 9832 9833 if (sdpyWindowClassStr is null) loadBinNameToWindowClassName(); 9834 if (sdpyWindowClassStr is null) sdpyWindowClass = "DSimpleWindow"; 9835 // window class 9836 if (sdpyWindowClassStr !is null && sdpyWindowClassStr[0]) { 9837 //{ import core.stdc.stdio; printf("winclass: [%s]\n", sdpyWindowClassStr); } 9838 XClassHint klass; 9839 XWMHints wh; 9840 XSizeHints size; 9841 klass.res_name = sdpyWindowClassStr; 9842 klass.res_class = sdpyWindowClassStr; 9843 XSetWMProperties(display, window, null, null, null, 0, &size, &wh, &klass); 9844 } 9845 9846 setTitle(title); 9847 SimpleWindow.nativeMapping[window] = this; 9848 CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; 9849 9850 // This gives our window a close button 9851 if (windowType != WindowTypes.eventOnly) { 9852 Atom atom = XInternAtom(display, "WM_DELETE_WINDOW".ptr, true); // FIXME: does this need to be freed? 9853 XSetWMProtocols(display, window, &atom, 1); 9854 } 9855 9856 // FIXME: windowType and customizationFlags 9857 Atom[8] wsatoms; // here, due to goto 9858 int wmsacount = 0; // here, due to goto 9859 9860 try 9861 final switch(windowType) { 9862 case WindowTypes.normal: 9863 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 9864 break; 9865 case WindowTypes.undecorated: 9866 motifHideDecorations(); 9867 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display)); 9868 break; 9869 case WindowTypes.eventOnly: 9870 _hidden = true; 9871 XSelectInput(display, window, EventMask.StructureNotifyMask); // without this, we won't get destroy notification 9872 goto hiddenWindow; 9873 //break; 9874 case WindowTypes.nestedChild: 9875 9876 break; 9877 9878 case WindowTypes.dropdownMenu: 9879 motifHideDecorations(); 9880 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"(display)); 9881 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 9882 break; 9883 case WindowTypes.popupMenu: 9884 motifHideDecorations(); 9885 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_POPUP_MENU"(display)); 9886 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 9887 break; 9888 case WindowTypes.notification: 9889 motifHideDecorations(); 9890 setNetWMWindowType(GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display)); 9891 customizationFlags |= WindowFlags.skipTaskbar | WindowFlags.alwaysOnTop; 9892 break; 9893 /+ 9894 case WindowTypes.menu: 9895 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 9896 motifHideDecorations(); 9897 break; 9898 case WindowTypes.desktop: 9899 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DESKTOP"(display); 9900 break; 9901 case WindowTypes.dock: 9902 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DOCK"(display); 9903 break; 9904 case WindowTypes.toolbar: 9905 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLBAR"(display); 9906 break; 9907 case WindowTypes.menu: 9908 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_MENU"(display); 9909 break; 9910 case WindowTypes.utility: 9911 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_UTILITY"(display); 9912 break; 9913 case WindowTypes.splash: 9914 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_SPLASH"(display); 9915 break; 9916 case WindowTypes.dialog: 9917 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DIALOG"(display); 9918 break; 9919 case WindowTypes.tooltip: 9920 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_TOOLTIP"(display); 9921 break; 9922 case WindowTypes.notification: 9923 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_NOTIFICATION"(display); 9924 break; 9925 case WindowTypes.combo: 9926 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_COMBO"(display); 9927 break; 9928 case WindowTypes.dnd: 9929 atoms[0] = GetAtom!"_NET_WM_WINDOW_TYPE_DND"(display); 9930 break; 9931 +/ 9932 } 9933 catch(Exception e) { 9934 // XInternAtom failed, prolly a WM 9935 // that doesn't support these things 9936 } 9937 9938 if (customizationFlags&WindowFlags.skipTaskbar) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(display); 9939 // the two following flags may be ignored by WM 9940 if (customizationFlags&WindowFlags.alwaysOnTop) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_ABOVE", true)(display); 9941 if (customizationFlags&WindowFlags.alwaysOnBottom) wsatoms[wmsacount++] = GetAtom!("_NET_WM_STATE_BELOW", true)(display); 9942 9943 if (wmsacount != 0) XChangeProperty(display, window, GetAtom!("_NET_WM_STATE", true)(display), XA_ATOM, 32 /* bits */,0 /*PropModeReplace*/, wsatoms.ptr, wmsacount); 9944 9945 if (this.resizability == Resizability.fixedSize || (opengl == OpenGlOptions.no && this.resizability != Resizability.allowResizing)) fixFixedSize!true(width, height); 9946 9947 // What would be ideal here is if they only were 9948 // selected if there was actually an event handler 9949 // for them... 9950 9951 selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false); 9952 9953 hiddenWindow: 9954 9955 // set the pid property for lookup later by window managers 9956 // a standard convenience 9957 import core.sys.posix.unistd; 9958 arch_ulong pid = getpid(); 9959 9960 XChangeProperty( 9961 display, 9962 impl.window, 9963 GetAtom!("_NET_WM_PID", true)(display), 9964 XA_CARDINAL, 9965 32 /* bits */, 9966 0 /*PropModeReplace*/, 9967 &pid, 9968 1); 9969 9970 9971 if(windowType != WindowTypes.eventOnly && (customizationFlags&WindowFlags.dontAutoShow) == 0) { 9972 XMapWindow(display, window); 9973 } else { 9974 _hidden = true; 9975 } 9976 } 9977 9978 void selectDefaultInput(bool forceIncludeMouseMotion) { 9979 auto mask = EventMask.ExposureMask | 9980 EventMask.KeyPressMask | 9981 EventMask.KeyReleaseMask | 9982 EventMask.PropertyChangeMask | 9983 EventMask.FocusChangeMask | 9984 EventMask.StructureNotifyMask | 9985 EventMask.VisibilityChangeMask 9986 | EventMask.ButtonPressMask 9987 | EventMask.ButtonReleaseMask 9988 ; 9989 9990 // xshm is our shortcut for local connections 9991 if(Image.impl.xshmAvailable || forceIncludeMouseMotion) 9992 mask |= EventMask.PointerMotionMask; 9993 9994 XSelectInput(display, window, mask); 9995 } 9996 9997 9998 void setNetWMWindowType(Atom type) { 9999 Atom[2] atoms; 10000 10001 atoms[0] = type; 10002 // generic fallback 10003 atoms[1] = GetAtom!"_NET_WM_WINDOW_TYPE_NORMAL"(display); 10004 10005 XChangeProperty( 10006 display, 10007 impl.window, 10008 GetAtom!"_NET_WM_WINDOW_TYPE"(display), 10009 XA_ATOM, 10010 32 /* bits */, 10011 0 /*PropModeReplace*/, 10012 atoms.ptr, 10013 cast(int) atoms.length); 10014 } 10015 10016 void motifHideDecorations() { 10017 MwmHints hints; 10018 hints.flags = MWM_HINTS_DECORATIONS; 10019 10020 XChangeProperty( 10021 display, 10022 impl.window, 10023 GetAtom!"_MOTIF_WM_HINTS"(display), 10024 GetAtom!"_MOTIF_WM_HINTS"(display), 10025 32 /* bits */, 10026 0 /*PropModeReplace*/, 10027 &hints, 10028 hints.sizeof / 4); 10029 } 10030 10031 /*k8: unused 10032 void createOpenGlContext() { 10033 10034 } 10035 */ 10036 10037 void closeWindow() { 10038 if (customEventFD != -1) { 10039 import core.sys.posix.unistd : close; 10040 close(customEventFD); 10041 customEventFD = -1; 10042 } 10043 if(buffer) 10044 XFreePixmap(display, buffer); 10045 bufferw = bufferh = 0; 10046 if (blankCurPtr && cursorSequenceNumber == XDisplayConnection.connectionSequenceNumber) XFreeCursor(display, blankCurPtr); 10047 XDestroyWindow(display, window); 10048 XFlush(display); 10049 } 10050 10051 void dispose() { 10052 } 10053 10054 bool destroyed = false; 10055 } 10056 10057 bool insideXEventLoop; 10058 } 10059 10060 version(X11) { 10061 10062 int mouseDoubleClickTimeout = 350; /// double click timeout. X only, you probably shouldn't change this. 10063 10064 /// Platform-specific, you might use it when doing a custom event loop 10065 bool doXNextEvent(Display* display) { 10066 bool done; 10067 XEvent e; 10068 XNextEvent(display, &e); 10069 version(sddddd) { 10070 import std.stdio, std.conv : to; 10071 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 10072 if(typeid(cast(Object) *win) == NotificationAreaIcon.classinfo) 10073 writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); 10074 } 10075 } 10076 10077 // filter out compose events 10078 if (XFilterEvent(&e, None)) { 10079 //{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); } 10080 //NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet) 10081 return false; 10082 } 10083 // process keyboard mapping changes 10084 if (e.type == EventType.KeymapNotify) { 10085 //{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); } 10086 XRefreshKeyboardMapping(&e.xmapping); 10087 return false; 10088 } 10089 10090 version(with_eventloop) 10091 import arsd.eventloop; 10092 10093 if(SimpleWindow.handleNativeGlobalEvent !is null) { 10094 // see windows impl's comments 10095 XUnlockDisplay(display); 10096 scope(exit) XLockDisplay(display); 10097 auto ret = SimpleWindow.handleNativeGlobalEvent(e); 10098 if(ret == 0) 10099 return done; 10100 } 10101 10102 10103 if(auto win = e.xany.window in CapableOfHandlingNativeEvent.nativeHandleMapping) { 10104 if(win.getNativeEventHandler !is null) { 10105 XUnlockDisplay(display); 10106 scope(exit) XLockDisplay(display); 10107 auto ret = win.getNativeEventHandler()(e); 10108 if(ret == 0) 10109 return done; 10110 } 10111 } 10112 10113 switch(e.type) { 10114 case EventType.SelectionClear: 10115 if(auto win = e.xselectionclear.window in SimpleWindow.nativeMapping) 10116 { /* FIXME??????? */ } 10117 break; 10118 case EventType.SelectionRequest: 10119 if(auto win = e.xselectionrequest.owner in SimpleWindow.nativeMapping) 10120 if(win.setSelectionHandler !is null) { 10121 XUnlockDisplay(display); 10122 scope(exit) XLockDisplay(display); 10123 win.setSelectionHandler(e); 10124 } 10125 break; 10126 case EventType.PropertyNotify: 10127 10128 break; 10129 case EventType.SelectionNotify: 10130 if(auto win = e.xselection.requestor in SimpleWindow.nativeMapping) 10131 if(win.getSelectionHandler !is null) { 10132 // FIXME: maybe we should call a different handler for PRIMARY vs CLIPBOARD 10133 if(e.xselection.property == None) { // || e.xselection.property == GetAtom!("NULL", true)(e.xselection.display)) { 10134 XUnlockDisplay(display); 10135 scope(exit) XLockDisplay(display); 10136 win.getSelectionHandler(null); 10137 } else { 10138 Atom target; 10139 int format; 10140 arch_ulong bytesafter, length; 10141 void* value; 10142 XGetWindowProperty( 10143 e.xselection.display, 10144 e.xselection.requestor, 10145 e.xselection.property, 10146 0, 10147 100000 /* length */, 10148 //false, /* don't erase it */ 10149 true, /* do erase it lol */ 10150 0 /*AnyPropertyType*/, 10151 &target, &format, &length, &bytesafter, &value); 10152 10153 // FIXME: I don't have to copy it now since it is in char[] instead of string 10154 10155 { 10156 XUnlockDisplay(display); 10157 scope(exit) XLockDisplay(display); 10158 10159 if(target == XA_ATOM) { 10160 // initial request, see what they are able to work with and request the best one 10161 // we can handle, if available 10162 10163 Atom[] answer = (cast(Atom*) value)[0 .. length]; 10164 Atom best = None; 10165 foreach(option; answer) { 10166 if(option == GetAtom!"UTF8_STRING"(display)) { 10167 best = option; 10168 break; 10169 } else if(option == XA_STRING) { 10170 best = option; 10171 } 10172 } 10173 10174 //writeln("got ", answer); 10175 10176 if(best != None) { 10177 // actually request the best format 10178 XConvertSelection(e.xselection.display, e.xselection.selection, best, GetAtom!("SDD_DATA", true)(display), e.xselection.requestor, 0 /*CurrentTime*/); 10179 } 10180 } else if(target == GetAtom!"UTF8_STRING"(display) || target == XA_STRING) { 10181 win.getSelectionHandler((cast(char[]) value[0 .. length]).idup); 10182 } else if(target == GetAtom!"INCR"(display)) { 10183 // incremental 10184 10185 //sdpyGettingPaste = true; // FIXME: should prolly be separate for the different selections 10186 10187 // FIXME: handle other events while it goes so app doesn't lock up with big pastes 10188 // can also be optimized if it chunks to the api somehow 10189 10190 char[] s; 10191 10192 do { 10193 10194 XEvent subevent; 10195 do { 10196 XMaskEvent(display, EventMask.PropertyChangeMask, &subevent); 10197 } while(subevent.type != EventType.PropertyNotify || subevent.xproperty.atom != e.xselection.property || subevent.xproperty.state != PropertyNotification.PropertyNewValue); 10198 10199 void* subvalue; 10200 XGetWindowProperty( 10201 e.xselection.display, 10202 e.xselection.requestor, 10203 e.xselection.property, 10204 0, 10205 100000 /* length */, 10206 true, /* erase it to signal we got it and want more */ 10207 0 /*AnyPropertyType*/, 10208 &target, &format, &length, &bytesafter, &subvalue); 10209 10210 s ~= (cast(char*) subvalue)[0 .. length]; 10211 10212 XFree(subvalue); 10213 } while(length > 0); 10214 10215 win.getSelectionHandler(s); 10216 } else { 10217 // unsupported type 10218 } 10219 } 10220 XFree(value); 10221 /* 10222 XDeleteProperty( 10223 e.xselection.display, 10224 e.xselection.requestor, 10225 e.xselection.property); 10226 */ 10227 } 10228 } 10229 break; 10230 case EventType.ConfigureNotify: 10231 auto event = e.xconfigure; 10232 if(auto win = event.window in SimpleWindow.nativeMapping) { 10233 //version(sdddd) { import std.stdio; writeln(" w=", event.width, "; h=", event.height); } 10234 if(event.width != win.width || event.height != win.height) { 10235 win._width = event.width; 10236 win._height = event.height; 10237 10238 if(win.openglMode == OpenGlOptions.no) { 10239 // FIXME: could this be more efficient? 10240 10241 if (win.bufferw < event.width || win.bufferh < event.height) { 10242 //{ import core.stdc.stdio; printf("new buffer; old size: %dx%d; new size: %dx%d\n", win.bufferw, win.bufferh, cast(int)event.width, cast(int)event.height); } 10243 // grow the internal buffer to match the window... 10244 auto newPixmap = XCreatePixmap(display, cast(Drawable) event.window, event.width, event.height, DefaultDepthOfDisplay(display)); 10245 { 10246 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 10247 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 10248 scope(exit) XFreeGC(win.display, xgc); 10249 XSetClipMask(win.display, xgc, None); 10250 XSetForeground(win.display, xgc, 0); 10251 XFillRectangle(display, cast(Drawable)newPixmap, xgc, 0, 0, event.width, event.height); 10252 } 10253 XCopyArea(display, 10254 cast(Drawable) (*win).buffer, 10255 cast(Drawable) newPixmap, 10256 (*win).gc, 0, 0, 10257 win.bufferw < event.width ? win.bufferw : win.width, 10258 win.bufferh < event.height ? win.bufferh : win.height, 10259 0, 0); 10260 10261 XFreePixmap(display, win.buffer); 10262 win.buffer = newPixmap; 10263 win.bufferw = event.width; 10264 win.bufferh = event.height; 10265 } 10266 10267 // clear unused parts of the buffer 10268 if (win.bufferw > event.width || win.bufferh > event.height) { 10269 GC xgc = XCreateGC(win.display, cast(Drawable)win.window, 0, null); 10270 XCopyGC(win.display, win.gc, 0xffffffff, xgc); 10271 scope(exit) XFreeGC(win.display, xgc); 10272 XSetClipMask(win.display, xgc, None); 10273 XSetForeground(win.display, xgc, 0); 10274 immutable int maxw = (win.bufferw > event.width ? win.bufferw : event.width); 10275 immutable int maxh = (win.bufferh > event.height ? win.bufferh : event.height); 10276 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, event.width, 0, maxw, maxh); // let X11 do clipping 10277 XFillRectangle(win.display, cast(Drawable)win.buffer, xgc, 0, event.height, maxw, maxh); // let X11 do clipping 10278 } 10279 10280 } 10281 10282 version(without_opengl) {} else 10283 if(win.openglMode == OpenGlOptions.yes && win.resizability == Resizability.automaticallyScaleIfPossible) { 10284 glViewport(0, 0, event.width, event.height); 10285 } 10286 10287 win.fixFixedSize(event.width, event.height); //k8: this does nothing on my FluxBox; wtf?! 10288 10289 if(win.windowResized !is null) { 10290 XUnlockDisplay(display); 10291 scope(exit) XLockDisplay(display); 10292 win.windowResized(event.width, event.height); 10293 } 10294 } 10295 } 10296 break; 10297 case EventType.Expose: 10298 if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { 10299 // if it is closing from a popup menu, it can get 10300 // an Expose event right by the end and trigger a 10301 // BadDrawable error ... we'll just check 10302 // closed to handle that. 10303 if((*win).closed) break; 10304 if((*win).openglMode == OpenGlOptions.no) { 10305 bool doCopy = true; 10306 if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); 10307 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); 10308 } else { 10309 // need to redraw the scene somehow 10310 XUnlockDisplay(display); 10311 scope(exit) XLockDisplay(display); 10312 version(without_opengl) {} else 10313 win.redrawOpenGlSceneNow(); 10314 } 10315 } 10316 break; 10317 case EventType.FocusIn: 10318 case EventType.FocusOut: 10319 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 10320 if (win.xic !is null) { 10321 //{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); } 10322 if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic); 10323 } 10324 10325 win._focused = e.type == EventType.FocusIn; 10326 10327 if(win.demandingAttention) 10328 demandAttention(*win, false); 10329 10330 if(win.onFocusChange) { 10331 XUnlockDisplay(display); 10332 scope(exit) XLockDisplay(display); 10333 win.onFocusChange(e.type == EventType.FocusIn); 10334 } 10335 } 10336 break; 10337 case EventType.VisibilityNotify: 10338 if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { 10339 if (e.xvisibility.state == VisibilityNotify.VisibilityFullyObscured) { 10340 if (win.visibilityChanged !is null) { 10341 XUnlockDisplay(display); 10342 scope(exit) XLockDisplay(display); 10343 win.visibilityChanged(false); 10344 } 10345 } else { 10346 if (win.visibilityChanged !is null) { 10347 XUnlockDisplay(display); 10348 scope(exit) XLockDisplay(display); 10349 win.visibilityChanged(true); 10350 } 10351 } 10352 } 10353 break; 10354 case EventType.ClientMessage: 10355 if (e.xclient.message_type == GetAtom!("_X11SDPY_INSMME_FLAG_EVENT_", true)(e.xany.display)) { 10356 // "ignore next mouse motion" event, increment ignore counter for teh window 10357 if (auto win = e.xclient.window in SimpleWindow.nativeMapping) { 10358 ++(*win).warpEventCount; 10359 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" message, new count=%d\n", (*win).warpEventCount); } 10360 } else { 10361 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"INSMME\" WTF?!!\n"); } 10362 } 10363 } else if(e.xclient.data.l[0] == GetAtom!"WM_DELETE_WINDOW"(e.xany.display)) { 10364 // user clicked the close button on the window manager 10365 if(auto win = e.xclient.window in SimpleWindow.nativeMapping) { 10366 XUnlockDisplay(display); 10367 scope(exit) XLockDisplay(display); 10368 if ((*win).closeQuery !is null) (*win).closeQuery(); else (*win).close(); 10369 } 10370 } else if(e.xclient.message_type == GetAtom!"MANAGER"(e.xany.display)) { 10371 foreach(nai; NotificationAreaIcon.activeIcons) 10372 nai.newManager(); 10373 } 10374 break; 10375 case EventType.MapNotify: 10376 if(auto win = e.xmap.window in SimpleWindow.nativeMapping) { 10377 (*win)._visible = true; 10378 if (!(*win)._visibleForTheFirstTimeCalled) { 10379 (*win)._visibleForTheFirstTimeCalled = true; 10380 if ((*win).visibleForTheFirstTime !is null) { 10381 XUnlockDisplay(display); 10382 scope(exit) XLockDisplay(display); 10383 version(without_opengl) {} else { 10384 if((*win).openglMode == OpenGlOptions.yes) { 10385 (*win).setAsCurrentOpenGlContextNT(); 10386 glViewport(0, 0, (*win).width, (*win).height); 10387 } 10388 } 10389 (*win).visibleForTheFirstTime(); 10390 } 10391 } 10392 if ((*win).visibilityChanged !is null) { 10393 XUnlockDisplay(display); 10394 scope(exit) XLockDisplay(display); 10395 (*win).visibilityChanged(true); 10396 } 10397 } 10398 break; 10399 case EventType.UnmapNotify: 10400 if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { 10401 win._visible = false; 10402 if (win.visibilityChanged !is null) { 10403 XUnlockDisplay(display); 10404 scope(exit) XLockDisplay(display); 10405 win.visibilityChanged(false); 10406 } 10407 } 10408 break; 10409 case EventType.DestroyNotify: 10410 if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { 10411 if (win.onDestroyed !is null) try { win.onDestroyed(); } catch (Exception e) {} // sorry 10412 win._closed = true; // just in case 10413 win.destroyed = true; 10414 if (win.xic !is null) { 10415 XDestroyIC(win.xic); 10416 win.xic = null; // just in calse 10417 } 10418 SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); 10419 bool anyImportant = false; 10420 foreach(SimpleWindow w; SimpleWindow.nativeMapping) 10421 if(w.beingOpenKeepsAppOpen) { 10422 anyImportant = true; 10423 break; 10424 } 10425 if(!anyImportant) 10426 done = true; 10427 } 10428 auto window = e.xdestroywindow.window; 10429 if(window in CapableOfHandlingNativeEvent.nativeHandleMapping) 10430 CapableOfHandlingNativeEvent.nativeHandleMapping.remove(window); 10431 10432 version(with_eventloop) { 10433 if(done) exit(); 10434 } 10435 break; 10436 10437 case EventType.MotionNotify: 10438 MouseEvent mouse; 10439 auto event = e.xmotion; 10440 10441 mouse.type = MouseEventType.motion; 10442 mouse.x = event.x; 10443 mouse.y = event.y; 10444 mouse.modifierState = event.state; 10445 10446 if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { 10447 mouse.window = *win; 10448 if (win.warpEventCount > 0) { 10449 debug(x11sdpy_warp_debug) { import core.stdc.stdio : printf; printf("X11: got \"warp motion\" message, current count=%d\n", (*win).warpEventCount); } 10450 --(*win).warpEventCount; 10451 (*win).mdx(mouse); // so deltas will be correctly updated 10452 } else { 10453 win.warpEventCount = 0; // just in case 10454 (*win).mdx(mouse); 10455 if((*win).handleMouseEvent) { 10456 XUnlockDisplay(display); 10457 scope(exit) XLockDisplay(display); 10458 (*win).handleMouseEvent(mouse); 10459 } 10460 } 10461 } 10462 10463 version(with_eventloop) 10464 send(mouse); 10465 break; 10466 case EventType.ButtonPress: 10467 case EventType.ButtonRelease: 10468 MouseEvent mouse; 10469 auto event = e.xbutton; 10470 10471 mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2); 10472 mouse.x = event.x; 10473 mouse.y = event.y; 10474 10475 static Time lastMouseDownTime = 0; 10476 10477 mouse.doubleClick = e.type == EventType.ButtonPress && (event.time - lastMouseDownTime) < mouseDoubleClickTimeout; 10478 if(e.type == EventType.ButtonPress) lastMouseDownTime = event.time; 10479 10480 switch(event.button) { 10481 case 1: mouse.button = MouseButton.left; break; // left 10482 case 2: mouse.button = MouseButton.middle; break; // middle 10483 case 3: mouse.button = MouseButton.right; break; // right 10484 case 4: mouse.button = MouseButton.wheelUp; break; // scroll up 10485 case 5: mouse.button = MouseButton.wheelDown; break; // scroll down 10486 case 6: break; // idk 10487 case 7: break; // idk 10488 case 8: mouse.button = MouseButton.backButton; break; 10489 case 9: mouse.button = MouseButton.forwardButton; break; 10490 default: 10491 } 10492 10493 // FIXME: double check this 10494 mouse.modifierState = event.state; 10495 10496 //mouse.modifierState = event.detail; 10497 10498 if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { 10499 mouse.window = *win; 10500 (*win).mdx(mouse); 10501 if((*win).handleMouseEvent) { 10502 XUnlockDisplay(display); 10503 scope(exit) XLockDisplay(display); 10504 (*win).handleMouseEvent(mouse); 10505 } 10506 } 10507 version(with_eventloop) 10508 send(mouse); 10509 break; 10510 10511 case EventType.KeyPress: 10512 case EventType.KeyRelease: 10513 //if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); } 10514 KeyEvent ke; 10515 ke.pressed = e.type == EventType.KeyPress; 10516 ke.hardwareCode = cast(ubyte) e.xkey.keycode; 10517 10518 auto sym = XKeycodeToKeysym( 10519 XDisplayConnection.get(), 10520 e.xkey.keycode, 10521 0); 10522 10523 ke.key = cast(Key) sym;//e.xkey.keycode; 10524 10525 ke.modifierState = e.xkey.state; 10526 10527 // import std.stdio; writefln("%x", sym); 10528 wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars! 10529 int charbuflen = 0; // return value of XwcLookupString 10530 if (ke.pressed) { 10531 auto win = e.xkey.window in SimpleWindow.nativeMapping; 10532 if (win !is null && win.xic !is null) { 10533 //{ import core.stdc.stdio : printf; printf("using xic!\n"); } 10534 Status status; 10535 charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status); 10536 //{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); } 10537 } else { 10538 //{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); } 10539 // If XIM initialization failed, don't process intl chars. Sorry, boys and girls. 10540 char[16] buffer; 10541 auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); 10542 if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0]; 10543 } 10544 } 10545 10546 // if there's no char, subst one 10547 if (charbuflen == 0) { 10548 switch (sym) { 10549 case 0xff09: charbuf[charbuflen++] = '\t'; break; 10550 case 0xff8d: // keypad enter 10551 case 0xff0d: charbuf[charbuflen++] = '\n'; break; 10552 default : // ignore 10553 } 10554 } 10555 10556 if (auto win = e.xkey.window in SimpleWindow.nativeMapping) { 10557 ke.window = *win; 10558 if (win.handleKeyEvent) { 10559 XUnlockDisplay(display); 10560 scope(exit) XLockDisplay(display); 10561 win.handleKeyEvent(ke); 10562 } 10563 10564 // char events are separate since they are on Windows too 10565 // also, xcompose can generate long char sequences 10566 // don't send char events if Meta and/or Hyper is pressed 10567 // TODO: ctrl+char should only send control chars; not yet 10568 if ((e.xkey.state&ModifierState.ctrl) != 0) { 10569 if (charbuflen > 1 || charbuf[0] >= ' ') charbuflen = 0; 10570 } 10571 if (ke.pressed && charbuflen > 0 && (e.xkey.state&(ModifierState.alt|ModifierState.windows)) == 0) { 10572 // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. 10573 foreach (immutable dchar ch; charbuf[0..charbuflen]) { 10574 if (win.handleCharEvent) { 10575 XUnlockDisplay(display); 10576 scope(exit) XLockDisplay(display); 10577 win.handleCharEvent(ch); 10578 } 10579 } 10580 } 10581 } 10582 10583 version(with_eventloop) 10584 send(ke); 10585 break; 10586 default: 10587 } 10588 10589 return done; 10590 } 10591 } 10592 10593 /* *************************************** */ 10594 /* Done with simpledisplay stuff */ 10595 /* *************************************** */ 10596 10597 // Necessary C library bindings follow 10598 version(Windows) {} else 10599 version(X11) { 10600 10601 extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc; 10602 10603 // X11 bindings needed here 10604 /* 10605 A little of this is from the bindings project on 10606 D Source and some of it is copy/paste from the C 10607 header. 10608 10609 The DSource listing consistently used D's long 10610 where C used long. That's wrong - C long is 32 bit, so 10611 it should be int in D. I changed that here. 10612 10613 Note: 10614 This isn't complete, just took what I needed for myself. 10615 */ 10616 10617 pragma(lib, "X11"); 10618 pragma(lib, "Xext"); 10619 import core.stdc.stddef : wchar_t; 10620 10621 extern(C) nothrow @nogc { 10622 10623 Cursor XCreateFontCursor(Display*, uint shape); 10624 int XDefineCursor(Display* display, Window w, Cursor cursor); 10625 int XUndefineCursor(Display* display, Window w); 10626 10627 Pixmap XCreateBitmapFromData(Display* display, Drawable d, const(char)* data, uint width, uint height); 10628 Cursor XCreatePixmapCursor(Display* display, Pixmap source, Pixmap mask, XColor* foreground_color, XColor* background_color, uint x, uint y); 10629 int XFreeCursor(Display* display, Cursor cursor); 10630 10631 int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); 10632 10633 int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return); 10634 10635 char *XKeysymToString(KeySym keysym); 10636 KeySym XKeycodeToKeysym( 10637 Display* /* display */, 10638 KeyCode /* keycode */, 10639 int /* index */ 10640 ); 10641 10642 10643 int XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); 10644 10645 int XFree(void*); 10646 int XDeleteProperty(Display *display, Window w, Atom property); 10647 10648 int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, in void *data, int nelements); 10649 10650 int XGetWindowProperty(Display *display, Window w, Atom property, arch_long 10651 long_offset, arch_long long_length, Bool del, Atom req_type, Atom 10652 *actual_type_return, int *actual_format_return, arch_ulong 10653 *nitems_return, arch_ulong *bytes_after_return, void** prop_return); 10654 Atom* XListProperties(Display *display, Window w, int *num_prop_return); 10655 Status XGetTextProperty(Display *display, Window w, XTextProperty *text_prop_return, Atom property); 10656 Status XQueryTree(Display *display, Window w, Window *root_return, Window *parent_return, Window **children_return, uint *nchildren_return); 10657 10658 int XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time); 10659 10660 Window XGetSelectionOwner(Display *display, Atom selection); 10661 10662 struct XVisualInfo { 10663 Visual* visual; 10664 VisualID visualid; 10665 int screen; 10666 uint depth; 10667 int c_class; 10668 c_ulong red_mask; 10669 c_ulong green_mask; 10670 c_ulong blue_mask; 10671 int colormap_size; 10672 int bits_per_rgb; 10673 } 10674 10675 enum VisualNoMask= 0x0; 10676 enum VisualIDMask= 0x1; 10677 enum VisualScreenMask=0x2; 10678 enum VisualDepthMask= 0x4; 10679 enum VisualClassMask= 0x8; 10680 enum VisualRedMaskMask=0x10; 10681 enum VisualGreenMaskMask=0x20; 10682 enum VisualBlueMaskMask=0x40; 10683 enum VisualColormapSizeMask=0x80; 10684 enum VisualBitsPerRGBMask=0x100; 10685 enum VisualAllMask= 0x1FF; 10686 10687 XVisualInfo* XGetVisualInfo(Display*, c_long, XVisualInfo*, int*); 10688 10689 10690 10691 Display* XOpenDisplay(const char*); 10692 int XCloseDisplay(Display*); 10693 10694 Bool XQueryExtension(Display*, const char*, int*, int*, int*); 10695 10696 // XIM and other crap 10697 struct _XOM {} 10698 struct _XIM {} 10699 struct _XIC {} 10700 alias XOM = _XOM*; 10701 alias XIM = _XIM*; 10702 alias XIC = _XIC*; 10703 Bool XSupportsLocale(); 10704 char* XSetLocaleModifiers(const(char)* modifier_list); 10705 XOM XOpenOM(Display* display, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 10706 Status XCloseOM(XOM om); 10707 10708 XIM XOpenIM(Display* dpy, _XrmHashBucketRec* rdb, const(char)* res_name, const(char)* res_class); 10709 Status XCloseIM(XIM im); 10710 10711 char* XGetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 10712 char* XSetIMValues(XIM im, ...) /*_X_SENTINEL(0)*/; 10713 Display* XDisplayOfIM(XIM im); 10714 char* XLocaleOfIM(XIM im); 10715 XIC XCreateIC(XIM im, ...) /*_X_SENTINEL(0)*/; 10716 void XDestroyIC(XIC ic); 10717 void XSetICFocus(XIC ic); 10718 void XUnsetICFocus(XIC ic); 10719 //wchar_t* XwcResetIC(XIC ic); 10720 char* XmbResetIC(XIC ic); 10721 char* Xutf8ResetIC(XIC ic); 10722 char* XSetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 10723 char* XGetICValues(XIC ic, ...) /*_X_SENTINEL(0)*/; 10724 XIM XIMOfIC(XIC ic); 10725 10726 alias XIMStyle = arch_ulong; 10727 enum : arch_ulong { 10728 XIMPreeditArea = 0x0001, 10729 XIMPreeditCallbacks = 0x0002, 10730 XIMPreeditPosition = 0x0004, 10731 XIMPreeditNothing = 0x0008, 10732 XIMPreeditNone = 0x0010, 10733 XIMStatusArea = 0x0100, 10734 XIMStatusCallbacks = 0x0200, 10735 XIMStatusNothing = 0x0400, 10736 XIMStatusNone = 0x0800, 10737 } 10738 10739 10740 /* X Shared Memory Extension functions */ 10741 //pragma(lib, "Xshm"); 10742 alias arch_ulong ShmSeg; 10743 struct XShmSegmentInfo { 10744 ShmSeg shmseg; 10745 int shmid; 10746 ubyte* shmaddr; 10747 Bool readOnly; 10748 } 10749 Status XShmAttach(Display*, XShmSegmentInfo*); 10750 Status XShmDetach(Display*, XShmSegmentInfo*); 10751 Status XShmPutImage( 10752 Display* /* dpy */, 10753 Drawable /* d */, 10754 GC /* gc */, 10755 XImage* /* image */, 10756 int /* src_x */, 10757 int /* src_y */, 10758 int /* dst_x */, 10759 int /* dst_y */, 10760 uint /* src_width */, 10761 uint /* src_height */, 10762 Bool /* send_event */ 10763 ); 10764 10765 Status XShmQueryExtension(Display*); 10766 10767 XImage *XShmCreateImage( 10768 Display* /* dpy */, 10769 Visual* /* visual */, 10770 uint /* depth */, 10771 int /* format */, 10772 char* /* data */, 10773 XShmSegmentInfo* /* shminfo */, 10774 uint /* width */, 10775 uint /* height */ 10776 ); 10777 10778 Pixmap XShmCreatePixmap( 10779 Display* /* dpy */, 10780 Drawable /* d */, 10781 char* /* data */, 10782 XShmSegmentInfo* /* shminfo */, 10783 uint /* width */, 10784 uint /* height */, 10785 uint /* depth */ 10786 ); 10787 10788 // and the necessary OS functions 10789 int shmget(int, size_t, int); 10790 void* shmat(int, in void*, int); 10791 int shmdt(in void*); 10792 int shmctl (int shmid, int cmd, void* ptr /*struct shmid_ds *buf*/); 10793 10794 enum IPC_PRIVATE = 0; 10795 enum IPC_CREAT = 512; 10796 enum IPC_RMID = 0; 10797 10798 /* MIT-SHM end */ 10799 10800 uint XSendEvent(Display* display, Window w, Bool propagate, arch_long event_mask, XEvent* event_send); 10801 10802 10803 enum MappingType:int { 10804 MappingModifier =0, 10805 MappingKeyboard =1, 10806 MappingPointer =2 10807 } 10808 10809 /* ImageFormat -- PutImage, GetImage */ 10810 enum ImageFormat:int { 10811 XYBitmap =0, /* depth 1, XYFormat */ 10812 XYPixmap =1, /* depth == drawable depth */ 10813 ZPixmap =2 /* depth == drawable depth */ 10814 } 10815 10816 enum ModifierName:int { 10817 ShiftMapIndex =0, 10818 LockMapIndex =1, 10819 ControlMapIndex =2, 10820 Mod1MapIndex =3, 10821 Mod2MapIndex =4, 10822 Mod3MapIndex =5, 10823 Mod4MapIndex =6, 10824 Mod5MapIndex =7 10825 } 10826 10827 enum ButtonMask:int { 10828 Button1Mask =1<<8, 10829 Button2Mask =1<<9, 10830 Button3Mask =1<<10, 10831 Button4Mask =1<<11, 10832 Button5Mask =1<<12, 10833 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 10834 } 10835 10836 enum KeyOrButtonMask:uint { 10837 ShiftMask =1<<0, 10838 LockMask =1<<1, 10839 ControlMask =1<<2, 10840 Mod1Mask =1<<3, 10841 Mod2Mask =1<<4, 10842 Mod3Mask =1<<5, 10843 Mod4Mask =1<<6, 10844 Mod5Mask =1<<7, 10845 Button1Mask =1<<8, 10846 Button2Mask =1<<9, 10847 Button3Mask =1<<10, 10848 Button4Mask =1<<11, 10849 Button5Mask =1<<12, 10850 AnyModifier =1<<15/* used in GrabButton, GrabKey */ 10851 } 10852 10853 enum ButtonName:int { 10854 Button1 =1, 10855 Button2 =2, 10856 Button3 =3, 10857 Button4 =4, 10858 Button5 =5 10859 } 10860 10861 /* Notify modes */ 10862 enum NotifyModes:int 10863 { 10864 NotifyNormal =0, 10865 NotifyGrab =1, 10866 NotifyUngrab =2, 10867 NotifyWhileGrabbed =3 10868 } 10869 const int NotifyHint =1; /* for MotionNotify events */ 10870 10871 /* Notify detail */ 10872 enum NotifyDetail:int 10873 { 10874 NotifyAncestor =0, 10875 NotifyVirtual =1, 10876 NotifyInferior =2, 10877 NotifyNonlinear =3, 10878 NotifyNonlinearVirtual =4, 10879 NotifyPointer =5, 10880 NotifyPointerRoot =6, 10881 NotifyDetailNone =7 10882 } 10883 10884 /* Visibility notify */ 10885 10886 enum VisibilityNotify:int 10887 { 10888 VisibilityUnobscured =0, 10889 VisibilityPartiallyObscured =1, 10890 VisibilityFullyObscured =2 10891 } 10892 10893 10894 enum WindowStackingMethod:int 10895 { 10896 Above =0, 10897 Below =1, 10898 TopIf =2, 10899 BottomIf =3, 10900 Opposite =4 10901 } 10902 10903 /* Circulation request */ 10904 enum CirculationRequest:int 10905 { 10906 PlaceOnTop =0, 10907 PlaceOnBottom =1 10908 } 10909 10910 enum PropertyNotification:int 10911 { 10912 PropertyNewValue =0, 10913 PropertyDelete =1 10914 } 10915 10916 enum ColorMapNotification:int 10917 { 10918 ColormapUninstalled =0, 10919 ColormapInstalled =1 10920 } 10921 10922 10923 struct _XPrivate {} 10924 struct _XrmHashBucketRec {} 10925 10926 alias void* XPointer; 10927 alias void* XExtData; 10928 10929 version( X86_64 ) { 10930 alias ulong XID; 10931 alias ulong arch_ulong; 10932 alias long arch_long; 10933 } else { 10934 alias uint XID; 10935 alias uint arch_ulong; 10936 alias int arch_long; 10937 } 10938 10939 alias XID Window; 10940 alias XID Drawable; 10941 alias XID Pixmap; 10942 10943 alias arch_ulong Atom; 10944 alias int Bool; 10945 alias Display XDisplay; 10946 10947 alias int ByteOrder; 10948 alias arch_ulong Time; 10949 alias void ScreenFormat; 10950 10951 struct XImage { 10952 int width, height; /* size of image */ 10953 int xoffset; /* number of pixels offset in X direction */ 10954 ImageFormat format; /* XYBitmap, XYPixmap, ZPixmap */ 10955 void *data; /* pointer to image data */ 10956 ByteOrder byte_order; /* data byte order, LSBFirst, MSBFirst */ 10957 int bitmap_unit; /* quant. of scanline 8, 16, 32 */ 10958 int bitmap_bit_order; /* LSBFirst, MSBFirst */ 10959 int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ 10960 int depth; /* depth of image */ 10961 int bytes_per_line; /* accelarator to next line */ 10962 int bits_per_pixel; /* bits per pixel (ZPixmap) */ 10963 arch_ulong red_mask; /* bits in z arrangment */ 10964 arch_ulong green_mask; 10965 arch_ulong blue_mask; 10966 XPointer obdata; /* hook for the object routines to hang on */ 10967 static struct F { /* image manipulation routines */ 10968 XImage* function( 10969 XDisplay* /* display */, 10970 Visual* /* visual */, 10971 uint /* depth */, 10972 int /* format */, 10973 int /* offset */, 10974 ubyte* /* data */, 10975 uint /* width */, 10976 uint /* height */, 10977 int /* bitmap_pad */, 10978 int /* bytes_per_line */) create_image; 10979 int function(XImage *) destroy_image; 10980 arch_ulong function(XImage *, int, int) get_pixel; 10981 int function(XImage *, int, int, arch_ulong) put_pixel; 10982 XImage* function(XImage *, int, int, uint, uint) sub_image; 10983 int function(XImage *, arch_long) add_pixel; 10984 } 10985 F f; 10986 } 10987 version(X86_64) static assert(XImage.sizeof == 136); 10988 else version(X86) static assert(XImage.sizeof == 88); 10989 10990 struct XCharStruct { 10991 short lbearing; /* origin to left edge of raster */ 10992 short rbearing; /* origin to right edge of raster */ 10993 short width; /* advance to next char's origin */ 10994 short ascent; /* baseline to top edge of raster */ 10995 short descent; /* baseline to bottom edge of raster */ 10996 ushort attributes; /* per char flags (not predefined) */ 10997 } 10998 10999 /* 11000 * To allow arbitrary information with fonts, there are additional properties 11001 * returned. 11002 */ 11003 struct XFontProp { 11004 Atom name; 11005 arch_ulong card32; 11006 } 11007 11008 alias Atom Font; 11009 11010 struct XFontStruct { 11011 XExtData *ext_data; /* Hook for extension to hang data */ 11012 Font fid; /* Font ID for this font */ 11013 uint direction; /* Direction the font is painted */ 11014 uint min_char_or_byte2; /* First character */ 11015 uint max_char_or_byte2; /* Last character */ 11016 uint min_byte1; /* First row that exists (for two-byte fonts) */ 11017 uint max_byte1; /* Last row that exists (for two-byte fonts) */ 11018 Bool all_chars_exist; /* Flag if all characters have nonzero size */ 11019 uint default_char; /* Char to print for undefined character */ 11020 int n_properties; /* How many properties there are */ 11021 XFontProp *properties; /* Pointer to array of additional properties*/ 11022 XCharStruct min_bounds; /* Minimum bounds over all existing char*/ 11023 XCharStruct max_bounds; /* Maximum bounds over all existing char*/ 11024 XCharStruct *per_char; /* first_char to last_char information */ 11025 int ascent; /* Max extent above baseline for spacing */ 11026 int descent; /* Max descent below baseline for spacing */ 11027 } 11028 11029 XFontStruct *XLoadQueryFont(Display *display, in char *name); 11030 int XFreeFont(Display *display, XFontStruct *font_struct); 11031 int XSetFont(Display* display, GC gc, Font font); 11032 int XTextWidth(XFontStruct*, in char*, int); 11033 11034 int XSetLineAttributes(Display *display, GC gc, uint line_width, int line_style, int cap_style, int join_style); 11035 int XSetDashes(Display *display, GC gc, int dash_offset, in byte* dash_list, int n); 11036 11037 11038 11039 /* 11040 * Definitions of specific events. 11041 */ 11042 struct XKeyEvent 11043 { 11044 int type; /* of event */ 11045 arch_ulong serial; /* # of last request processed by server */ 11046 Bool send_event; /* true if this came from a SendEvent request */ 11047 Display *display; /* Display the event was read from */ 11048 Window window; /* "event" window it is reported relative to */ 11049 Window root; /* root window that the event occurred on */ 11050 Window subwindow; /* child window */ 11051 Time time; /* milliseconds */ 11052 int x, y; /* pointer x, y coordinates in event window */ 11053 int x_root, y_root; /* coordinates relative to root */ 11054 KeyOrButtonMask state; /* key or button mask */ 11055 uint keycode; /* detail */ 11056 Bool same_screen; /* same screen flag */ 11057 } 11058 version(X86_64) static assert(XKeyEvent.sizeof == 96); 11059 alias XKeyEvent XKeyPressedEvent; 11060 alias XKeyEvent XKeyReleasedEvent; 11061 11062 struct XButtonEvent 11063 { 11064 int type; /* of event */ 11065 arch_ulong serial; /* # of last request processed by server */ 11066 Bool send_event; /* true if this came from a SendEvent request */ 11067 Display *display; /* Display the event was read from */ 11068 Window window; /* "event" window it is reported relative to */ 11069 Window root; /* root window that the event occurred on */ 11070 Window subwindow; /* child window */ 11071 Time time; /* milliseconds */ 11072 int x, y; /* pointer x, y coordinates in event window */ 11073 int x_root, y_root; /* coordinates relative to root */ 11074 KeyOrButtonMask state; /* key or button mask */ 11075 uint button; /* detail */ 11076 Bool same_screen; /* same screen flag */ 11077 } 11078 alias XButtonEvent XButtonPressedEvent; 11079 alias XButtonEvent XButtonReleasedEvent; 11080 11081 struct XMotionEvent{ 11082 int type; /* of event */ 11083 arch_ulong serial; /* # of last request processed by server */ 11084 Bool send_event; /* true if this came from a SendEvent request */ 11085 Display *display; /* Display the event was read from */ 11086 Window window; /* "event" window reported relative to */ 11087 Window root; /* root window that the event occurred on */ 11088 Window subwindow; /* child window */ 11089 Time time; /* milliseconds */ 11090 int x, y; /* pointer x, y coordinates in event window */ 11091 int x_root, y_root; /* coordinates relative to root */ 11092 KeyOrButtonMask state; /* key or button mask */ 11093 byte is_hint; /* detail */ 11094 Bool same_screen; /* same screen flag */ 11095 } 11096 alias XMotionEvent XPointerMovedEvent; 11097 11098 struct XCrossingEvent{ 11099 int type; /* of event */ 11100 arch_ulong serial; /* # of last request processed by server */ 11101 Bool send_event; /* true if this came from a SendEvent request */ 11102 Display *display; /* Display the event was read from */ 11103 Window window; /* "event" window reported relative to */ 11104 Window root; /* root window that the event occurred on */ 11105 Window subwindow; /* child window */ 11106 Time time; /* milliseconds */ 11107 int x, y; /* pointer x, y coordinates in event window */ 11108 int x_root, y_root; /* coordinates relative to root */ 11109 NotifyModes mode; /* NotifyNormal, NotifyGrab, NotifyUngrab */ 11110 NotifyDetail detail; 11111 /* 11112 * NotifyAncestor, NotifyVirtual, NotifyInferior, 11113 * NotifyNonlinear,NotifyNonlinearVirtual 11114 */ 11115 Bool same_screen; /* same screen flag */ 11116 Bool focus; /* Boolean focus */ 11117 KeyOrButtonMask state; /* key or button mask */ 11118 } 11119 alias XCrossingEvent XEnterWindowEvent; 11120 alias XCrossingEvent XLeaveWindowEvent; 11121 11122 struct XFocusChangeEvent{ 11123 int type; /* FocusIn or FocusOut */ 11124 arch_ulong serial; /* # of last request processed by server */ 11125 Bool send_event; /* true if this came from a SendEvent request */ 11126 Display *display; /* Display the event was read from */ 11127 Window window; /* window of event */ 11128 NotifyModes mode; /* NotifyNormal, NotifyWhileGrabbed, 11129 NotifyGrab, NotifyUngrab */ 11130 NotifyDetail detail; 11131 /* 11132 * NotifyAncestor, NotifyVirtual, NotifyInferior, 11133 * NotifyNonlinear,NotifyNonlinearVirtual, NotifyPointer, 11134 * NotifyPointerRoot, NotifyDetailNone 11135 */ 11136 } 11137 alias XFocusChangeEvent XFocusInEvent; 11138 alias XFocusChangeEvent XFocusOutEvent; 11139 Window XCreateSimpleWindow( 11140 Display* /* display */, 11141 Window /* parent */, 11142 int /* x */, 11143 int /* y */, 11144 uint /* width */, 11145 uint /* height */, 11146 uint /* border_width */, 11147 uint /* border */, 11148 uint /* background */ 11149 ); 11150 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); 11151 11152 int XReparentWindow(Display*, Window, Window, int, int); 11153 int XClearWindow(Display*, Window); 11154 int XMoveResizeWindow(Display*, Window, int, int, uint, uint); 11155 int XMoveWindow(Display*, Window, int, int); 11156 int XResizeWindow(Display *display, Window w, uint width, uint height); 11157 11158 Colormap XCreateColormap(Display *display, Window w, Visual *visual, int alloc); 11159 11160 enum CWBackPixmap = (1L<<0); 11161 enum CWBackPixel = (1L<<1); 11162 enum CWBorderPixmap = (1L<<2); 11163 enum CWBorderPixel = (1L<<3); 11164 enum CWBitGravity = (1L<<4); 11165 enum CWWinGravity = (1L<<5); 11166 enum CWBackingStore = (1L<<6); 11167 enum CWBackingPlanes = (1L<<7); 11168 enum CWBackingPixel = (1L<<8); 11169 enum CWOverrideRedirect = (1L<<9); 11170 enum CWSaveUnder = (1L<<10); 11171 enum CWEventMask = (1L<<11); 11172 enum CWDontPropagate = (1L<<12); 11173 enum CWColormap = (1L<<13); 11174 enum CWCursor = (1L<<14); 11175 11176 struct XWindowAttributes { 11177 int x, y; /* location of window */ 11178 int width, height; /* width and height of window */ 11179 int border_width; /* border width of window */ 11180 int depth; /* depth of window */ 11181 Visual *visual; /* the associated visual structure */ 11182 Window root; /* root of screen containing window */ 11183 int class_; /* InputOutput, InputOnly*/ 11184 int bit_gravity; /* one of the bit gravity values */ 11185 int win_gravity; /* one of the window gravity values */ 11186 int backing_store; /* NotUseful, WhenMapped, Always */ 11187 arch_ulong backing_planes; /* planes to be preserved if possible */ 11188 arch_ulong backing_pixel; /* value to be used when restoring planes */ 11189 Bool save_under; /* boolean, should bits under be saved? */ 11190 Colormap colormap; /* color map to be associated with window */ 11191 Bool map_installed; /* boolean, is color map currently installed*/ 11192 int map_state; /* IsUnmapped, IsUnviewable, IsViewable */ 11193 arch_long all_event_masks; /* set of events all people have interest in*/ 11194 arch_long your_event_mask; /* my event mask */ 11195 arch_long do_not_propagate_mask; /* set of events that should not propagate */ 11196 Bool override_redirect; /* boolean value for override-redirect */ 11197 Screen *screen; /* back pointer to correct screen */ 11198 } 11199 11200 enum IsUnmapped = 0; 11201 enum IsUnviewable = 1; 11202 enum IsViewable = 2; 11203 11204 Status XGetWindowAttributes(Display*, Window, XWindowAttributes*); 11205 11206 struct XSetWindowAttributes { 11207 Pixmap background_pixmap;/* background, None, or ParentRelative */ 11208 arch_ulong background_pixel;/* background pixel */ 11209 Pixmap border_pixmap; /* border of the window or CopyFromParent */ 11210 arch_ulong border_pixel;/* border pixel value */ 11211 int bit_gravity; /* one of bit gravity values */ 11212 int win_gravity; /* one of the window gravity values */ 11213 int backing_store; /* NotUseful, WhenMapped, Always */ 11214 arch_ulong backing_planes;/* planes to be preserved if possible */ 11215 arch_ulong backing_pixel;/* value to use in restoring planes */ 11216 Bool save_under; /* should bits under be saved? (popups) */ 11217 arch_long event_mask; /* set of events that should be saved */ 11218 arch_long do_not_propagate_mask;/* set of events that should not propagate */ 11219 Bool override_redirect; /* boolean value for override_redirect */ 11220 Colormap colormap; /* color map to be associated with window */ 11221 Cursor cursor; /* cursor to be displayed (or None) */ 11222 } 11223 11224 11225 11226 11227 XImage *XCreateImage( 11228 Display* /* display */, 11229 Visual* /* visual */, 11230 uint /* depth */, 11231 int /* format */, 11232 int /* offset */, 11233 ubyte* /* data */, 11234 uint /* width */, 11235 uint /* height */, 11236 int /* bitmap_pad */, 11237 int /* bytes_per_line */ 11238 ); 11239 11240 Status XInitImage (XImage* image); 11241 11242 Atom XInternAtom( 11243 Display* /* display */, 11244 const char* /* atom_name */, 11245 Bool /* only_if_exists */ 11246 ); 11247 11248 Status XInternAtoms(Display*, const char**, int, Bool, Atom*); 11249 char* XGetAtomName(Display*, Atom); 11250 Status XGetAtomNames(Display*, Atom*, int count, char**); 11251 11252 alias int Status; 11253 11254 11255 enum EventMask:int 11256 { 11257 NoEventMask =0, 11258 KeyPressMask =1<<0, 11259 KeyReleaseMask =1<<1, 11260 ButtonPressMask =1<<2, 11261 ButtonReleaseMask =1<<3, 11262 EnterWindowMask =1<<4, 11263 LeaveWindowMask =1<<5, 11264 PointerMotionMask =1<<6, 11265 PointerMotionHintMask =1<<7, 11266 Button1MotionMask =1<<8, 11267 Button2MotionMask =1<<9, 11268 Button3MotionMask =1<<10, 11269 Button4MotionMask =1<<11, 11270 Button5MotionMask =1<<12, 11271 ButtonMotionMask =1<<13, 11272 KeymapStateMask =1<<14, 11273 ExposureMask =1<<15, 11274 VisibilityChangeMask =1<<16, 11275 StructureNotifyMask =1<<17, 11276 ResizeRedirectMask =1<<18, 11277 SubstructureNotifyMask =1<<19, 11278 SubstructureRedirectMask=1<<20, 11279 FocusChangeMask =1<<21, 11280 PropertyChangeMask =1<<22, 11281 ColormapChangeMask =1<<23, 11282 OwnerGrabButtonMask =1<<24 11283 } 11284 11285 int XPutImage( 11286 Display* /* display */, 11287 Drawable /* d */, 11288 GC /* gc */, 11289 XImage* /* image */, 11290 int /* src_x */, 11291 int /* src_y */, 11292 int /* dest_x */, 11293 int /* dest_y */, 11294 uint /* width */, 11295 uint /* height */ 11296 ); 11297 11298 int XDestroyWindow( 11299 Display* /* display */, 11300 Window /* w */ 11301 ); 11302 11303 int XDestroyImage(XImage*); 11304 11305 int XSelectInput( 11306 Display* /* display */, 11307 Window /* w */, 11308 EventMask /* event_mask */ 11309 ); 11310 11311 int XMapWindow( 11312 Display* /* display */, 11313 Window /* w */ 11314 ); 11315 11316 struct MwmHints { 11317 int flags; 11318 int functions; 11319 int decorations; 11320 int input_mode; 11321 int status; 11322 } 11323 11324 enum { 11325 MWM_HINTS_FUNCTIONS = (1L << 0), 11326 MWM_HINTS_DECORATIONS = (1L << 1), 11327 11328 MWM_FUNC_ALL = (1L << 0), 11329 MWM_FUNC_RESIZE = (1L << 1), 11330 MWM_FUNC_MOVE = (1L << 2), 11331 MWM_FUNC_MINIMIZE = (1L << 3), 11332 MWM_FUNC_MAXIMIZE = (1L << 4), 11333 MWM_FUNC_CLOSE = (1L << 5) 11334 } 11335 11336 Status XIconifyWindow(Display*, Window, int); 11337 int XMapRaised(Display*, Window); 11338 int XMapSubwindows(Display*, Window); 11339 11340 int XNextEvent( 11341 Display* /* display */, 11342 XEvent* /* event_return */ 11343 ); 11344 11345 int XMaskEvent(Display*, arch_long, XEvent*); 11346 11347 Bool XFilterEvent(XEvent *event, Window window); 11348 int XRefreshKeyboardMapping(XMappingEvent *event_map); 11349 11350 Status XSetWMProtocols( 11351 Display* /* display */, 11352 Window /* w */, 11353 Atom* /* protocols */, 11354 int /* count */ 11355 ); 11356 11357 import core.stdc.config : c_long, c_ulong; 11358 void XSetWMNormalHints(Display *display, Window w, XSizeHints *hints); 11359 Status XGetWMNormalHints(Display *display, Window w, XSizeHints *hints, c_long* supplied_return); 11360 11361 /* Size hints mask bits */ 11362 11363 enum USPosition = (1L << 0) /* user specified x, y */; 11364 enum USSize = (1L << 1) /* user specified width, height */; 11365 enum PPosition = (1L << 2) /* program specified position */; 11366 enum PSize = (1L << 3) /* program specified size */; 11367 enum PMinSize = (1L << 4) /* program specified minimum size */; 11368 enum PMaxSize = (1L << 5) /* program specified maximum size */; 11369 enum PResizeInc = (1L << 6) /* program specified resize increments */; 11370 enum PAspect = (1L << 7) /* program specified min and max aspect ratios */; 11371 enum PBaseSize = (1L << 8); 11372 enum PWinGravity = (1L << 9); 11373 enum PAllHints = (PPosition|PSize| PMinSize|PMaxSize| PResizeInc|PAspect); 11374 struct XSizeHints { 11375 arch_long flags; /* marks which fields in this structure are defined */ 11376 int x, y; /* Obsolete */ 11377 int width, height; /* Obsolete */ 11378 int min_width, min_height; 11379 int max_width, max_height; 11380 int width_inc, height_inc; 11381 struct Aspect { 11382 int x; /* numerator */ 11383 int y; /* denominator */ 11384 } 11385 11386 Aspect min_aspect; 11387 Aspect max_aspect; 11388 int base_width, base_height; 11389 int win_gravity; 11390 /* this structure may be extended in the future */ 11391 } 11392 11393 11394 11395 enum EventType:int 11396 { 11397 KeyPress =2, 11398 KeyRelease =3, 11399 ButtonPress =4, 11400 ButtonRelease =5, 11401 MotionNotify =6, 11402 EnterNotify =7, 11403 LeaveNotify =8, 11404 FocusIn =9, 11405 FocusOut =10, 11406 KeymapNotify =11, 11407 Expose =12, 11408 GraphicsExpose =13, 11409 NoExpose =14, 11410 VisibilityNotify =15, 11411 CreateNotify =16, 11412 DestroyNotify =17, 11413 UnmapNotify =18, 11414 MapNotify =19, 11415 MapRequest =20, 11416 ReparentNotify =21, 11417 ConfigureNotify =22, 11418 ConfigureRequest =23, 11419 GravityNotify =24, 11420 ResizeRequest =25, 11421 CirculateNotify =26, 11422 CirculateRequest =27, 11423 PropertyNotify =28, 11424 SelectionClear =29, 11425 SelectionRequest =30, 11426 SelectionNotify =31, 11427 ColormapNotify =32, 11428 ClientMessage =33, 11429 MappingNotify =34, 11430 LASTEvent =35 /* must be bigger than any event # */ 11431 } 11432 /* generated on EnterWindow and FocusIn when KeyMapState selected */ 11433 struct XKeymapEvent 11434 { 11435 int type; 11436 arch_ulong serial; /* # of last request processed by server */ 11437 Bool send_event; /* true if this came from a SendEvent request */ 11438 Display *display; /* Display the event was read from */ 11439 Window window; 11440 byte[32] key_vector; 11441 } 11442 11443 struct XExposeEvent 11444 { 11445 int type; 11446 arch_ulong serial; /* # of last request processed by server */ 11447 Bool send_event; /* true if this came from a SendEvent request */ 11448 Display *display; /* Display the event was read from */ 11449 Window window; 11450 int x, y; 11451 int width, height; 11452 int count; /* if non-zero, at least this many more */ 11453 } 11454 11455 struct XGraphicsExposeEvent{ 11456 int type; 11457 arch_ulong serial; /* # of last request processed by server */ 11458 Bool send_event; /* true if this came from a SendEvent request */ 11459 Display *display; /* Display the event was read from */ 11460 Drawable drawable; 11461 int x, y; 11462 int width, height; 11463 int count; /* if non-zero, at least this many more */ 11464 int major_code; /* core is CopyArea or CopyPlane */ 11465 int minor_code; /* not defined in the core */ 11466 } 11467 11468 struct XNoExposeEvent{ 11469 int type; 11470 arch_ulong serial; /* # of last request processed by server */ 11471 Bool send_event; /* true if this came from a SendEvent request */ 11472 Display *display; /* Display the event was read from */ 11473 Drawable drawable; 11474 int major_code; /* core is CopyArea or CopyPlane */ 11475 int minor_code; /* not defined in the core */ 11476 } 11477 11478 struct XVisibilityEvent{ 11479 int type; 11480 arch_ulong serial; /* # of last request processed by server */ 11481 Bool send_event; /* true if this came from a SendEvent request */ 11482 Display *display; /* Display the event was read from */ 11483 Window window; 11484 VisibilityNotify state; /* Visibility state */ 11485 } 11486 11487 struct XCreateWindowEvent{ 11488 int type; 11489 arch_ulong serial; /* # of last request processed by server */ 11490 Bool send_event; /* true if this came from a SendEvent request */ 11491 Display *display; /* Display the event was read from */ 11492 Window parent; /* parent of the window */ 11493 Window window; /* window id of window created */ 11494 int x, y; /* window location */ 11495 int width, height; /* size of window */ 11496 int border_width; /* border width */ 11497 Bool override_redirect; /* creation should be overridden */ 11498 } 11499 11500 struct XDestroyWindowEvent 11501 { 11502 int type; 11503 arch_ulong serial; /* # of last request processed by server */ 11504 Bool send_event; /* true if this came from a SendEvent request */ 11505 Display *display; /* Display the event was read from */ 11506 Window event; 11507 Window window; 11508 } 11509 11510 struct XUnmapEvent 11511 { 11512 int type; 11513 arch_ulong serial; /* # of last request processed by server */ 11514 Bool send_event; /* true if this came from a SendEvent request */ 11515 Display *display; /* Display the event was read from */ 11516 Window event; 11517 Window window; 11518 Bool from_configure; 11519 } 11520 11521 struct XMapEvent 11522 { 11523 int type; 11524 arch_ulong serial; /* # of last request processed by server */ 11525 Bool send_event; /* true if this came from a SendEvent request */ 11526 Display *display; /* Display the event was read from */ 11527 Window event; 11528 Window window; 11529 Bool override_redirect; /* Boolean, is override set... */ 11530 } 11531 11532 struct XMapRequestEvent 11533 { 11534 int type; 11535 arch_ulong serial; /* # of last request processed by server */ 11536 Bool send_event; /* true if this came from a SendEvent request */ 11537 Display *display; /* Display the event was read from */ 11538 Window parent; 11539 Window window; 11540 } 11541 11542 struct XReparentEvent 11543 { 11544 int type; 11545 arch_ulong serial; /* # of last request processed by server */ 11546 Bool send_event; /* true if this came from a SendEvent request */ 11547 Display *display; /* Display the event was read from */ 11548 Window event; 11549 Window window; 11550 Window parent; 11551 int x, y; 11552 Bool override_redirect; 11553 } 11554 11555 struct XConfigureEvent 11556 { 11557 int type; 11558 arch_ulong serial; /* # of last request processed by server */ 11559 Bool send_event; /* true if this came from a SendEvent request */ 11560 Display *display; /* Display the event was read from */ 11561 Window event; 11562 Window window; 11563 int x, y; 11564 int width, height; 11565 int border_width; 11566 Window above; 11567 Bool override_redirect; 11568 } 11569 11570 struct XGravityEvent 11571 { 11572 int type; 11573 arch_ulong serial; /* # of last request processed by server */ 11574 Bool send_event; /* true if this came from a SendEvent request */ 11575 Display *display; /* Display the event was read from */ 11576 Window event; 11577 Window window; 11578 int x, y; 11579 } 11580 11581 struct XResizeRequestEvent 11582 { 11583 int type; 11584 arch_ulong serial; /* # of last request processed by server */ 11585 Bool send_event; /* true if this came from a SendEvent request */ 11586 Display *display; /* Display the event was read from */ 11587 Window window; 11588 int width, height; 11589 } 11590 11591 struct XConfigureRequestEvent 11592 { 11593 int type; 11594 arch_ulong serial; /* # of last request processed by server */ 11595 Bool send_event; /* true if this came from a SendEvent request */ 11596 Display *display; /* Display the event was read from */ 11597 Window parent; 11598 Window window; 11599 int x, y; 11600 int width, height; 11601 int border_width; 11602 Window above; 11603 WindowStackingMethod detail; /* Above, Below, TopIf, BottomIf, Opposite */ 11604 arch_ulong value_mask; 11605 } 11606 11607 struct XCirculateEvent 11608 { 11609 int type; 11610 arch_ulong serial; /* # of last request processed by server */ 11611 Bool send_event; /* true if this came from a SendEvent request */ 11612 Display *display; /* Display the event was read from */ 11613 Window event; 11614 Window window; 11615 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 11616 } 11617 11618 struct XCirculateRequestEvent 11619 { 11620 int type; 11621 arch_ulong serial; /* # of last request processed by server */ 11622 Bool send_event; /* true if this came from a SendEvent request */ 11623 Display *display; /* Display the event was read from */ 11624 Window parent; 11625 Window window; 11626 CirculationRequest place; /* PlaceOnTop, PlaceOnBottom */ 11627 } 11628 11629 struct XPropertyEvent 11630 { 11631 int type; 11632 arch_ulong serial; /* # of last request processed by server */ 11633 Bool send_event; /* true if this came from a SendEvent request */ 11634 Display *display; /* Display the event was read from */ 11635 Window window; 11636 Atom atom; 11637 Time time; 11638 PropertyNotification state; /* NewValue, Deleted */ 11639 } 11640 11641 struct XSelectionClearEvent 11642 { 11643 int type; 11644 arch_ulong serial; /* # of last request processed by server */ 11645 Bool send_event; /* true if this came from a SendEvent request */ 11646 Display *display; /* Display the event was read from */ 11647 Window window; 11648 Atom selection; 11649 Time time; 11650 } 11651 11652 struct XSelectionRequestEvent 11653 { 11654 int type; 11655 arch_ulong serial; /* # of last request processed by server */ 11656 Bool send_event; /* true if this came from a SendEvent request */ 11657 Display *display; /* Display the event was read from */ 11658 Window owner; 11659 Window requestor; 11660 Atom selection; 11661 Atom target; 11662 Atom property; 11663 Time time; 11664 } 11665 11666 struct XSelectionEvent 11667 { 11668 int type; 11669 arch_ulong serial; /* # of last request processed by server */ 11670 Bool send_event; /* true if this came from a SendEvent request */ 11671 Display *display; /* Display the event was read from */ 11672 Window requestor; 11673 Atom selection; 11674 Atom target; 11675 Atom property; /* ATOM or None */ 11676 Time time; 11677 } 11678 version(X86_64) static assert(XSelectionClearEvent.sizeof == 56); 11679 11680 struct XColormapEvent 11681 { 11682 int type; 11683 arch_ulong serial; /* # of last request processed by server */ 11684 Bool send_event; /* true if this came from a SendEvent request */ 11685 Display *display; /* Display the event was read from */ 11686 Window window; 11687 Colormap colormap; /* COLORMAP or None */ 11688 Bool new_; /* C++ */ 11689 ColorMapNotification state; /* ColormapInstalled, ColormapUninstalled */ 11690 } 11691 version(X86_64) static assert(XColormapEvent.sizeof == 56); 11692 11693 struct XClientMessageEvent 11694 { 11695 int type; 11696 arch_ulong serial; /* # of last request processed by server */ 11697 Bool send_event; /* true if this came from a SendEvent request */ 11698 Display *display; /* Display the event was read from */ 11699 Window window; 11700 Atom message_type; 11701 int format; 11702 union Data{ 11703 byte[20] b; 11704 short[10] s; 11705 arch_ulong[5] l; 11706 } 11707 Data data; 11708 11709 } 11710 version(X86_64) static assert(XClientMessageEvent.sizeof == 96); 11711 11712 struct XMappingEvent 11713 { 11714 int type; 11715 arch_ulong serial; /* # of last request processed by server */ 11716 Bool send_event; /* true if this came from a SendEvent request */ 11717 Display *display; /* Display the event was read from */ 11718 Window window; /* unused */ 11719 MappingType request; /* one of MappingModifier, MappingKeyboard, 11720 MappingPointer */ 11721 int first_keycode; /* first keycode */ 11722 int count; /* defines range of change w. first_keycode*/ 11723 } 11724 11725 struct XErrorEvent 11726 { 11727 int type; 11728 Display *display; /* Display the event was read from */ 11729 XID resourceid; /* resource id */ 11730 arch_ulong serial; /* serial number of failed request */ 11731 ubyte error_code; /* error code of failed request */ 11732 ubyte request_code; /* Major op-code of failed request */ 11733 ubyte minor_code; /* Minor op-code of failed request */ 11734 } 11735 11736 struct XAnyEvent 11737 { 11738 int type; 11739 arch_ulong serial; /* # of last request processed by server */ 11740 Bool send_event; /* true if this came from a SendEvent request */ 11741 Display *display;/* Display the event was read from */ 11742 Window window; /* window on which event was requested in event mask */ 11743 } 11744 11745 union XEvent{ 11746 int type; /* must not be changed; first element */ 11747 XAnyEvent xany; 11748 XKeyEvent xkey; 11749 XButtonEvent xbutton; 11750 XMotionEvent xmotion; 11751 XCrossingEvent xcrossing; 11752 XFocusChangeEvent xfocus; 11753 XExposeEvent xexpose; 11754 XGraphicsExposeEvent xgraphicsexpose; 11755 XNoExposeEvent xnoexpose; 11756 XVisibilityEvent xvisibility; 11757 XCreateWindowEvent xcreatewindow; 11758 XDestroyWindowEvent xdestroywindow; 11759 XUnmapEvent xunmap; 11760 XMapEvent xmap; 11761 XMapRequestEvent xmaprequest; 11762 XReparentEvent xreparent; 11763 XConfigureEvent xconfigure; 11764 XGravityEvent xgravity; 11765 XResizeRequestEvent xresizerequest; 11766 XConfigureRequestEvent xconfigurerequest; 11767 XCirculateEvent xcirculate; 11768 XCirculateRequestEvent xcirculaterequest; 11769 XPropertyEvent xproperty; 11770 XSelectionClearEvent xselectionclear; 11771 XSelectionRequestEvent xselectionrequest; 11772 XSelectionEvent xselection; 11773 XColormapEvent xcolormap; 11774 XClientMessageEvent xclient; 11775 XMappingEvent xmapping; 11776 XErrorEvent xerror; 11777 XKeymapEvent xkeymap; 11778 arch_ulong[24] pad; 11779 } 11780 11781 11782 struct Display { 11783 XExtData *ext_data; /* hook for extension to hang data */ 11784 _XPrivate *private1; 11785 int fd; /* Network socket. */ 11786 int private2; 11787 int proto_major_version;/* major version of server's X protocol */ 11788 int proto_minor_version;/* minor version of servers X protocol */ 11789 char *vendor; /* vendor of the server hardware */ 11790 XID private3; 11791 XID private4; 11792 XID private5; 11793 int private6; 11794 XID function(Display*)resource_alloc;/* allocator function */ 11795 ByteOrder byte_order; /* screen byte order, LSBFirst, MSBFirst */ 11796 int bitmap_unit; /* padding and data requirements */ 11797 int bitmap_pad; /* padding requirements on bitmaps */ 11798 ByteOrder bitmap_bit_order; /* LeastSignificant or MostSignificant */ 11799 int nformats; /* number of pixmap formats in list */ 11800 ScreenFormat *pixmap_format; /* pixmap format list */ 11801 int private8; 11802 int release; /* release of the server */ 11803 _XPrivate *private9; 11804 _XPrivate *private10; 11805 int qlen; /* Length of input event queue */ 11806 arch_ulong last_request_read; /* seq number of last event read */ 11807 arch_ulong request; /* sequence number of last request. */ 11808 XPointer private11; 11809 XPointer private12; 11810 XPointer private13; 11811 XPointer private14; 11812 uint max_request_size; /* maximum number 32 bit words in request*/ 11813 _XrmHashBucketRec *db; 11814 int function (Display*)private15; 11815 char *display_name; /* "host:display" string used on this connect*/ 11816 int default_screen; /* default screen for operations */ 11817 int nscreens; /* number of screens on this server*/ 11818 Screen *screens; /* pointer to list of screens */ 11819 arch_ulong motion_buffer; /* size of motion buffer */ 11820 arch_ulong private16; 11821 int min_keycode; /* minimum defined keycode */ 11822 int max_keycode; /* maximum defined keycode */ 11823 XPointer private17; 11824 XPointer private18; 11825 int private19; 11826 byte *xdefaults; /* contents of defaults from server */ 11827 /* there is more to this structure, but it is private to Xlib */ 11828 } 11829 11830 // I got these numbers from a C program as a sanity test 11831 version(X86_64) { 11832 static assert(Display.sizeof == 296); 11833 static assert(XPointer.sizeof == 8); 11834 static assert(XErrorEvent.sizeof == 40); 11835 static assert(XAnyEvent.sizeof == 40); 11836 static assert(XMappingEvent.sizeof == 56); 11837 static assert(XEvent.sizeof == 192); 11838 } else { 11839 static assert(Display.sizeof == 176); 11840 static assert(XPointer.sizeof == 4); 11841 static assert(XEvent.sizeof == 96); 11842 } 11843 11844 struct Depth 11845 { 11846 int depth; /* this depth (Z) of the depth */ 11847 int nvisuals; /* number of Visual types at this depth */ 11848 Visual *visuals; /* list of visuals possible at this depth */ 11849 } 11850 11851 alias void* GC; 11852 alias c_ulong VisualID; 11853 alias XID Colormap; 11854 alias XID Cursor; 11855 alias XID KeySym; 11856 alias uint KeyCode; 11857 enum None = 0; 11858 } 11859 11860 version(without_opengl) {} 11861 else { 11862 extern(C) nothrow @nogc { 11863 11864 11865 static if(!SdpyIsUsingIVGLBinds) { 11866 enum GLX_USE_GL= 1; /* support GLX rendering */ 11867 enum GLX_BUFFER_SIZE= 2; /* depth of the color buffer */ 11868 enum GLX_LEVEL= 3; /* level in plane stacking */ 11869 enum GLX_RGBA= 4; /* true if RGBA mode */ 11870 enum GLX_DOUBLEBUFFER= 5; /* double buffering supported */ 11871 enum GLX_STEREO= 6; /* stereo buffering supported */ 11872 enum GLX_AUX_BUFFERS= 7; /* number of aux buffers */ 11873 enum GLX_RED_SIZE= 8; /* number of red component bits */ 11874 enum GLX_GREEN_SIZE= 9; /* number of green component bits */ 11875 enum GLX_BLUE_SIZE= 10; /* number of blue component bits */ 11876 enum GLX_ALPHA_SIZE= 11; /* number of alpha component bits */ 11877 enum GLX_DEPTH_SIZE= 12; /* number of depth bits */ 11878 enum GLX_STENCIL_SIZE= 13; /* number of stencil bits */ 11879 enum GLX_ACCUM_RED_SIZE= 14; /* number of red accum bits */ 11880 enum GLX_ACCUM_GREEN_SIZE= 15; /* number of green accum bits */ 11881 enum GLX_ACCUM_BLUE_SIZE= 16; /* number of blue accum bits */ 11882 enum GLX_ACCUM_ALPHA_SIZE= 17; /* number of alpha accum bits */ 11883 11884 11885 //XVisualInfo* glXChooseVisual(Display *dpy, int screen, in int *attrib_list); 11886 11887 11888 11889 enum GL_TRUE = 1; 11890 enum GL_FALSE = 0; 11891 alias int GLint; 11892 } 11893 11894 alias XID GLXContextID; 11895 alias XID GLXPixmap; 11896 alias XID GLXDrawable; 11897 alias XID GLXPbuffer; 11898 alias XID GLXWindow; 11899 alias XID GLXFBConfigID; 11900 alias void* GLXContext; 11901 11902 static if (!SdpyIsUsingIVGLBinds) { 11903 XVisualInfo* glXChooseVisual(Display *dpy, int screen, 11904 const int *attrib_list); 11905 11906 void glXCopyContext(Display *dpy, GLXContext src, 11907 GLXContext dst, arch_ulong mask); 11908 11909 GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, 11910 GLXContext share_list, Bool direct); 11911 11912 GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisualInfo *vis, 11913 Pixmap pixmap); 11914 11915 void glXDestroyContext(Display *dpy, GLXContext ctx); 11916 11917 void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix); 11918 11919 int glXGetConfig(Display *dpy, XVisualInfo *vis, 11920 int attrib, int *value); 11921 11922 GLXContext glXGetCurrentContext(); 11923 11924 GLXDrawable glXGetCurrentDrawable(); 11925 11926 Bool glXIsDirect(Display *dpy, GLXContext ctx); 11927 11928 Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, 11929 GLXContext ctx); 11930 11931 Bool glXQueryExtension(Display *dpy, int *error_base, int *event_base); 11932 11933 Bool glXQueryVersion(Display *dpy, int *major, int *minor); 11934 11935 void glXSwapBuffers(Display *dpy, GLXDrawable drawable); 11936 11937 void glXUseXFont(Font font, int first, int count, int list_base); 11938 11939 void glXWaitGL(); 11940 11941 void glXWaitX(); 11942 } 11943 11944 } 11945 } 11946 11947 enum AllocNone = 0; 11948 11949 extern(C) { 11950 /* WARNING, this type not in Xlib spec */ 11951 extern(C) alias XIOErrorHandler = int function (Display* display); 11952 XIOErrorHandler XSetIOErrorHandler (XIOErrorHandler handler); 11953 } 11954 11955 extern(C) nothrow @nogc { 11956 struct Screen{ 11957 XExtData *ext_data; /* hook for extension to hang data */ 11958 Display *display; /* back pointer to display structure */ 11959 Window root; /* Root window id. */ 11960 int width, height; /* width and height of screen */ 11961 int mwidth, mheight; /* width and height of in millimeters */ 11962 int ndepths; /* number of depths possible */ 11963 Depth *depths; /* list of allowable depths on the screen */ 11964 int root_depth; /* bits per pixel */ 11965 Visual *root_visual; /* root visual */ 11966 GC default_gc; /* GC for the root root visual */ 11967 Colormap cmap; /* default color map */ 11968 uint white_pixel; 11969 uint black_pixel; /* White and Black pixel values */ 11970 int max_maps, min_maps; /* max and min color maps */ 11971 int backing_store; /* Never, WhenMapped, Always */ 11972 bool save_unders; 11973 int root_input_mask; /* initial root input mask */ 11974 } 11975 11976 struct Visual 11977 { 11978 XExtData *ext_data; /* hook for extension to hang data */ 11979 VisualID visualid; /* visual id of this visual */ 11980 int class_; /* class of screen (monochrome, etc.) */ 11981 c_ulong red_mask, green_mask, blue_mask; /* mask values */ 11982 int bits_per_rgb; /* log base 2 of distinct color values */ 11983 int map_entries; /* color map entries */ 11984 } 11985 11986 alias Display* _XPrivDisplay; 11987 11988 Screen* ScreenOfDisplay(Display* dpy, int scr) { 11989 assert(dpy !is null); 11990 return &dpy.screens[scr]; 11991 } 11992 11993 Window RootWindow(Display *dpy,int scr) { 11994 return ScreenOfDisplay(dpy,scr).root; 11995 } 11996 11997 struct XWMHints { 11998 arch_long flags; 11999 Bool input; 12000 int initial_state; 12001 Pixmap icon_pixmap; 12002 Window icon_window; 12003 int icon_x, icon_y; 12004 Pixmap icon_mask; 12005 XID window_group; 12006 } 12007 12008 struct XClassHint { 12009 char* res_name; 12010 char* res_class; 12011 } 12012 12013 Status XInitThreads(); 12014 void XLockDisplay (Display* display); 12015 void XUnlockDisplay (Display* display); 12016 12017 void XSetWMProperties(Display*, Window, XTextProperty*, XTextProperty*, char**, int, XSizeHints*, XWMHints*, XClassHint*); 12018 12019 int XSetWindowBackground (Display* display, Window w, c_ulong background_pixel); 12020 int XSetWindowBackgroundPixmap (Display* display, Window w, Pixmap background_pixmap); 12021 //int XSetWindowBorder (Display* display, Window w, c_ulong border_pixel); 12022 //int XSetWindowBorderPixmap (Display* display, Window w, Pixmap border_pixmap); 12023 //int XSetWindowBorderWidth (Display* display, Window w, uint width); 12024 12025 12026 // this requires -lXpm 12027 int XpmCreatePixmapFromData(Display*, Drawable, in char**, Pixmap*, Pixmap*, void*); // FIXME: void* should be XpmAttributes 12028 12029 int DefaultScreen(Display *dpy) { 12030 return dpy.default_screen; 12031 } 12032 12033 int DefaultDepth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).root_depth; } 12034 int DisplayWidth(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).width; } 12035 int DisplayHeight(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).height; } 12036 int DisplayWidthMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mwidth; } 12037 int DisplayHeightMM(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).mheight; } 12038 auto DefaultColormap(Display* dpy, int scr) { return ScreenOfDisplay(dpy, scr).cmap; } 12039 12040 int ConnectionNumber(Display* dpy) { return dpy.fd; } 12041 12042 enum int AnyPropertyType = 0; 12043 enum int Success = 0; 12044 12045 enum int RevertToNone = None; 12046 enum int PointerRoot = 1; 12047 enum Time CurrentTime = 0; 12048 enum int RevertToPointerRoot = PointerRoot; 12049 enum int RevertToParent = 2; 12050 12051 int DefaultDepthOfDisplay(Display* dpy) { 12052 return ScreenOfDisplay(dpy, DefaultScreen(dpy)).root_depth; 12053 } 12054 12055 Visual* DefaultVisual(Display *dpy,int scr) { 12056 return ScreenOfDisplay(dpy,scr).root_visual; 12057 } 12058 12059 GC DefaultGC(Display *dpy,int scr) { 12060 return ScreenOfDisplay(dpy,scr).default_gc; 12061 } 12062 12063 uint BlackPixel(Display *dpy,int scr) { 12064 return ScreenOfDisplay(dpy,scr).black_pixel; 12065 } 12066 12067 uint WhitePixel(Display *dpy,int scr) { 12068 return ScreenOfDisplay(dpy,scr).white_pixel; 12069 } 12070 12071 // check out Xft too: http://www.keithp.com/~keithp/render/Xft.tutorial 12072 int XDrawString(Display*, Drawable, GC, int, int, in char*, int); 12073 int XDrawLine(Display*, Drawable, GC, int, int, int, int); 12074 int XDrawRectangle(Display*, Drawable, GC, int, int, uint, uint); 12075 int XDrawArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 12076 int XFillRectangle(Display*, Drawable, GC, int, int, uint, uint); 12077 int XFillArc(Display*, Drawable, GC, int, int, uint, uint, int, int); 12078 int XDrawPoint(Display*, Drawable, GC, int, int); 12079 int XSetForeground(Display*, GC, uint); 12080 int XSetBackground(Display*, GC, uint); 12081 12082 alias void* XFontSet; // i think 12083 XFontSet XCreateFontSet(Display*, const char*, char***, int*, char**); 12084 void XFreeFontSet(Display*, XFontSet); 12085 void Xutf8DrawString(Display*, Drawable, XFontSet, GC, int, int, in char*, int); 12086 void Xutf8DrawText(Display*, Drawable, GC, int, int, XmbTextItem*, int); 12087 struct XmbTextItem { 12088 char* chars; 12089 int nchars; 12090 int delta; 12091 XFontSet font_set; 12092 } 12093 12094 void XDrawText(Display*, Drawable, GC, int, int, XTextItem*, int); 12095 struct XTextItem { 12096 char* chars; 12097 int nchars; 12098 int delta; 12099 Font font; 12100 } 12101 12102 int XSetFunction(Display*, GC, int); 12103 enum { 12104 GXclear = 0x0, /* 0 */ 12105 GXand = 0x1, /* src AND dst */ 12106 GXandReverse = 0x2, /* src AND NOT dst */ 12107 GXcopy = 0x3, /* src */ 12108 GXandInverted = 0x4, /* NOT src AND dst */ 12109 GXnoop = 0x5, /* dst */ 12110 GXxor = 0x6, /* src XOR dst */ 12111 GXor = 0x7, /* src OR dst */ 12112 GXnor = 0x8, /* NOT src AND NOT dst */ 12113 GXequiv = 0x9, /* NOT src XOR dst */ 12114 GXinvert = 0xa, /* NOT dst */ 12115 GXorReverse = 0xb, /* src OR NOT dst */ 12116 GXcopyInverted = 0xc, /* NOT src */ 12117 GXorInverted = 0xd, /* NOT src OR dst */ 12118 GXnand = 0xe, /* NOT src OR NOT dst */ 12119 GXset = 0xf, /* 1 */ 12120 } 12121 12122 GC XCreateGC(Display*, Drawable, uint, void*); 12123 int XCopyGC(Display*, GC, uint, GC); 12124 int XFreeGC(Display*, GC); 12125 12126 bool XCheckWindowEvent(Display*, Window, int, XEvent*); 12127 bool XCheckMaskEvent(Display*, int, XEvent*); 12128 12129 int XPending(Display*); 12130 int XEventsQueued(Display* display, int mode); 12131 enum QueueMode : int { 12132 QueuedAlready, 12133 QueuedAfterReading, 12134 QueuedAfterFlush 12135 } 12136 12137 Pixmap XCreatePixmap(Display*, Drawable, uint, uint, uint); 12138 int XFreePixmap(Display*, Pixmap); 12139 int XCopyArea(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int); 12140 int XFlush(Display*); 12141 int XBell(Display*, int); 12142 int XSync(Display*, bool); 12143 12144 enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } 12145 int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode); 12146 int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window); 12147 KeyCode XKeysymToKeycode (Display* display, KeySym keysym); 12148 12149 struct XPoint { 12150 short x; 12151 short y; 12152 } 12153 12154 int XDrawLines(Display*, Drawable, GC, XPoint*, int, CoordMode); 12155 int XFillPolygon(Display*, Drawable, GC, XPoint*, int, PolygonShape, CoordMode); 12156 12157 enum CoordMode:int { 12158 CoordModeOrigin = 0, 12159 CoordModePrevious = 1 12160 } 12161 12162 enum PolygonShape:int { 12163 Complex = 0, 12164 Nonconvex = 1, 12165 Convex = 2 12166 } 12167 12168 struct XTextProperty { 12169 const(char)* value; /* same as Property routines */ 12170 Atom encoding; /* prop type */ 12171 int format; /* prop data format: 8, 16, or 32 */ 12172 arch_ulong nitems; /* number of data items in value */ 12173 } 12174 12175 version( X86_64 ) { 12176 static assert(XTextProperty.sizeof == 32); 12177 } 12178 12179 12180 struct XGCValues { 12181 int function_; /* logical operation */ 12182 arch_ulong plane_mask;/* plane mask */ 12183 arch_ulong foreground;/* foreground pixel */ 12184 arch_ulong background;/* background pixel */ 12185 int line_width; /* line width */ 12186 int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */ 12187 int cap_style; /* CapNotLast, CapButt, 12188 CapRound, CapProjecting */ 12189 int join_style; /* JoinMiter, JoinRound, JoinBevel */ 12190 int fill_style; /* FillSolid, FillTiled, 12191 FillStippled, FillOpaeueStippled */ 12192 int fill_rule; /* EvenOddRule, WindingRule */ 12193 int arc_mode; /* ArcChord, ArcPieSlice */ 12194 Pixmap tile; /* tile pixmap for tiling operations */ 12195 Pixmap stipple; /* stipple 1 plane pixmap for stipping */ 12196 int ts_x_origin; /* offset for tile or stipple operations */ 12197 int ts_y_origin; 12198 Font font; /* default text font for text operations */ 12199 int subwindow_mode; /* ClipByChildren, IncludeInferiors */ 12200 Bool graphics_exposures;/* boolean, should exposures be generated */ 12201 int clip_x_origin; /* origin for clipping */ 12202 int clip_y_origin; 12203 Pixmap clip_mask; /* bitmap clipping; other calls for rects */ 12204 int dash_offset; /* patterned/dashed line information */ 12205 char dashes; 12206 } 12207 12208 struct XColor { 12209 arch_ulong pixel; 12210 ushort red, green, blue; 12211 byte flags; 12212 byte pad; 12213 } 12214 Status XAllocColor(Display*, Colormap, XColor*); 12215 12216 int XWithdrawWindow(Display*, Window, int); 12217 int XUnmapWindow(Display*, Window); 12218 int XLowerWindow(Display*, Window); 12219 int XRaiseWindow(Display*, Window); 12220 12221 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); 12222 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); 12223 12224 int XGetInputFocus(Display*, Window*, int*); 12225 int XSetInputFocus(Display*, Window, int, Time); 12226 alias XErrorHandler = int function(Display*, XErrorEvent*); 12227 XErrorHandler XSetErrorHandler(XErrorHandler); 12228 12229 int XGetErrorText(Display*, int, char*, int); 12230 12231 Bool XkbSetDetectableAutoRepeat(Display* dpy, Bool detectable, Bool* supported); 12232 12233 12234 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); 12235 int XUngrabPointer(Display *display, Time time); 12236 int XChangeActivePointerGrab(Display *display, uint event_mask, Cursor cursor, Time time); 12237 12238 int XCopyPlane(Display*, Drawable, Drawable, GC, int, int, uint, uint, int, int, arch_ulong); 12239 12240 Status XGetGeometry(Display*, Drawable, Window*, int*, int*, uint*, uint*, uint*, uint*); 12241 int XSetClipMask(Display*, GC, Pixmap); 12242 int XSetClipOrigin(Display*, GC, int, int); 12243 12244 void XSetClipRectangles(Display*, GC, int, int, XRectangle*, int, int); 12245 12246 struct XRectangle { 12247 short x; 12248 short y; 12249 ushort width; 12250 ushort height; 12251 } 12252 12253 void XSetWMName(Display*, Window, XTextProperty*); 12254 Status XGetWMName(Display*, Window, XTextProperty*); 12255 int XStoreName(Display* display, Window w, const(char)* window_name); 12256 12257 enum ClipByChildren = 0; 12258 enum IncludeInferiors = 1; 12259 12260 enum Atom XA_PRIMARY = 1; 12261 enum Atom XA_SECONDARY = 2; 12262 enum Atom XA_STRING = 31; 12263 enum Atom XA_CARDINAL = 6; 12264 enum Atom XA_WM_NAME = 39; 12265 enum Atom XA_ATOM = 4; 12266 enum Atom XA_WINDOW = 33; 12267 enum Atom XA_WM_HINTS = 35; 12268 enum int PropModeAppend = 2; 12269 enum int PropModeReplace = 0; 12270 enum int PropModePrepend = 1; 12271 12272 enum int CopyFromParent = 0; 12273 enum int InputOutput = 1; 12274 12275 // XWMHints 12276 enum InputHint = 1 << 0; 12277 enum StateHint = 1 << 1; 12278 enum IconPixmapHint = (1L << 2); 12279 enum IconWindowHint = (1L << 3); 12280 enum IconPositionHint = (1L << 4); 12281 enum IconMaskHint = (1L << 5); 12282 enum WindowGroupHint = (1L << 6); 12283 enum AllHints = (InputHint|StateHint|IconPixmapHint|IconWindowHint|IconPositionHint|IconMaskHint|WindowGroupHint); 12284 enum XUrgencyHint = (1L << 8); 12285 12286 // GC Components 12287 enum GCFunction = (1L<<0); 12288 enum GCPlaneMask = (1L<<1); 12289 enum GCForeground = (1L<<2); 12290 enum GCBackground = (1L<<3); 12291 enum GCLineWidth = (1L<<4); 12292 enum GCLineStyle = (1L<<5); 12293 enum GCCapStyle = (1L<<6); 12294 enum GCJoinStyle = (1L<<7); 12295 enum GCFillStyle = (1L<<8); 12296 enum GCFillRule = (1L<<9); 12297 enum GCTile = (1L<<10); 12298 enum GCStipple = (1L<<11); 12299 enum GCTileStipXOrigin = (1L<<12); 12300 enum GCTileStipYOrigin = (1L<<13); 12301 enum GCFont = (1L<<14); 12302 enum GCSubwindowMode = (1L<<15); 12303 enum GCGraphicsExposures= (1L<<16); 12304 enum GCClipXOrigin = (1L<<17); 12305 enum GCClipYOrigin = (1L<<18); 12306 enum GCClipMask = (1L<<19); 12307 enum GCDashOffset = (1L<<20); 12308 enum GCDashList = (1L<<21); 12309 enum GCArcMode = (1L<<22); 12310 enum GCLastBit = 22; 12311 12312 12313 enum int WithdrawnState = 0; 12314 enum int NormalState = 1; 12315 enum int IconicState = 3; 12316 12317 } 12318 } else version (OSXCocoa) { 12319 private: 12320 alias void* id; 12321 alias void* Class; 12322 alias void* SEL; 12323 alias void* IMP; 12324 alias void* Ivar; 12325 alias byte BOOL; 12326 alias const(void)* CFStringRef; 12327 alias const(void)* CFAllocatorRef; 12328 alias const(void)* CFTypeRef; 12329 alias const(void)* CGContextRef; 12330 alias const(void)* CGColorSpaceRef; 12331 alias const(void)* CGImageRef; 12332 alias ulong CGBitmapInfo; 12333 12334 struct objc_super { 12335 id self; 12336 Class superclass; 12337 } 12338 12339 struct CFRange { 12340 long location, length; 12341 } 12342 12343 struct NSPoint { 12344 double x, y; 12345 12346 static fromTuple(T)(T tupl) { 12347 return NSPoint(tupl.tupleof); 12348 } 12349 } 12350 struct NSSize { 12351 double width, height; 12352 } 12353 struct NSRect { 12354 NSPoint origin; 12355 NSSize size; 12356 } 12357 alias NSPoint CGPoint; 12358 alias NSSize CGSize; 12359 alias NSRect CGRect; 12360 12361 struct CGAffineTransform { 12362 double a, b, c, d, tx, ty; 12363 } 12364 12365 enum NSApplicationActivationPolicyRegular = 0; 12366 enum NSBackingStoreBuffered = 2; 12367 enum kCFStringEncodingUTF8 = 0x08000100; 12368 12369 enum : size_t { 12370 NSBorderlessWindowMask = 0, 12371 NSTitledWindowMask = 1 << 0, 12372 NSClosableWindowMask = 1 << 1, 12373 NSMiniaturizableWindowMask = 1 << 2, 12374 NSResizableWindowMask = 1 << 3, 12375 NSTexturedBackgroundWindowMask = 1 << 8 12376 } 12377 12378 enum : ulong { 12379 kCGImageAlphaNone, 12380 kCGImageAlphaPremultipliedLast, 12381 kCGImageAlphaPremultipliedFirst, 12382 kCGImageAlphaLast, 12383 kCGImageAlphaFirst, 12384 kCGImageAlphaNoneSkipLast, 12385 kCGImageAlphaNoneSkipFirst 12386 } 12387 enum : ulong { 12388 kCGBitmapAlphaInfoMask = 0x1F, 12389 kCGBitmapFloatComponents = (1 << 8), 12390 kCGBitmapByteOrderMask = 0x7000, 12391 kCGBitmapByteOrderDefault = (0 << 12), 12392 kCGBitmapByteOrder16Little = (1 << 12), 12393 kCGBitmapByteOrder32Little = (2 << 12), 12394 kCGBitmapByteOrder16Big = (3 << 12), 12395 kCGBitmapByteOrder32Big = (4 << 12) 12396 } 12397 enum CGPathDrawingMode { 12398 kCGPathFill, 12399 kCGPathEOFill, 12400 kCGPathStroke, 12401 kCGPathFillStroke, 12402 kCGPathEOFillStroke 12403 } 12404 enum objc_AssociationPolicy : size_t { 12405 OBJC_ASSOCIATION_ASSIGN = 0, 12406 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 12407 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 12408 OBJC_ASSOCIATION_RETAIN = 0x301, //01401, 12409 OBJC_ASSOCIATION_COPY = 0x303 //01403 12410 } 12411 12412 extern(C) { 12413 id objc_msgSend(id receiver, SEL selector, ...); 12414 id objc_msgSendSuper(objc_super* superStruct, SEL selector, ...); 12415 id objc_getClass(const(char)* name); 12416 SEL sel_registerName(const(char)* str); 12417 Class objc_allocateClassPair(Class superclass, const(char)* name, 12418 size_t extra_bytes); 12419 void objc_registerClassPair(Class cls); 12420 BOOL class_addMethod(Class cls, SEL name, IMP imp, const(char)* types); 12421 id objc_getAssociatedObject(id object, void* key); 12422 void objc_setAssociatedObject(id object, void* key, id value, 12423 objc_AssociationPolicy policy); 12424 Ivar class_getInstanceVariable(Class cls, const(char)* name); 12425 id object_getIvar(id object, Ivar ivar); 12426 void object_setIvar(id object, Ivar ivar, id value); 12427 BOOL class_addIvar(Class cls, const(char)* name, 12428 size_t size, ubyte alignment, const(char)* types); 12429 12430 extern __gshared id NSApp; 12431 12432 void CFRelease(CFTypeRef obj); 12433 12434 CFStringRef CFStringCreateWithBytes(CFAllocatorRef allocator, 12435 const(char)* bytes, long numBytes, 12436 long encoding, 12437 BOOL isExternalRepresentation); 12438 long CFStringGetBytes(CFStringRef theString, CFRange range, long encoding, 12439 char lossByte, bool isExternalRepresentation, 12440 char* buffer, long maxBufLen, long* usedBufLen); 12441 long CFStringGetLength(CFStringRef theString); 12442 12443 CGContextRef CGBitmapContextCreate(void* data, 12444 size_t width, size_t height, 12445 size_t bitsPerComponent, 12446 size_t bytesPerRow, 12447 CGColorSpaceRef colorspace, 12448 CGBitmapInfo bitmapInfo); 12449 void CGContextRelease(CGContextRef c); 12450 ubyte* CGBitmapContextGetData(CGContextRef c); 12451 CGImageRef CGBitmapContextCreateImage(CGContextRef c); 12452 size_t CGBitmapContextGetWidth(CGContextRef c); 12453 size_t CGBitmapContextGetHeight(CGContextRef c); 12454 12455 CGColorSpaceRef CGColorSpaceCreateDeviceRGB(); 12456 void CGColorSpaceRelease(CGColorSpaceRef cs); 12457 12458 void CGContextSetRGBStrokeColor(CGContextRef c, 12459 double red, double green, double blue, 12460 double alpha); 12461 void CGContextSetRGBFillColor(CGContextRef c, 12462 double red, double green, double blue, 12463 double alpha); 12464 void CGContextDrawImage(CGContextRef c, CGRect rect, CGImageRef image); 12465 void CGContextShowTextAtPoint(CGContextRef c, double x, double y, 12466 const(char)* str, size_t length); 12467 void CGContextStrokeLineSegments(CGContextRef c, 12468 const(CGPoint)* points, size_t count); 12469 12470 void CGContextBeginPath(CGContextRef c); 12471 void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); 12472 void CGContextAddEllipseInRect(CGContextRef c, CGRect rect); 12473 void CGContextAddArc(CGContextRef c, double x, double y, double radius, 12474 double startAngle, double endAngle, long clockwise); 12475 void CGContextAddRect(CGContextRef c, CGRect rect); 12476 void CGContextAddLines(CGContextRef c, 12477 const(CGPoint)* points, size_t count); 12478 void CGContextSaveGState(CGContextRef c); 12479 void CGContextRestoreGState(CGContextRef c); 12480 void CGContextSelectFont(CGContextRef c, const(char)* name, double size, 12481 ulong textEncoding); 12482 CGAffineTransform CGContextGetTextMatrix(CGContextRef c); 12483 void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t); 12484 12485 void CGImageRelease(CGImageRef image); 12486 } 12487 12488 private: 12489 // A convenient method to create a CFString (=NSString) from a D string. 12490 CFStringRef createCFString(string str) { 12491 return CFStringCreateWithBytes(null, str.ptr, cast(long) str.length, 12492 kCFStringEncodingUTF8, false); 12493 } 12494 12495 // Objective-C calls. 12496 RetType objc_msgSend_specialized(string selector, RetType, T...)(id self, T args) { 12497 auto _cmd = sel_registerName(selector.ptr); 12498 alias extern(C) RetType function(id, SEL, T) ExpectedType; 12499 return (cast(ExpectedType)&objc_msgSend)(self, _cmd, args); 12500 } 12501 RetType objc_msgSend_classMethod(string selector, RetType, T...)(const(char)* className, T args) { 12502 auto _cmd = sel_registerName(selector.ptr); 12503 auto cls = objc_getClass(className); 12504 alias extern(C) RetType function(id, SEL, T) ExpectedType; 12505 return (cast(ExpectedType)&objc_msgSend)(cls, _cmd, args); 12506 } 12507 RetType objc_msgSend_classMethod(string className, string selector, RetType, T...)(T args) { 12508 return objc_msgSend_classMethod!(selector, RetType, T)(className.ptr, args); 12509 } 12510 12511 alias objc_msgSend_specialized!("setNeedsDisplay:", void, BOOL) setNeedsDisplay; 12512 alias objc_msgSend_classMethod!("alloc", id) alloc; 12513 alias objc_msgSend_specialized!("initWithContentRect:styleMask:backing:defer:", 12514 id, NSRect, size_t, size_t, BOOL) initWithContentRect; 12515 alias objc_msgSend_specialized!("setTitle:", void, CFStringRef) setTitle; 12516 alias objc_msgSend_specialized!("center", void) center; 12517 alias objc_msgSend_specialized!("initWithFrame:", id, NSRect) initWithFrame; 12518 alias objc_msgSend_specialized!("setContentView:", void, id) setContentView; 12519 alias objc_msgSend_specialized!("release", void) release; 12520 alias objc_msgSend_classMethod!("NSColor", "whiteColor", id) whiteNSColor; 12521 alias objc_msgSend_specialized!("setBackgroundColor:", void, id) setBackgroundColor; 12522 alias objc_msgSend_specialized!("makeKeyAndOrderFront:", void, id) makeKeyAndOrderFront; 12523 alias objc_msgSend_specialized!("invalidate", void) invalidate; 12524 alias objc_msgSend_specialized!("close", void) close; 12525 alias objc_msgSend_classMethod!("NSTimer", "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:", 12526 id, double, id, SEL, id, BOOL) scheduledTimer; 12527 alias objc_msgSend_specialized!("run", void) run; 12528 alias objc_msgSend_classMethod!("NSGraphicsContext", "currentContext", 12529 id) currentNSGraphicsContext; 12530 alias objc_msgSend_specialized!("graphicsPort", CGContextRef) graphicsPort; 12531 alias objc_msgSend_specialized!("characters", CFStringRef) characters; 12532 alias objc_msgSend_specialized!("superclass", Class) superclass; 12533 alias objc_msgSend_specialized!("init", id) init; 12534 alias objc_msgSend_specialized!("addItem:", void, id) addItem; 12535 alias objc_msgSend_specialized!("setMainMenu:", void, id) setMainMenu; 12536 alias objc_msgSend_specialized!("initWithTitle:action:keyEquivalent:", 12537 id, CFStringRef, SEL, CFStringRef) initWithTitle; 12538 alias objc_msgSend_specialized!("setSubmenu:", void, id) setSubmenu; 12539 alias objc_msgSend_specialized!("setDelegate:", void, id) setDelegate; 12540 alias objc_msgSend_specialized!("activateIgnoringOtherApps:", 12541 void, BOOL) activateIgnoringOtherApps; 12542 alias objc_msgSend_classMethod!("NSApplication", "sharedApplication", 12543 id) sharedNSApplication; 12544 alias objc_msgSend_specialized!("setActivationPolicy:", void, ptrdiff_t) setActivationPolicy; 12545 } else static assert(0, "Unsupported operating system"); 12546 12547 12548 version(OSXCocoa) { 12549 // I don't know anything about the Mac, but a couple years ago, KennyTM on the newsgroup wrote this for me 12550 // 12551 // http://forum.dlang.org/thread/innr0v$1deh$1@digitalmars.com?page=4#post-int88l:24uaf:241:40digitalmars.com 12552 // https://github.com/kennytm/simpledisplay.d/blob/osx/simpledisplay.d 12553 // 12554 // and it is about time I merged it in here. It is available with -version=OSXCocoa until someone tests it for me! 12555 // Probably won't even fully compile right now 12556 12557 import std.math : PI; 12558 import std.algorithm : map; 12559 import std.array : array; 12560 12561 alias SimpleWindow NativeWindowHandle; 12562 alias void delegate(id) NativeEventHandler; 12563 12564 __gshared Ivar simpleWindowIvar; 12565 12566 enum KEY_ESCAPE = 27; 12567 12568 mixin template NativeImageImplementation() { 12569 CGContextRef context; 12570 ubyte* rawData; 12571 final: 12572 12573 void convertToRgbaBytes(ubyte[] where) { 12574 assert(where.length == this.width * this.height * 4); 12575 12576 // if rawData had a length.... 12577 //assert(rawData.length == where.length); 12578 for(long idx = 0; idx < where.length; idx += 4) { 12579 auto alpha = rawData[idx + 3]; 12580 if(alpha == 255) { 12581 where[idx + 0] = rawData[idx + 0]; // r 12582 where[idx + 1] = rawData[idx + 1]; // g 12583 where[idx + 2] = rawData[idx + 2]; // b 12584 where[idx + 3] = rawData[idx + 3]; // a 12585 } else { 12586 where[idx + 0] = cast(ubyte)(rawData[idx + 0] * 255 / alpha); // r 12587 where[idx + 1] = cast(ubyte)(rawData[idx + 1] * 255 / alpha); // g 12588 where[idx + 2] = cast(ubyte)(rawData[idx + 2] * 255 / alpha); // b 12589 where[idx + 3] = rawData[idx + 3]; // a 12590 12591 } 12592 } 12593 } 12594 12595 void setFromRgbaBytes(in ubyte[] where) { 12596 // FIXME: this is probably wrong 12597 assert(where.length == this.width * this.height * 4); 12598 12599 // if rawData had a length.... 12600 //assert(rawData.length == where.length); 12601 for(long idx = 0; idx < where.length; idx += 4) { 12602 auto alpha = rawData[idx + 3]; 12603 if(alpha == 255) { 12604 rawData[idx + 0] = where[idx + 0]; // r 12605 rawData[idx + 1] = where[idx + 1]; // g 12606 rawData[idx + 2] = where[idx + 2]; // b 12607 rawData[idx + 3] = where[idx + 3]; // a 12608 } else { 12609 rawData[idx + 0] = cast(ubyte)(where[idx + 0] * 255 / alpha); // r 12610 rawData[idx + 1] = cast(ubyte)(where[idx + 1] * 255 / alpha); // g 12611 rawData[idx + 2] = cast(ubyte)(where[idx + 2] * 255 / alpha); // b 12612 rawData[idx + 3] = where[idx + 3]; // a 12613 12614 } 12615 } 12616 } 12617 12618 12619 void createImage(int width, int height, bool forcexshm=false) { 12620 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 12621 context = CGBitmapContextCreate(null, width, height, 8, 4*width, 12622 colorSpace, 12623 kCGImageAlphaPremultipliedLast 12624 |kCGBitmapByteOrder32Big); 12625 CGColorSpaceRelease(colorSpace); 12626 rawData = CGBitmapContextGetData(context); 12627 } 12628 void dispose() { 12629 CGContextRelease(context); 12630 } 12631 12632 void setPixel(int x, int y, Color c) { 12633 auto offset = (y * width + x) * 4; 12634 if (c.a == 255) { 12635 rawData[offset + 0] = c.r; 12636 rawData[offset + 1] = c.g; 12637 rawData[offset + 2] = c.b; 12638 rawData[offset + 3] = c.a; 12639 } else { 12640 rawData[offset + 0] = cast(ubyte)(c.r*c.a/255); 12641 rawData[offset + 1] = cast(ubyte)(c.g*c.a/255); 12642 rawData[offset + 2] = cast(ubyte)(c.b*c.a/255); 12643 rawData[offset + 3] = c.a; 12644 } 12645 } 12646 } 12647 12648 mixin template NativeScreenPainterImplementation() { 12649 CGContextRef context; 12650 ubyte[4] _outlineComponents; 12651 id view; 12652 12653 void create(NativeWindowHandle window) { 12654 context = window.drawingContext; 12655 view = window.view; 12656 } 12657 12658 void dispose() { 12659 setNeedsDisplay(view, true); 12660 } 12661 12662 // NotYetImplementedException 12663 Size textSize(in char[] txt) { return Size(32, 16); throw new NotYetImplementedException(); } 12664 void rasterOp(RasterOp op) {} 12665 Pen _activePen; 12666 Color _fillColor; 12667 Rectangle _clipRectangle; 12668 void setClipRectangle(int, int, int, int) {} 12669 void setFont(OperatingSystemFont) {} 12670 int fontHeight() { return 14; } 12671 12672 // end 12673 12674 void pen(Pen pen) { 12675 _activePen = pen; 12676 auto color = pen.color; // FIXME 12677 double alphaComponent = color.a/255.0f; 12678 CGContextSetRGBStrokeColor(context, 12679 color.r/255.0f, color.g/255.0f, color.b/255.0f, alphaComponent); 12680 12681 if (color.a != 255) { 12682 _outlineComponents[0] = cast(ubyte)(color.r*color.a/255); 12683 _outlineComponents[1] = cast(ubyte)(color.g*color.a/255); 12684 _outlineComponents[2] = cast(ubyte)(color.b*color.a/255); 12685 _outlineComponents[3] = color.a; 12686 } else { 12687 _outlineComponents[0] = color.r; 12688 _outlineComponents[1] = color.g; 12689 _outlineComponents[2] = color.b; 12690 _outlineComponents[3] = color.a; 12691 } 12692 } 12693 12694 @property void fillColor(Color color) { 12695 CGContextSetRGBFillColor(context, 12696 color.r/255.0f, color.g/255.0f, color.b/255.0f, color.a/255.0f); 12697 } 12698 12699 void drawImage(int x, int y, Image image, int ulx, int upy, int width, int height) { 12700 // NotYetImplementedException for upper left/width/height 12701 auto cgImage = CGBitmapContextCreateImage(image.context); 12702 auto size = CGSize(CGBitmapContextGetWidth(image.context), 12703 CGBitmapContextGetHeight(image.context)); 12704 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 12705 CGImageRelease(cgImage); 12706 } 12707 12708 version(OSXCocoa) {} else // NotYetImplementedException 12709 void drawPixmap(Sprite image, int x, int y) { 12710 // FIXME: is this efficient? 12711 auto cgImage = CGBitmapContextCreateImage(image.context); 12712 auto size = CGSize(CGBitmapContextGetWidth(image.context), 12713 CGBitmapContextGetHeight(image.context)); 12714 CGContextDrawImage(context, CGRect(CGPoint(x, y), size), cgImage); 12715 CGImageRelease(cgImage); 12716 } 12717 12718 12719 void drawText(int x, int y, int x2, int y2, in char[] text, uint alignment) { 12720 // FIXME: alignment 12721 if (_outlineComponents[3] != 0) { 12722 CGContextSaveGState(context); 12723 auto invAlpha = 1.0f/_outlineComponents[3]; 12724 CGContextSetRGBFillColor(context, _outlineComponents[0]*invAlpha, 12725 _outlineComponents[1]*invAlpha, 12726 _outlineComponents[2]*invAlpha, 12727 _outlineComponents[3]/255.0f); 12728 CGContextShowTextAtPoint(context, x, y + 12 /* this is cuz this picks baseline but i want bounding box */, text.ptr, text.length); 12729 // auto cfstr = cast(id)createCFString(text); 12730 // objc_msgSend(cfstr, sel_registerName("drawAtPoint:withAttributes:"), 12731 // NSPoint(x, y), null); 12732 // CFRelease(cfstr); 12733 CGContextRestoreGState(context); 12734 } 12735 } 12736 12737 void drawPixel(int x, int y) { 12738 auto rawData = CGBitmapContextGetData(context); 12739 auto width = CGBitmapContextGetWidth(context); 12740 auto height = CGBitmapContextGetHeight(context); 12741 auto offset = ((height - y - 1) * width + x) * 4; 12742 rawData[offset .. offset+4] = _outlineComponents; 12743 } 12744 12745 void drawLine(int x1, int y1, int x2, int y2) { 12746 CGPoint[2] linePoints; 12747 linePoints[0] = CGPoint(x1, y1); 12748 linePoints[1] = CGPoint(x2, y2); 12749 CGContextStrokeLineSegments(context, linePoints.ptr, linePoints.length); 12750 } 12751 12752 void drawRectangle(int x, int y, int width, int height) { 12753 CGContextBeginPath(context); 12754 auto rect = CGRect(CGPoint(x, y), CGSize(width, height)); 12755 CGContextAddRect(context, rect); 12756 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 12757 } 12758 12759 void drawEllipse(int x1, int y1, int x2, int y2) { 12760 CGContextBeginPath(context); 12761 auto rect = CGRect(CGPoint(x1, y1), CGSize(x2-x1, y2-y1)); 12762 CGContextAddEllipseInRect(context, rect); 12763 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 12764 } 12765 12766 void drawArc(int x1, int y1, int width, int height, int start, int finish) { 12767 // @@@BUG@@@ Does not support elliptic arc (width != height). 12768 CGContextBeginPath(context); 12769 CGContextAddArc(context, x1+width*0.5f, y1+height*0.5f, width, 12770 start*PI/(180*64), finish*PI/(180*64), 0); 12771 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 12772 } 12773 12774 void drawPolygon(Point[] intPoints) { 12775 CGContextBeginPath(context); 12776 auto points = array(map!(CGPoint.fromTuple)(intPoints)); 12777 CGContextAddLines(context, points.ptr, points.length); 12778 CGContextDrawPath(context, CGPathDrawingMode.kCGPathFillStroke); 12779 } 12780 } 12781 12782 mixin template NativeSimpleWindowImplementation() { 12783 void createWindow(int width, int height, string title, OpenGlOptions opengl, SimpleWindow parent) { 12784 synchronized { 12785 if (NSApp == null) initializeApp(); 12786 } 12787 12788 auto contentRect = NSRect(NSPoint(0, 0), NSSize(width, height)); 12789 12790 // create the window. 12791 window = initWithContentRect(alloc("NSWindow"), 12792 contentRect, 12793 NSTitledWindowMask 12794 |NSClosableWindowMask 12795 |NSMiniaturizableWindowMask 12796 |NSResizableWindowMask, 12797 NSBackingStoreBuffered, 12798 true); 12799 12800 // set the title & move the window to center. 12801 auto windowTitle = createCFString(title); 12802 setTitle(window, windowTitle); 12803 CFRelease(windowTitle); 12804 center(window); 12805 12806 // create area to draw on. 12807 auto colorSpace = CGColorSpaceCreateDeviceRGB(); 12808 drawingContext = CGBitmapContextCreate(null, width, height, 12809 8, 4*width, colorSpace, 12810 kCGImageAlphaPremultipliedLast 12811 |kCGBitmapByteOrder32Big); 12812 CGColorSpaceRelease(colorSpace); 12813 CGContextSelectFont(drawingContext, "Lucida Grande", 12.0f, 1); 12814 auto matrix = CGContextGetTextMatrix(drawingContext); 12815 matrix.c = -matrix.c; 12816 matrix.d = -matrix.d; 12817 CGContextSetTextMatrix(drawingContext, matrix); 12818 12819 // create the subview that things will be drawn on. 12820 view = initWithFrame(alloc("SDGraphicsView"), contentRect); 12821 setContentView(window, view); 12822 object_setIvar(view, simpleWindowIvar, cast(id)this); 12823 release(view); 12824 12825 setBackgroundColor(window, whiteNSColor); 12826 makeKeyAndOrderFront(window, null); 12827 } 12828 void dispose() { 12829 closeWindow(); 12830 release(window); 12831 } 12832 void closeWindow() { 12833 invalidate(timer); 12834 .close(window); 12835 } 12836 12837 ScreenPainter getPainter() { 12838 return ScreenPainter(this, this); 12839 } 12840 12841 id window; 12842 id timer; 12843 id view; 12844 CGContextRef drawingContext; 12845 } 12846 12847 extern(C) { 12848 private: 12849 BOOL returnTrue3(id self, SEL _cmd, id app) { 12850 return true; 12851 } 12852 BOOL returnTrue2(id self, SEL _cmd) { 12853 return true; 12854 } 12855 12856 void pulse(id self, SEL _cmd) { 12857 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 12858 simpleWindow.handlePulse(); 12859 setNeedsDisplay(self, true); 12860 } 12861 void drawRect(id self, SEL _cmd, NSRect rect) { 12862 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 12863 auto curCtx = graphicsPort(currentNSGraphicsContext); 12864 auto cgImage = CGBitmapContextCreateImage(simpleWindow.drawingContext); 12865 auto size = CGSize(CGBitmapContextGetWidth(simpleWindow.drawingContext), 12866 CGBitmapContextGetHeight(simpleWindow.drawingContext)); 12867 CGContextDrawImage(curCtx, CGRect(CGPoint(0, 0), size), cgImage); 12868 CGImageRelease(cgImage); 12869 } 12870 void keyDown(id self, SEL _cmd, id event) { 12871 auto simpleWindow = cast(SimpleWindow)object_getIvar(self, simpleWindowIvar); 12872 12873 // the event may have multiple characters, and we send them all at 12874 // once. 12875 if (simpleWindow.handleCharEvent || simpleWindow.handleKeyEvent) { 12876 auto chars = characters(event); 12877 auto range = CFRange(0, CFStringGetLength(chars)); 12878 auto buffer = new char[range.length*3]; 12879 long actualLength; 12880 CFStringGetBytes(chars, range, kCFStringEncodingUTF8, 0, false, 12881 buffer.ptr, cast(int) buffer.length, &actualLength); 12882 foreach (dchar dc; buffer[0..actualLength]) { 12883 if (simpleWindow.handleCharEvent) 12884 simpleWindow.handleCharEvent(dc); 12885 // NotYetImplementedException 12886 //if (simpleWindow.handleKeyEvent) 12887 //simpleWindow.handleKeyEvent(KeyEvent(dc)); // FIXME: what about keyUp? 12888 } 12889 } 12890 12891 // the event's 'keyCode' is hardware-dependent. I don't think people 12892 // will like it. Let's leave it to the native handler. 12893 12894 // perform the default action. 12895 12896 // so the default action is to make a bomp sound and i dont want that 12897 // sooooooooo yeah not gonna do that. 12898 12899 //auto superData = objc_super(self, superclass(self)); 12900 //alias extern(C) void function(objc_super*, SEL, id) T; 12901 //(cast(T)&objc_msgSendSuper)(&superData, _cmd, event); 12902 } 12903 } 12904 12905 // initialize the app so that it can be interacted with the user. 12906 // based on http://cocoawithlove.com/2010/09/minimalist-cocoa-programming.html 12907 private void initializeApp() { 12908 // push an autorelease pool to avoid leaking. 12909 init(alloc("NSAutoreleasePool")); 12910 12911 // create a new NSApp instance 12912 sharedNSApplication; 12913 setActivationPolicy(NSApp, NSApplicationActivationPolicyRegular); 12914 12915 // create the "Quit" menu. 12916 auto menuBar = init(alloc("NSMenu")); 12917 auto appMenuItem = init(alloc("NSMenuItem")); 12918 addItem(menuBar, appMenuItem); 12919 setMainMenu(NSApp, menuBar); 12920 release(appMenuItem); 12921 release(menuBar); 12922 12923 auto appMenu = init(alloc("NSMenu")); 12924 auto quitTitle = createCFString("Quit"); 12925 auto q = createCFString("q"); 12926 auto quitItem = initWithTitle(alloc("NSMenuItem"), 12927 quitTitle, sel_registerName("terminate:"), q); 12928 addItem(appMenu, quitItem); 12929 setSubmenu(appMenuItem, appMenu); 12930 release(quitItem); 12931 release(appMenu); 12932 CFRelease(q); 12933 CFRelease(quitTitle); 12934 12935 // assign a delegate for the application, allow it to quit when the last 12936 // window is closed. 12937 auto delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), 12938 "SDWindowCloseDelegate", 0); 12939 class_addMethod(delegateClass, 12940 sel_registerName("applicationShouldTerminateAfterLastWindowClosed:"), 12941 &returnTrue3, "c@:@"); 12942 objc_registerClassPair(delegateClass); 12943 12944 auto appDelegate = init(alloc("SDWindowCloseDelegate")); 12945 setDelegate(NSApp, appDelegate); 12946 activateIgnoringOtherApps(NSApp, true); 12947 12948 // create a new view that draws the graphics and respond to keyDown 12949 // events. 12950 auto viewClass = objc_allocateClassPair(objc_getClass("NSView"), 12951 "SDGraphicsView", (void*).sizeof); 12952 class_addIvar(viewClass, "simpledisplay_simpleWindow", 12953 (void*).sizeof, (void*).alignof, "^v"); 12954 class_addMethod(viewClass, sel_registerName("simpledisplay_pulse"), 12955 &pulse, "v@:"); 12956 class_addMethod(viewClass, sel_registerName("drawRect:"), 12957 &drawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); 12958 class_addMethod(viewClass, sel_registerName("isFlipped"), 12959 &returnTrue2, "c@:"); 12960 class_addMethod(viewClass, sel_registerName("acceptsFirstResponder"), 12961 &returnTrue2, "c@:"); 12962 class_addMethod(viewClass, sel_registerName("keyDown:"), 12963 &keyDown, "v@:@"); 12964 objc_registerClassPair(viewClass); 12965 simpleWindowIvar = class_getInstanceVariable(viewClass, 12966 "simpledisplay_simpleWindow"); 12967 } 12968 } 12969 12970 version(without_opengl) {} else 12971 extern(System) nothrow @nogc { 12972 //enum uint GL_VERSION = 0x1F02; 12973 //const(char)* glGetString (/*GLenum*/uint); 12974 version(X11) { 12975 static if (!SdpyIsUsingIVGLBinds) { 12976 struct __GLXFBConfigRec {} 12977 alias GLXFBConfig = __GLXFBConfigRec*; 12978 12979 enum GLX_X_RENDERABLE = 0x8012; 12980 enum GLX_DRAWABLE_TYPE = 0x8010; 12981 enum GLX_RENDER_TYPE = 0x8011; 12982 enum GLX_X_VISUAL_TYPE = 0x22; 12983 enum GLX_TRUE_COLOR = 0x8002; 12984 enum GLX_WINDOW_BIT = 0x00000001; 12985 enum GLX_RGBA_BIT = 0x00000001; 12986 enum GLX_COLOR_INDEX_BIT = 0x00000002; 12987 enum GLX_SAMPLE_BUFFERS = 0x186a0; 12988 enum GLX_SAMPLES = 0x186a1; 12989 enum GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 12990 enum GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; 12991 12992 GLXFBConfig* glXChooseFBConfig (Display*, int, int*, int*); 12993 int glXGetFBConfigAttrib (Display*, GLXFBConfig, int, int*); 12994 XVisualInfo* glXGetVisualFromFBConfig (Display*, GLXFBConfig); 12995 12996 char* glXQueryExtensionsString (Display*, int); 12997 void* glXGetProcAddress (const(char)*); 12998 12999 alias glbindGetProcAddress = glXGetProcAddress; 13000 } 13001 13002 // GLX_EXT_swap_control 13003 alias glXSwapIntervalEXT = void function (Display* dpy, /*GLXDrawable*/Drawable drawable, int interval); 13004 private __gshared glXSwapIntervalEXT _glx_swapInterval_fn = null; 13005 13006 //k8: ugly code to prevent warnings when sdpy is compiled into .a 13007 extern(System) { 13008 alias glXCreateContextAttribsARB_fna = GLXContext function (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list); 13009 } 13010 private __gshared /*glXCreateContextAttribsARB_fna*/void* glXCreateContextAttribsARBFn = cast(void*)1; //HACK! 13011 13012 // this made public so we don't have to get it again and again 13013 public bool glXCreateContextAttribsARB_present () { 13014 if (glXCreateContextAttribsARBFn is cast(void*)1) { 13015 // get it 13016 glXCreateContextAttribsARBFn = cast(void*)glbindGetProcAddress("glXCreateContextAttribsARB"); 13017 //{ import core.stdc.stdio; printf("checking glXCreateContextAttribsARB: %shere\n", (glXCreateContextAttribsARBFn !is null ? "".ptr : "not ".ptr)); } 13018 } 13019 return (glXCreateContextAttribsARBFn !is null); 13020 } 13021 13022 // this made public so we don't have to get it again and again 13023 public GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, /*Bool*/int direct, const(int)* attrib_list) { 13024 if (!glXCreateContextAttribsARB_present()) assert(0, "glXCreateContextAttribsARB is not present"); 13025 return (cast(glXCreateContextAttribsARB_fna)glXCreateContextAttribsARBFn)(dpy, config, share_context, direct, attrib_list); 13026 } 13027 13028 void glxSetVSync (Display* dpy, /*GLXDrawable*/Drawable drawable, bool wait) { 13029 if (cast(void*)_glx_swapInterval_fn is cast(void*)1) return; 13030 if (_glx_swapInterval_fn is null) { 13031 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)glXGetProcAddress("glXSwapIntervalEXT"); 13032 if (_glx_swapInterval_fn is null) { 13033 _glx_swapInterval_fn = cast(glXSwapIntervalEXT)1; 13034 return; 13035 } 13036 version(sdddd) { import std.stdio; writeln("glXSwapIntervalEXT found!"); } 13037 } 13038 _glx_swapInterval_fn(dpy, drawable, (wait ? 1 : 0)); 13039 } 13040 } else version(Windows) { 13041 static if (!SdpyIsUsingIVGLBinds) { 13042 enum GL_TRUE = 1; 13043 enum GL_FALSE = 0; 13044 alias int GLint; 13045 13046 public void* glbindGetProcAddress (const(char)* name) { 13047 void* res = wglGetProcAddress(name); 13048 if (res is null) { 13049 //{ import core.stdc.stdio; printf("GL: '%s' not found (0)\n", name); } 13050 import core.sys.windows.windef, core.sys.windows.winbase; 13051 __gshared HINSTANCE dll = null; 13052 if (dll is null) { 13053 dll = LoadLibraryA("opengl32.dll"); 13054 if (dll is null) return null; // <32, but idc 13055 } 13056 res = GetProcAddress(dll, name); 13057 } 13058 //{ import core.stdc.stdio; printf(" GL: '%s' is 0x%08x\n", name, cast(uint)res); } 13059 return res; 13060 } 13061 } 13062 13063 enum WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 13064 enum WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 13065 enum WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 13066 enum WGL_CONTEXT_FLAGS_ARB = 0x2094; 13067 enum WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 13068 13069 enum WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 13070 enum WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 13071 13072 enum WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 13073 enum WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 13074 13075 alias wglCreateContextAttribsARB_fna = HGLRC function (HDC hDC, HGLRC hShareContext, const(int)* attribList); 13076 __gshared wglCreateContextAttribsARB_fna wglCreateContextAttribsARB = null; 13077 13078 void wglInitOtherFunctions () { 13079 if (wglCreateContextAttribsARB is null) { 13080 wglCreateContextAttribsARB = cast(wglCreateContextAttribsARB_fna)glbindGetProcAddress("wglCreateContextAttribsARB"); 13081 } 13082 } 13083 } 13084 13085 static if (!SdpyIsUsingIVGLBinds) { 13086 void glGetIntegerv(int, void*); 13087 void glMatrixMode(int); 13088 void glPushMatrix(); 13089 void glLoadIdentity(); 13090 void glOrtho(double, double, double, double, double, double); 13091 void glFrustum(double, double, double, double, double, double); 13092 13093 void gluLookAt(double, double, double, double, double, double, double, double, double); 13094 void gluPerspective(double, double, double, double); 13095 13096 void glPopMatrix(); 13097 void glEnable(int); 13098 void glDisable(int); 13099 void glClear(int); 13100 void glBegin(int); 13101 void glVertex2f(float, float); 13102 void glVertex3f(float, float, float); 13103 void glEnd(); 13104 void glColor3b(byte, byte, byte); 13105 void glColor3ub(ubyte, ubyte, ubyte); 13106 void glColor4b(byte, byte, byte, byte); 13107 void glColor4ub(ubyte, ubyte, ubyte, ubyte); 13108 void glColor3i(int, int, int); 13109 void glColor3ui(uint, uint, uint); 13110 void glColor4i(int, int, int, int); 13111 void glColor4ui(uint, uint, uint, uint); 13112 void glColor3f(float, float, float); 13113 void glColor4f(float, float, float, float); 13114 void glTranslatef(float, float, float); 13115 void glScalef(float, float, float); 13116 void glSecondaryColor3b(byte, byte, byte); 13117 void glSecondaryColor3ub(ubyte, ubyte, ubyte); 13118 void glSecondaryColor3i(int, int, int); 13119 void glSecondaryColor3ui(uint, uint, uint); 13120 void glSecondaryColor3f(float, float, float); 13121 13122 void glDrawElements(int, int, int, void*); 13123 13124 void glRotatef(float, float, float, float); 13125 13126 uint glGetError(); 13127 13128 void glDeleteTextures(int, uint*); 13129 13130 char* gluErrorString(uint); 13131 13132 void glRasterPos2i(int, int); 13133 void glDrawPixels(int, int, uint, uint, void*); 13134 void glClearColor(float, float, float, float); 13135 13136 13137 13138 void glGenTextures(uint, uint*); 13139 void glBindTexture(int, int); 13140 void glTexParameteri(uint, uint, int); 13141 void glTexParameterf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 13142 void glTexImage2D(int, int, int, int, int, int, int, int, in void*); 13143 void glTexSubImage2D(uint/*GLenum*/ target, int level, int xoffset, int yoffset, 13144 /*GLsizei*/int width, /*GLsizei*/int height, 13145 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 13146 void glTextureSubImage2D(uint texture, int level, int xoffset, int yoffset, 13147 /*GLsizei*/int width, /*GLsizei*/int height, 13148 uint/*GLenum*/ format, uint/*GLenum*/ type, in void* pixels); 13149 void glTexEnvf(uint/*GLenum*/ target, uint/*GLenum*/ pname, float param); 13150 13151 13152 void glTexCoord2f(float, float); 13153 void glVertex2i(int, int); 13154 void glBlendFunc (int, int); 13155 void glDepthFunc (int); 13156 void glViewport(int, int, int, int); 13157 13158 void glClearDepth(double); 13159 13160 void glReadBuffer(uint); 13161 void glReadPixels(int, int, int, int, int, int, void*); 13162 13163 void glFlush(); 13164 void glFinish(); 13165 13166 enum uint GL_FRONT = 0x0404; 13167 13168 enum uint GL_BLEND = 0x0be2; 13169 enum uint GL_SRC_ALPHA = 0x0302; 13170 enum uint GL_ONE_MINUS_SRC_ALPHA = 0x0303; 13171 enum uint GL_LEQUAL = 0x0203; 13172 13173 13174 enum uint GL_UNSIGNED_BYTE = 0x1401; 13175 enum uint GL_RGB = 0x1907; 13176 enum uint GL_BGRA = 0x80e1; 13177 enum uint GL_RGBA = 0x1908; 13178 enum uint GL_TEXTURE_2D = 0x0DE1; 13179 enum uint GL_TEXTURE_MIN_FILTER = 0x2801; 13180 enum uint GL_NEAREST = 0x2600; 13181 enum uint GL_LINEAR = 0x2601; 13182 enum uint GL_TEXTURE_MAG_FILTER = 0x2800; 13183 enum uint GL_TEXTURE_WRAP_S = 0x2802; 13184 enum uint GL_TEXTURE_WRAP_T = 0x2803; 13185 enum uint GL_REPEAT = 0x2901; 13186 enum uint GL_CLAMP = 0x2900; 13187 enum uint GL_CLAMP_TO_EDGE = 0x812F; 13188 enum uint GL_CLAMP_TO_BORDER = 0x812D; 13189 enum uint GL_DECAL = 0x2101; 13190 enum uint GL_MODULATE = 0x2100; 13191 enum uint GL_TEXTURE_ENV = 0x2300; 13192 enum uint GL_TEXTURE_ENV_MODE = 0x2200; 13193 enum uint GL_REPLACE = 0x1E01; 13194 enum uint GL_LIGHTING = 0x0B50; 13195 enum uint GL_DITHER = 0x0BD0; 13196 13197 enum uint GL_NO_ERROR = 0; 13198 13199 13200 13201 enum int GL_VIEWPORT = 0x0BA2; 13202 enum int GL_MODELVIEW = 0x1700; 13203 enum int GL_TEXTURE = 0x1702; 13204 enum int GL_PROJECTION = 0x1701; 13205 enum int GL_DEPTH_TEST = 0x0B71; 13206 13207 enum int GL_COLOR_BUFFER_BIT = 0x00004000; 13208 enum int GL_ACCUM_BUFFER_BIT = 0x00000200; 13209 enum int GL_DEPTH_BUFFER_BIT = 0x00000100; 13210 enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 13211 13212 enum int GL_POINTS = 0x0000; 13213 enum int GL_LINES = 0x0001; 13214 enum int GL_LINE_LOOP = 0x0002; 13215 enum int GL_LINE_STRIP = 0x0003; 13216 enum int GL_TRIANGLES = 0x0004; 13217 enum int GL_TRIANGLE_STRIP = 5; 13218 enum int GL_TRIANGLE_FAN = 6; 13219 enum int GL_QUADS = 7; 13220 enum int GL_QUAD_STRIP = 8; 13221 enum int GL_POLYGON = 9; 13222 } 13223 } 13224 13225 version(linux) { 13226 version(with_eventloop) {} else { 13227 private int epollFd = -1; 13228 void prepareEventLoop() { 13229 if(epollFd != -1) 13230 return; // already initialized, no need to do it again 13231 import ep = core.sys.linux.epoll; 13232 13233 epollFd = ep.epoll_create1(ep.EPOLL_CLOEXEC); 13234 if(epollFd == -1) 13235 throw new Exception("epoll create failure"); 13236 } 13237 } 13238 13239 } 13240 13241 version(X11) { 13242 import core.stdc.locale : LC_ALL; // rdmd fix 13243 __gshared bool sdx_isUTF8Locale; 13244 13245 // This whole crap is used to initialize X11 locale, so that you can use XIM methods later. 13246 // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will 13247 // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" 13248 // anal magic is here. I (Ketmar) hope you like it. 13249 // We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will 13250 // always return correct unicode symbols. The detection is here 'cause user can change locale 13251 // later. 13252 shared static this () { 13253 import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE; 13254 13255 // this doesn't hurt; it may add some locking, but the speed is still 13256 // allows doing 60 FPS videogames; also, ignore the result, as most 13257 // users will probably won't do mulththreaded X11 anyway (and I (ketmar) 13258 // never seen this failing). 13259 if (XInitThreads() == 0) { import core.stdc.stdio; fprintf(stderr, "XInitThreads() failed!\n"); } 13260 13261 setlocale(LC_ALL, ""); 13262 // check if out locale is UTF-8 13263 auto lct = setlocale(LC_CTYPE, null); 13264 if (lct is null) { 13265 sdx_isUTF8Locale = false; 13266 } else { 13267 for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { 13268 if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && 13269 (lct[idx+1] == 't' || lct[idx+1] == 'T') && 13270 (lct[idx+2] == 'f' || lct[idx+2] == 'F')) 13271 { 13272 sdx_isUTF8Locale = true; 13273 break; 13274 } 13275 } 13276 } 13277 //{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); } 13278 } 13279 } 13280 13281 mixin template ExperimentalTextComponent2() { 13282 /+ 13283 Stage 1: get it working monospace 13284 Stage 2: use proportional font 13285 Stage 3: allow changes in inline style 13286 Stage 4: allow new fonts and sizes in the middle 13287 Stage 5: optimize gap buffer 13288 Stage 6: optimize layout 13289 Stage 7: word wrap 13290 Stage 8: justification 13291 Stage 9: editing, selection, etc. 13292 +/ 13293 } 13294 13295 13296 // Don't use this yet. When I'm happy with it, I will move it to the 13297 // regular module namespace. 13298 mixin template ExperimentalTextComponent() { 13299 13300 alias Rectangle = arsd.color.Rectangle; 13301 13302 struct ForegroundColor { 13303 Color color; 13304 alias color this; 13305 13306 this(Color c) { 13307 color = c; 13308 } 13309 13310 this(int r, int g, int b, int a = 255) { 13311 color = Color(r, g, b, a); 13312 } 13313 13314 static ForegroundColor opDispatch(string s)() if(__traits(compiles, ForegroundColor(mixin("Color." ~ s)))) { 13315 return ForegroundColor(mixin("Color." ~ s)); 13316 } 13317 } 13318 13319 struct BackgroundColor { 13320 Color color; 13321 alias color this; 13322 13323 this(Color c) { 13324 color = c; 13325 } 13326 13327 this(int r, int g, int b, int a = 255) { 13328 color = Color(r, g, b, a); 13329 } 13330 13331 static BackgroundColor opDispatch(string s)() if(__traits(compiles, BackgroundColor(mixin("Color." ~ s)))) { 13332 return BackgroundColor(mixin("Color." ~ s)); 13333 } 13334 } 13335 13336 static class InlineElement { 13337 string text; 13338 13339 BlockElement containingBlock; 13340 13341 Color color = Color.black; 13342 Color backgroundColor = Color.transparent; 13343 ushort styles; 13344 13345 string font; 13346 int fontSize; 13347 13348 int lineHeight; 13349 13350 void* identifier; 13351 13352 Rectangle boundingBox; 13353 int[] letterXs; // FIXME: maybe i should do bounding boxes for every character 13354 13355 bool isMergeCompatible(InlineElement other) { 13356 return 13357 containingBlock is other.containingBlock && 13358 color == other.color && 13359 backgroundColor == other.backgroundColor && 13360 styles == other.styles && 13361 font == other.font && 13362 fontSize == other.fontSize && 13363 lineHeight == other.lineHeight && 13364 true; 13365 } 13366 13367 int xOfIndex(size_t index) { 13368 if(index < letterXs.length) 13369 return letterXs[index]; 13370 else 13371 return boundingBox.right; 13372 } 13373 13374 InlineElement clone() { 13375 auto ie = new InlineElement(); 13376 ie.tupleof = this.tupleof; 13377 return ie; 13378 } 13379 13380 InlineElement getPreviousInlineElement() { 13381 InlineElement prev = null; 13382 foreach(ie; this.containingBlock.parts) { 13383 if(ie is this) 13384 break; 13385 prev = ie; 13386 } 13387 if(prev is null) { 13388 BlockElement pb; 13389 BlockElement cb = this.containingBlock; 13390 moar: 13391 foreach(ie; this.containingBlock.containingLayout.blocks) { 13392 if(ie is cb) 13393 break; 13394 pb = ie; 13395 } 13396 if(pb is null) 13397 return null; 13398 if(pb.parts.length == 0) { 13399 cb = pb; 13400 goto moar; 13401 } 13402 13403 prev = pb.parts[$-1]; 13404 13405 } 13406 return prev; 13407 } 13408 13409 InlineElement getNextInlineElement() { 13410 InlineElement next = null; 13411 foreach(idx, ie; this.containingBlock.parts) { 13412 if(ie is this) { 13413 if(idx + 1 < this.containingBlock.parts.length) 13414 next = this.containingBlock.parts[idx + 1]; 13415 break; 13416 } 13417 } 13418 if(next is null) { 13419 BlockElement n; 13420 foreach(idx, ie; this.containingBlock.containingLayout.blocks) { 13421 if(ie is this.containingBlock) { 13422 if(idx + 1 < this.containingBlock.containingLayout.blocks.length) 13423 n = this.containingBlock.containingLayout.blocks[idx + 1]; 13424 break; 13425 } 13426 } 13427 if(n is null) 13428 return null; 13429 13430 if(n.parts.length) 13431 next = n.parts[0]; 13432 else {} // FIXME 13433 13434 } 13435 return next; 13436 } 13437 13438 } 13439 13440 // Block elements are used entirely for positioning inline elements, 13441 // which are the things that are actually drawn. 13442 class BlockElement { 13443 InlineElement[] parts; 13444 uint alignment; 13445 13446 int whiteSpace; // pre, pre-wrap, wrap 13447 13448 TextLayout containingLayout; 13449 13450 // inputs 13451 Point where; 13452 Size minimumSize; 13453 Size maximumSize; 13454 Rectangle[] excludedBoxes; // like if you want it to write around a floated image or something. Coordinates are relative to the bounding box. 13455 void* identifier; 13456 13457 Rectangle margin; 13458 Rectangle padding; 13459 13460 // outputs 13461 Rectangle[] boundingBoxes; 13462 } 13463 13464 struct TextIdentifyResult { 13465 InlineElement element; 13466 int offset; 13467 13468 private TextIdentifyResult fixupNewline() { 13469 if(element !is null && offset < element.text.length && element.text[offset] == '\n') { 13470 offset--; 13471 } else if(element !is null && offset == element.text.length && element.text.length > 1 && element.text[$-1] == '\n') { 13472 offset--; 13473 } 13474 return this; 13475 } 13476 } 13477 13478 class TextLayout { 13479 BlockElement[] blocks; 13480 Rectangle boundingBox_; 13481 Rectangle boundingBox() { return boundingBox_; } 13482 void boundingBox(Rectangle r) { 13483 if(r != boundingBox_) { 13484 boundingBox_ = r; 13485 layoutInvalidated = true; 13486 } 13487 } 13488 13489 Rectangle contentBoundingBox() { 13490 Rectangle r; 13491 foreach(block; blocks) 13492 foreach(ie; block.parts) { 13493 if(ie.boundingBox.right > r.right) 13494 r.right = ie.boundingBox.right; 13495 if(ie.boundingBox.bottom > r.bottom) 13496 r.bottom = ie.boundingBox.bottom; 13497 } 13498 return r; 13499 } 13500 13501 BlockElement[] getBlocks() { 13502 return blocks; 13503 } 13504 13505 InlineElement[] getTexts() { 13506 InlineElement[] elements; 13507 foreach(block; blocks) 13508 elements ~= block.parts; 13509 return elements; 13510 } 13511 13512 string getPlainText() { 13513 string text; 13514 foreach(block; blocks) 13515 foreach(part; block.parts) 13516 text ~= part.text; 13517 return text; 13518 } 13519 13520 string getHtml() { 13521 return null; // FIXME 13522 } 13523 13524 this(Rectangle boundingBox) { 13525 this.boundingBox = boundingBox; 13526 } 13527 13528 BlockElement addBlock(InlineElement after = null, Rectangle margin = Rectangle(0, 0, 0, 0), Rectangle padding = Rectangle(0, 0, 0, 0)) { 13529 auto be = new BlockElement(); 13530 be.containingLayout = this; 13531 if(after is null) 13532 blocks ~= be; 13533 else { 13534 foreach(idx, b; blocks) { 13535 if(b is after.containingBlock) { 13536 blocks = blocks[0 .. idx + 1] ~ be ~ blocks[idx + 1 .. $]; 13537 break; 13538 } 13539 } 13540 } 13541 return be; 13542 } 13543 13544 void clear() { 13545 blocks = null; 13546 selectionStart = selectionEnd = caret = Caret.init; 13547 } 13548 13549 void addText(Args...)(Args args) { 13550 if(blocks.length == 0) 13551 addBlock(); 13552 13553 InlineElement ie = new InlineElement(); 13554 foreach(idx, arg; args) { 13555 static if(is(typeof(arg) == ForegroundColor)) 13556 ie.color = arg; 13557 else static if(is(typeof(arg) == TextFormat)) { 13558 if(arg & 0x8000) // ~TextFormat.something turns it off 13559 ie.styles &= arg; 13560 else 13561 ie.styles |= arg; 13562 } else static if(is(typeof(arg) == string)) { 13563 static if(idx == 0 && args.length > 1) 13564 static assert(0, "Put styles before the string."); 13565 size_t lastLineIndex; 13566 foreach(cidx, char a; arg) { 13567 if(a == '\n') { 13568 ie.text = arg[lastLineIndex .. cidx + 1]; 13569 lastLineIndex = cidx + 1; 13570 ie.containingBlock = blocks[$-1]; 13571 blocks[$-1].parts ~= ie.clone; 13572 ie.text = null; 13573 } else { 13574 13575 } 13576 } 13577 13578 ie.text = arg[lastLineIndex .. $]; 13579 ie.containingBlock = blocks[$-1]; 13580 blocks[$-1].parts ~= ie.clone; 13581 caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length); 13582 } 13583 } 13584 13585 invalidateLayout(); 13586 } 13587 13588 void tryMerge(InlineElement into, InlineElement what) { 13589 if(!into.isMergeCompatible(what)) { 13590 return; // cannot merge, different configs 13591 } 13592 13593 // cool, can merge, bring text together... 13594 into.text ~= what.text; 13595 13596 // and remove what 13597 for(size_t a = 0; a < what.containingBlock.parts.length; a++) { 13598 if(what.containingBlock.parts[a] is what) { 13599 for(size_t i = a; i < what.containingBlock.parts.length - 1; i++) 13600 what.containingBlock.parts[i] = what.containingBlock.parts[i + 1]; 13601 what.containingBlock.parts = what.containingBlock.parts[0 .. $-1]; 13602 13603 } 13604 } 13605 13606 // FIXME: ensure no other carets have a reference to it 13607 } 13608 13609 /// exact = true means return null if no match. otherwise, get the closest one that makes sense for a mouse click. 13610 TextIdentifyResult identify(int x, int y, bool exact = false) { 13611 TextIdentifyResult inexactMatch; 13612 foreach(block; blocks) { 13613 foreach(part; block.parts) { 13614 if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { 13615 13616 // FIXME binary search 13617 int tidx; 13618 int lastX; 13619 foreach_reverse(idxo, lx; part.letterXs) { 13620 int idx = cast(int) idxo; 13621 if(lx <= x) { 13622 if(lastX && lastX - x < x - lx) 13623 tidx = idx + 1; 13624 else 13625 tidx = idx; 13626 break; 13627 } 13628 lastX = lx; 13629 } 13630 13631 return TextIdentifyResult(part, tidx).fixupNewline; 13632 } else if(!exact) { 13633 // we're not in the box, but are we on the same line? 13634 if(y >= part.boundingBox.top && y < part.boundingBox.bottom) 13635 inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length); 13636 } 13637 } 13638 } 13639 13640 if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) 13641 return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline; 13642 13643 return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; 13644 } 13645 13646 void moveCaretToPixelCoordinates(int x, int y) { 13647 auto result = identify(x, y); 13648 caret.inlineElement = result.element; 13649 caret.offset = result.offset; 13650 } 13651 13652 void selectToPixelCoordinates(int x, int y) { 13653 auto result = identify(x, y); 13654 13655 if(y < caretLastDrawnY1) { 13656 // on a previous line, carat is selectionEnd 13657 selectionEnd = caret; 13658 13659 selectionStart = Caret(this, result.element, result.offset); 13660 } else if(y > caretLastDrawnY2) { 13661 // on a later line 13662 selectionStart = caret; 13663 13664 selectionEnd = Caret(this, result.element, result.offset); 13665 } else { 13666 // on the same line... 13667 if(x <= caretLastDrawnX) { 13668 selectionEnd = caret; 13669 selectionStart = Caret(this, result.element, result.offset); 13670 } else { 13671 selectionStart = caret; 13672 selectionEnd = Caret(this, result.element, result.offset); 13673 } 13674 13675 } 13676 } 13677 13678 13679 /// Call this if the inputs change. It will reflow everything 13680 void redoLayout(ScreenPainter painter) { 13681 //painter.setClipRectangle(boundingBox); 13682 auto pos = Point(boundingBox.left, boundingBox.top); 13683 13684 int lastHeight; 13685 void nl() { 13686 pos.x = boundingBox.left; 13687 pos.y += lastHeight; 13688 } 13689 foreach(block; blocks) { 13690 nl(); 13691 foreach(part; block.parts) { 13692 part.letterXs = null; 13693 13694 auto size = painter.textSize(part.text); 13695 version(Windows) 13696 if(part.text.length && part.text[$-1] == '\n') 13697 size.height /= 2; // windows counts the new line at the end, but we don't want that 13698 13699 part.boundingBox = Rectangle(pos.x, pos.y, pos.x + size.width, pos.y + size.height); 13700 13701 foreach(idx, char c; part.text) { 13702 // FIXME: unicode 13703 part.letterXs ~= painter.textSize(part.text[0 .. idx]).width + pos.x; 13704 } 13705 13706 pos.x += size.width; 13707 if(pos.x >= boundingBox.right) { 13708 pos.y += size.height; 13709 pos.x = boundingBox.left; 13710 lastHeight = 0; 13711 } else { 13712 lastHeight = size.height; 13713 } 13714 13715 if(part.text.length && part.text[$-1] == '\n') 13716 nl(); 13717 } 13718 } 13719 13720 layoutInvalidated = false; 13721 } 13722 13723 bool layoutInvalidated = true; 13724 void invalidateLayout() { 13725 layoutInvalidated = true; 13726 } 13727 13728 // FIXME: caret can remain sometimes when inserting 13729 // FIXME: inserting at the beginning once you already have something can eff it up. 13730 void drawInto(ScreenPainter painter, bool focused = false) { 13731 if(layoutInvalidated) 13732 redoLayout(painter); 13733 foreach(block; blocks) { 13734 foreach(part; block.parts) { 13735 painter.outlineColor = part.color; 13736 painter.fillColor = part.backgroundColor; 13737 13738 auto pos = part.boundingBox.upperLeft; 13739 auto size = part.boundingBox.size; 13740 13741 painter.drawText(pos, part.text); 13742 if(part.styles & TextFormat.underline) 13743 painter.drawLine(Point(pos.x, pos.y + size.height - 4), Point(pos.x + size.width, pos.y + size.height - 4)); 13744 if(part.styles & TextFormat.strikethrough) 13745 painter.drawLine(Point(pos.x, pos.y + size.height/2), Point(pos.x + size.width, pos.y + size.height/2)); 13746 } 13747 } 13748 13749 // on every redraw, I will force the caret to be 13750 // redrawn too, in order to eliminate perceived lag 13751 // when moving around with the mouse. 13752 eraseCaret(painter); 13753 13754 if(focused) { 13755 highlightSelection(painter); 13756 drawCaret(painter); 13757 } 13758 } 13759 13760 void highlightSelection(ScreenPainter painter) { 13761 if(selectionStart is selectionEnd) 13762 return; // no selection 13763 13764 if(selectionStart.inlineElement is null) return; 13765 if(selectionEnd.inlineElement is null) return; 13766 13767 assert(selectionStart.inlineElement !is null); 13768 assert(selectionEnd.inlineElement !is null); 13769 13770 painter.rasterOp = RasterOp.xor; 13771 painter.outlineColor = Color.transparent; 13772 painter.fillColor = Color(255, 255, 127); 13773 13774 auto at = selectionStart.inlineElement; 13775 auto atOffset = selectionStart.offset; 13776 bool done; 13777 while(at) { 13778 auto box = at.boundingBox; 13779 if(atOffset < at.letterXs.length) 13780 box.left = at.letterXs[atOffset]; 13781 13782 if(at is selectionEnd.inlineElement) { 13783 if(selectionEnd.offset < at.letterXs.length) 13784 box.right = at.letterXs[selectionEnd.offset]; 13785 done = true; 13786 } 13787 13788 painter.drawRectangle(box.upperLeft, box.width, box.height); 13789 13790 if(done) 13791 break; 13792 13793 at = at.getNextInlineElement(); 13794 atOffset = 0; 13795 } 13796 } 13797 13798 int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2; 13799 bool caretShowingOnScreen = false; 13800 void drawCaret(ScreenPainter painter) { 13801 //painter.setClipRectangle(boundingBox); 13802 int x, y1, y2; 13803 if(caret.inlineElement is null) { 13804 x = boundingBox.left; 13805 y1 = boundingBox.top + 2; 13806 y2 = boundingBox.top + painter.fontHeight; 13807 } else { 13808 x = caret.inlineElement.xOfIndex(caret.offset); 13809 y1 = caret.inlineElement.boundingBox.top + 2; 13810 y2 = caret.inlineElement.boundingBox.bottom - 2; 13811 } 13812 13813 if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2)) 13814 eraseCaret(painter); 13815 13816 painter.pen = Pen(Color.white, 1); 13817 painter.rasterOp = RasterOp.xor; 13818 painter.drawLine( 13819 Point(x, y1), 13820 Point(x, y2) 13821 ); 13822 painter.rasterOp = RasterOp.normal; 13823 caretShowingOnScreen = !caretShowingOnScreen; 13824 13825 if(caretShowingOnScreen) { 13826 caretLastDrawnX = x; 13827 caretLastDrawnY1 = y1; 13828 caretLastDrawnY2 = y2; 13829 } 13830 } 13831 13832 Rectangle caretBoundingBox() { 13833 int x, y1, y2; 13834 if(caret.inlineElement is null) { 13835 x = boundingBox.left; 13836 y1 = boundingBox.top + 2; 13837 y2 = boundingBox.top + 16; 13838 } else { 13839 x = caret.inlineElement.xOfIndex(caret.offset); 13840 y1 = caret.inlineElement.boundingBox.top + 2; 13841 y2 = caret.inlineElement.boundingBox.bottom - 2; 13842 } 13843 13844 return Rectangle(x, y1, x + 1, y2); 13845 } 13846 13847 void eraseCaret(ScreenPainter painter) { 13848 //painter.setClipRectangle(boundingBox); 13849 if(!caretShowingOnScreen) return; 13850 painter.pen = Pen(Color.white, 1); 13851 painter.rasterOp = RasterOp.xor; 13852 painter.drawLine( 13853 Point(caretLastDrawnX, caretLastDrawnY1), 13854 Point(caretLastDrawnX, caretLastDrawnY2) 13855 ); 13856 13857 caretShowingOnScreen = false; 13858 painter.rasterOp = RasterOp.normal; 13859 } 13860 13861 /// Caret movement api 13862 /// These should give the user a logical result based on what they see on screen... 13863 /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) 13864 void moveUp() { 13865 if(caret.inlineElement is null) return; 13866 auto x = caret.inlineElement.xOfIndex(caret.offset); 13867 auto y = caret.inlineElement.boundingBox.top + 2; 13868 13869 y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 13870 if(y < 0) 13871 return; 13872 13873 auto i = identify(x, y); 13874 13875 if(i.element) { 13876 caret.inlineElement = i.element; 13877 caret.offset = i.offset; 13878 } 13879 } 13880 void moveDown() { 13881 if(caret.inlineElement is null) return; 13882 auto x = caret.inlineElement.xOfIndex(caret.offset); 13883 auto y = caret.inlineElement.boundingBox.bottom - 2; 13884 13885 y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top; 13886 13887 auto i = identify(x, y); 13888 if(i.element) { 13889 caret.inlineElement = i.element; 13890 caret.offset = i.offset; 13891 } 13892 } 13893 void moveLeft() { 13894 if(caret.inlineElement is null) return; 13895 if(caret.offset) 13896 caret.offset--; 13897 else { 13898 auto p = caret.inlineElement.getPreviousInlineElement(); 13899 if(p) { 13900 caret.inlineElement = p; 13901 if(p.text.length && p.text[$-1] == '\n') 13902 caret.offset = cast(int) p.text.length - 1; 13903 else 13904 caret.offset = cast(int) p.text.length; 13905 } 13906 } 13907 } 13908 void moveRight() { 13909 if(caret.inlineElement is null) return; 13910 if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { 13911 caret.offset++; 13912 } else { 13913 auto p = caret.inlineElement.getNextInlineElement(); 13914 if(p) { 13915 caret.inlineElement = p; 13916 caret.offset = 0; 13917 } 13918 } 13919 } 13920 void moveHome() { 13921 if(caret.inlineElement is null) return; 13922 auto x = 0; 13923 auto y = caret.inlineElement.boundingBox.top + 2; 13924 13925 auto i = identify(x, y); 13926 13927 if(i.element) { 13928 caret.inlineElement = i.element; 13929 caret.offset = i.offset; 13930 } 13931 } 13932 void moveEnd() { 13933 if(caret.inlineElement is null) return; 13934 auto x = int.max; 13935 auto y = caret.inlineElement.boundingBox.top + 2; 13936 13937 auto i = identify(x, y); 13938 13939 if(i.element) { 13940 caret.inlineElement = i.element; 13941 caret.offset = i.offset; 13942 } 13943 13944 } 13945 void movePageUp(ref Caret caret) {} 13946 void movePageDown(ref Caret caret) {} 13947 13948 void moveDocumentStart(ref Caret caret) { 13949 if(blocks.length && blocks[0].parts.length) 13950 caret = Caret(this, blocks[0].parts[0], 0); 13951 else 13952 caret = Caret.init; 13953 } 13954 13955 void moveDocumentEnd(ref Caret caret) { 13956 if(blocks.length) { 13957 auto parts = blocks[$-1].parts; 13958 if(parts.length) { 13959 caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length); 13960 } else { 13961 caret = Caret.init; 13962 } 13963 } else 13964 caret = Caret.init; 13965 } 13966 13967 void deleteSelection() { 13968 if(selectionStart is selectionEnd) 13969 return; 13970 13971 if(selectionStart.inlineElement is null) return; 13972 if(selectionEnd.inlineElement is null) return; 13973 13974 assert(selectionStart.inlineElement !is null); 13975 assert(selectionEnd.inlineElement !is null); 13976 13977 auto at = selectionStart.inlineElement; 13978 13979 if(selectionEnd.inlineElement is at) { 13980 // same element, need to chop out 13981 at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $]; 13982 at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $]; 13983 selectionEnd.offset -= selectionEnd.offset - selectionStart.offset; 13984 } else { 13985 // different elements, we can do it with slicing 13986 at.text = at.text[0 .. selectionStart.offset]; 13987 if(selectionStart.offset < at.letterXs.length) 13988 at.letterXs = at.letterXs[0 .. selectionStart.offset]; 13989 13990 at = at.getNextInlineElement(); 13991 13992 while(at) { 13993 if(at is selectionEnd.inlineElement) { 13994 at.text = at.text[selectionEnd.offset .. $]; 13995 if(selectionEnd.offset < at.letterXs.length) 13996 at.letterXs = at.letterXs[selectionEnd.offset .. $]; 13997 selectionEnd.offset = 0; 13998 break; 13999 } else { 14000 auto cfd = at; 14001 cfd.text = null; // delete the whole thing 14002 14003 at = at.getNextInlineElement(); 14004 14005 if(cfd.text.length == 0) { 14006 // and remove cfd 14007 for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) { 14008 if(cfd.containingBlock.parts[a] is cfd) { 14009 for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++) 14010 cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1]; 14011 cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1]; 14012 14013 } 14014 } 14015 } 14016 } 14017 } 14018 } 14019 14020 caret = selectionEnd; 14021 selectNone(); 14022 14023 invalidateLayout(); 14024 14025 } 14026 14027 /// Plain text editing api. These work at the current caret inside the selected inline element. 14028 void insert(in char[] text) { 14029 foreach(dchar ch; text) 14030 insert(ch); 14031 } 14032 /// ditto 14033 void insert(dchar ch) { 14034 14035 bool selectionDeleted = false; 14036 if(selectionStart !is selectionEnd) { 14037 deleteSelection(); 14038 selectionDeleted = true; 14039 } 14040 14041 if(ch == 127) { 14042 delete_(); 14043 return; 14044 } 14045 if(ch == 8) { 14046 if(!selectionDeleted) 14047 backspace(); 14048 return; 14049 } 14050 14051 invalidateLayout(); 14052 14053 if(ch == 13) ch = 10; 14054 auto e = caret.inlineElement; 14055 if(e is null) { 14056 addText("" ~ cast(char) ch) ; // FIXME 14057 return; 14058 } 14059 14060 if(caret.offset == e.text.length) { 14061 e.text ~= cast(char) ch; // FIXME 14062 caret.offset++; 14063 if(ch == 10) { 14064 auto c = caret.inlineElement.clone; 14065 c.text = null; 14066 c.letterXs = null; 14067 insertPartAfter(c,e); 14068 caret = Caret(this, c, 0); 14069 } 14070 } else { 14071 // FIXME cast char sucks 14072 if(ch == 10) { 14073 auto c = caret.inlineElement.clone; 14074 c.text = e.text[caret.offset .. $]; 14075 if(caret.offset < c.letterXs.length) 14076 c.letterXs = e.letterXs[caret.offset .. $]; // FIXME boundingBox 14077 e.text = e.text[0 .. caret.offset] ~ cast(char) ch; 14078 if(caret.offset <= e.letterXs.length) { 14079 e.letterXs = e.letterXs[0 .. caret.offset] ~ 0; // FIXME bounding box 14080 } 14081 insertPartAfter(c,e); 14082 caret = Caret(this, c, 0); 14083 } else { 14084 e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $]; 14085 caret.offset++; 14086 } 14087 } 14088 } 14089 14090 void insertPartAfter(InlineElement what, InlineElement where) { 14091 foreach(idx, p; where.containingBlock.parts) { 14092 if(p is where) { 14093 if(idx + 1 == where.containingBlock.parts.length) 14094 where.containingBlock.parts ~= what; 14095 else 14096 where.containingBlock.parts = where.containingBlock.parts[0 .. idx + 1] ~ what ~ where.containingBlock.parts[idx + 1 .. $]; 14097 return; 14098 } 14099 } 14100 } 14101 14102 void cleanupStructures() { 14103 for(size_t i = 0; i < blocks.length; i++) { 14104 auto block = blocks[i]; 14105 for(size_t a = 0; a < block.parts.length; a++) { 14106 auto part = block.parts[a]; 14107 if(part.text.length == 0) { 14108 for(size_t b = a; b < block.parts.length - 1; b++) 14109 block.parts[b] = block.parts[b+1]; 14110 block.parts = block.parts[0 .. $-1]; 14111 } 14112 } 14113 if(block.parts.length == 0) { 14114 for(size_t a = i; a < blocks.length - 1; a++) 14115 blocks[a] = blocks[a+1]; 14116 blocks = blocks[0 .. $-1]; 14117 } 14118 } 14119 } 14120 14121 void backspace() { 14122 try_again: 14123 auto e = caret.inlineElement; 14124 if(e is null) 14125 return; 14126 if(caret.offset == 0) { 14127 auto prev = e.getPreviousInlineElement(); 14128 if(prev is null) 14129 return; 14130 auto newOffset = cast(int) prev.text.length; 14131 tryMerge(prev, e); 14132 caret.inlineElement = prev; 14133 caret.offset = prev is null ? 0 : newOffset; 14134 14135 goto try_again; 14136 } else if(caret.offset == e.text.length) { 14137 e.text = e.text[0 .. $-1]; 14138 caret.offset--; 14139 } else { 14140 e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $]; 14141 caret.offset--; 14142 } 14143 //cleanupStructures(); 14144 14145 invalidateLayout(); 14146 } 14147 void delete_() { 14148 if(selectionStart !is selectionEnd) 14149 deleteSelection(); 14150 else { 14151 auto before = caret; 14152 moveRight(); 14153 if(caret != before) { 14154 backspace(); 14155 } 14156 } 14157 14158 invalidateLayout(); 14159 } 14160 void overstrike() {} 14161 14162 /// Selection API. See also: caret movement. 14163 void selectAll() { 14164 moveDocumentStart(selectionStart); 14165 moveDocumentEnd(selectionEnd); 14166 } 14167 bool selectNone() { 14168 if(selectionStart != selectionEnd) { 14169 selectionStart = selectionEnd = Caret.init; 14170 return true; 14171 } 14172 return false; 14173 } 14174 14175 /// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements. 14176 /// They will modify the current selection if there is one and will splice one in if needed. 14177 void changeAttributes() {} 14178 14179 14180 /// Text search api. They manipulate the selection and/or caret. 14181 void findText(string text) {} 14182 void findIndex(size_t textIndex) {} 14183 14184 // sample event handlers 14185 14186 void handleEvent(KeyEvent event) { 14187 //if(event.type == KeyEvent.Type.KeyPressed) { 14188 14189 //} 14190 } 14191 14192 void handleEvent(dchar ch) { 14193 14194 } 14195 14196 void handleEvent(MouseEvent event) { 14197 14198 } 14199 14200 bool contentEditable; // can it be edited? 14201 bool contentCaretable; // is there a caret/cursor that moves around in there? 14202 bool contentSelectable; // selectable? 14203 14204 Caret caret; 14205 Caret selectionStart; 14206 Caret selectionEnd; 14207 14208 bool insertMode; 14209 } 14210 14211 struct Caret { 14212 TextLayout layout; 14213 InlineElement inlineElement; 14214 int offset; 14215 } 14216 14217 enum TextFormat : ushort { 14218 // decorations 14219 underline = 1, 14220 strikethrough = 2, 14221 14222 // font selectors 14223 14224 bold = 0x4000 | 1, // weight 700 14225 light = 0x4000 | 2, // weight 300 14226 veryBoldOrLight = 0x4000 | 4, // weight 100 with light, weight 900 with bold 14227 // bold | light is really invalid but should give weight 500 14228 // veryBoldOrLight without one of the others should just give the default for the font; it should be ignored. 14229 14230 italic = 0x4000 | 8, 14231 smallcaps = 0x4000 | 16, 14232 } 14233 14234 void* findFont(string family, int weight, TextFormat formats) { 14235 return null; 14236 } 14237 14238 } 14239 14240 static if(UsingSimpledisplayX11) { 14241 14242 enum _NET_WM_STATE_ADD = 1; 14243 enum _NET_WM_STATE_REMOVE = 0; 14244 enum _NET_WM_STATE_TOGGLE = 2; 14245 14246 /// X-specific. Use [SimpleWindow.requestAttention] instead for most casesl 14247 void demandAttention(SimpleWindow window, bool needs = true) { 14248 demandAttention(window.impl.window, needs); 14249 } 14250 14251 /// ditto 14252 void demandAttention(Window window, bool needs = true) { 14253 auto display = XDisplayConnection.get(); 14254 auto atom = XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", true); 14255 if(atom == None) 14256 return; // non-failure error 14257 //auto atom2 = GetAtom!"_NET_WM_STATE_SHADED"(display); 14258 14259 XClientMessageEvent xclient; 14260 14261 xclient.type = EventType.ClientMessage; 14262 xclient.window = window; 14263 xclient.message_type = GetAtom!"_NET_WM_STATE"(display); 14264 xclient.format = 32; 14265 xclient.data.l[0] = needs ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 14266 xclient.data.l[1] = atom; 14267 //xclient.data.l[2] = atom2; 14268 // [2] == a second property 14269 // [3] == source. 0 == unknown, 1 == app, 2 == else 14270 14271 XSendEvent( 14272 display, 14273 RootWindow(display, DefaultScreen(display)), 14274 false, 14275 EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask, 14276 cast(XEvent*) &xclient 14277 ); 14278 14279 /+ 14280 XChangeProperty( 14281 display, 14282 window.impl.window, 14283 GetAtom!"_NET_WM_STATE"(display), 14284 XA_ATOM, 14285 32 /* bits */, 14286 PropModeAppend, 14287 &atom, 14288 1); 14289 +/ 14290 } 14291 14292 /// X-specific 14293 TrueColorImage getWindowNetWmIcon(Window window) { 14294 auto display = XDisplayConnection.get; 14295 14296 auto data = getX11PropertyData (window, GetAtom!"_NET_WM_ICON"(display), XA_CARDINAL); 14297 14298 if (data.length > arch_ulong.sizeof * 2) { 14299 auto meta = cast(arch_ulong[]) (data[0 .. arch_ulong.sizeof * 2]); 14300 // these are an array of rgba images that we have to convert into pixmaps ourself 14301 14302 int width = cast(int) meta[0]; 14303 int height = cast(int) meta[1]; 14304 14305 auto bytes = cast(ubyte[]) (data[arch_ulong.sizeof * 2 .. $]); 14306 14307 static if(arch_ulong.sizeof == 4) { 14308 bytes = bytes[0 .. width * height * 4]; 14309 alias imageData = bytes; 14310 } else static if(arch_ulong.sizeof == 8) { 14311 bytes = bytes[0 .. width * height * 8]; 14312 auto imageData = new ubyte[](4 * width * height); 14313 } else static assert(0); 14314 14315 14316 14317 // this returns ARGB. Remember it is little-endian so 14318 // we have BGRA 14319 // our thing uses RGBA, which in little endian, is ABGR 14320 for(int idx = 0, idx2 = 0; idx < bytes.length; idx += arch_ulong.sizeof, idx2 += 4) { 14321 auto r = bytes[idx + 2]; 14322 auto g = bytes[idx + 1]; 14323 auto b = bytes[idx + 0]; 14324 auto a = bytes[idx + 3]; 14325 14326 imageData[idx2 + 0] = r; 14327 imageData[idx2 + 1] = g; 14328 imageData[idx2 + 2] = b; 14329 imageData[idx2 + 3] = a; 14330 } 14331 14332 return new TrueColorImage(width, height, imageData); 14333 } 14334 14335 return null; 14336 } 14337 14338 } 14339 14340 14341 void loadBinNameToWindowClassName () { 14342 import core.stdc.stdlib : realloc; 14343 version(linux) { 14344 // args[0] MAY be empty, so we'll just use this 14345 import core.sys.posix.unistd : readlink; 14346 char[1024] ebuf = void; // 1KB should be enough for everyone! 14347 auto len = readlink("/proc/self/exe", ebuf.ptr, ebuf.length); 14348 if (len < 1) return; 14349 } else /*version(Windows)*/ { 14350 import core.runtime : Runtime; 14351 if (Runtime.args.length == 0 || Runtime.args[0].length == 0) return; 14352 auto ebuf = Runtime.args[0]; 14353 auto len = ebuf.length; 14354 } 14355 auto pos = len; 14356 while (pos > 0 && ebuf[pos-1] != '/') --pos; 14357 sdpyWindowClassStr = cast(char*)realloc(sdpyWindowClassStr, len-pos+1); 14358 if (sdpyWindowClassStr is null) return; // oops 14359 sdpyWindowClassStr[0..len-pos+1] = 0; // just in case 14360 sdpyWindowClassStr[0..len-pos] = ebuf[pos..len]; 14361 } 14362 14363 /++ 14364 An interface representing a font. 14365 14366 This is still MAJOR work in progress. 14367 +/ 14368 interface DrawableFont { 14369 void drawString(ScreenPainter painter, Point upperLeft, in char[] text); 14370 } 14371 14372 /++ 14373 Loads a true type font using [arsd.ttf]. That module must be compiled 14374 in if you choose to use this function. 14375 14376 Be warned: this can be slow and memory hungry, especially on remote connections 14377 to the X server. 14378 14379 This is still MAJOR work in progress. 14380 +/ 14381 DrawableFont arsdTtfFont()(in ubyte[] data, int size) { 14382 import arsd.ttf; 14383 static class ArsdTtfFont : DrawableFont { 14384 TtfFont font; 14385 int size; 14386 this(in ubyte[] data, int size) { 14387 font = TtfFont(data); 14388 this.size = size; 14389 } 14390 14391 Sprite[string] cache; 14392 14393 void drawString(ScreenPainter painter, Point upperLeft, in char[] text) { 14394 Sprite sprite = (text in cache) ? *(text in cache) : null; 14395 14396 auto fg = painter.impl._outlineColor; 14397 auto bg = painter.impl._fillColor; 14398 14399 if(sprite is null) { 14400 int width, height; 14401 auto data = font.renderString(text, size, width, height); 14402 auto image = new TrueColorImage(width, height); 14403 int pos = 0; 14404 foreach(y; 0 .. height) 14405 foreach(x; 0 .. width) { 14406 fg.a = data[0]; 14407 bg.a = 255; 14408 auto color = alphaBlend(fg, bg); 14409 image.imageData.bytes[pos++] = color.r; 14410 image.imageData.bytes[pos++] = color.g; 14411 image.imageData.bytes[pos++] = color.b; 14412 image.imageData.bytes[pos++] = data[0]; 14413 data = data[1 .. $]; 14414 } 14415 assert(data.length == 0); 14416 14417 sprite = new Sprite(painter.window, Image.fromMemoryImage(image)); 14418 cache[text.idup] = sprite; 14419 } 14420 14421 sprite.drawAt(painter, upperLeft); 14422 } 14423 } 14424 14425 return new ArsdTtfFont(data, size); 14426 } 14427 14428 class NotYetImplementedException : Exception { 14429 this(string file = __FILE__, size_t line = __LINE__) { 14430 super("Not yet implemented", file, line); 14431 } 14432 } 14433 14434 private alias scriptable = arsd_jsvar_compatible;