├── .editorconfig ├── .gitignore ├── Class Hierarchy.dia ├── LICENSE ├── NewHiearachy.dia ├── README.md ├── d-linter.ini ├── dscanner.ini ├── dub.json ├── dwin.desktop ├── scripts ├── config.json ├── jsonConfig.ds ├── lemonbar.ds └── printTree.ds └── source ├── app.d ├── arsd ├── README.md ├── jsvar.d └── script.d └── dwin ├── backend ├── bindmanager.d ├── container.d ├── engine.d ├── layout.d ├── mouse.d ├── mousestyle.d ├── screen.d ├── window.d ├── workspace.d └── xcb │ ├── atom.d │ ├── event.d │ ├── keyparser.d │ ├── xcb.d │ ├── xcbbindmanager.d │ ├── xcbmouse.d │ ├── xcbmousestyle.d │ └── xcbwindow.d ├── dwin.d ├── event.d ├── layout ├── floatinglayout.d └── tilinglayout.d ├── log.d ├── script ├── api │ ├── bindmanagerapi.d │ ├── dataapi.d │ ├── engineapi.d │ ├── infoapi.d │ ├── ioapi.d │ ├── logapi.d │ ├── package.d │ └── systemapi.d ├── script.d └── utils.d └── util └── data.d /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 160 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.d] 15 | # It's more like K&R than otbs, but K&R does not exist in dfmt 16 | dfmt_brace_style = otbs 17 | dfmt_soft_max_line_length = 120 18 | dfmt_align_switch_statements = true 19 | dfmt_outdent_attributes = true 20 | dfmt_split_operator_at_line_end = false 21 | dfmt_space_after_cast = false 22 | dfmt_space_after_keywords = false 23 | dfmt_selective_import_space = true 24 | dfmt_compact_labeled_statements = true 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | /dwin 7 | dub.selections.json 8 | .gdb_history 9 | *~ 10 | .vscode 11 | *.xcf 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. "Contributor Version" 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. "Covered Software" 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the terms of 34 | a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. "Larger Work" 41 | 42 | means a work that combines Covered Software with other material, in a 43 | separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | 47 | means this document. 48 | 49 | 1.9. "Licensable" 50 | 51 | means having the right to grant, to the maximum extent possible, whether 52 | at the time of the initial grant or subsequently, any and all of the 53 | rights conveyed by this License. 54 | 55 | 1.10. "Modifications" 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, 60 | deletion from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. "Patent Claims" of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, 67 | process, and apparatus claims, in any patent Licensable by such 68 | Contributor that would be infringed, but for the grant of the License, 69 | by the making, using, selling, offering for sale, having made, import, 70 | or transfer of either its Contributions or its Contributor Version. 71 | 72 | 1.12. "Secondary License" 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. "Source Code Form" 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. "You" (or "Your") 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, "You" includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, "control" means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or 104 | as part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its 108 | Contributions or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution 113 | become effective for each Contribution on the date the Contributor first 114 | distributes such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under 119 | this License. No additional rights or licenses will be implied from the 120 | distribution or licensing of Covered Software under this License. 121 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 122 | Contributor: 123 | 124 | a. for any code that a Contributor has removed from Covered Software; or 125 | 126 | b. for infringements caused by: (i) Your and any other third party's 127 | modifications of Covered Software, or (ii) the combination of its 128 | Contributions with other software (except as part of its Contributor 129 | Version); or 130 | 131 | c. under Patent Claims infringed by Covered Software in the absence of 132 | its Contributions. 133 | 134 | This License does not grant any rights in the trademarks, service marks, 135 | or logos of any Contributor (except as may be necessary to comply with 136 | the notice requirements in Section 3.4). 137 | 138 | 2.4. Subsequent Licenses 139 | 140 | No Contributor makes additional grants as a result of Your choice to 141 | distribute the Covered Software under a subsequent version of this 142 | License (see Section 10.2) or under the terms of a Secondary License (if 143 | permitted under the terms of Section 3.3). 144 | 145 | 2.5. Representation 146 | 147 | Each Contributor represents that the Contributor believes its 148 | Contributions are its original creation(s) or it has sufficient rights to 149 | grant the rights to its Contributions conveyed by this License. 150 | 151 | 2.6. Fair Use 152 | 153 | This License is not intended to limit any rights You have under 154 | applicable copyright doctrines of fair use, fair dealing, or other 155 | equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under 169 | the terms of this License. You must inform recipients that the Source 170 | Code Form of the Covered Software is governed by the terms of this 171 | License, and how they can obtain a copy of this License. You may not 172 | attempt to alter or restrict the recipients' rights in the Source Code 173 | Form. 174 | 175 | 3.2. Distribution of Executable Form 176 | 177 | If You distribute Covered Software in Executable Form then: 178 | 179 | a. such Covered Software must also be made available in Source Code Form, 180 | as described in Section 3.1, and You must inform recipients of the 181 | Executable Form how they can obtain a copy of such Source Code Form by 182 | reasonable means in a timely manner, at a charge no more than the cost 183 | of distribution to the recipient; and 184 | 185 | b. You may distribute such Executable Form under the terms of this 186 | License, or sublicense it under different terms, provided that the 187 | license for the Executable Form does not attempt to limit or alter the 188 | recipients' rights in the Source Code Form under this License. 189 | 190 | 3.3. Distribution of a Larger Work 191 | 192 | You may create and distribute a Larger Work under terms of Your choice, 193 | provided that You also comply with the requirements of this License for 194 | the Covered Software. If the Larger Work is a combination of Covered 195 | Software with a work governed by one or more Secondary Licenses, and the 196 | Covered Software is not Incompatible With Secondary Licenses, this 197 | License permits You to additionally distribute such Covered Software 198 | under the terms of such Secondary License(s), so that the recipient of 199 | the Larger Work may, at their option, further distribute the Covered 200 | Software under the terms of either this License or such Secondary 201 | License(s). 202 | 203 | 3.4. Notices 204 | 205 | You may not remove or alter the substance of any license notices 206 | (including copyright notices, patent notices, disclaimers of warranty, or 207 | limitations of liability) contained within the Source Code Form of the 208 | Covered Software, except that You may alter any license notices to the 209 | extent required to remedy known factual inaccuracies. 210 | 211 | 3.5. Application of Additional Terms 212 | 213 | You may choose to offer, and to charge a fee for, warranty, support, 214 | indemnity or liability obligations to one or more recipients of Covered 215 | Software. However, You may do so only on Your own behalf, and not on 216 | behalf of any Contributor. You must make it absolutely clear that any 217 | such warranty, support, indemnity, or liability obligation is offered by 218 | You alone, and You hereby agree to indemnify every Contributor for any 219 | liability incurred by such Contributor as a result of warranty, support, 220 | indemnity or liability terms You offer. You may include additional 221 | disclaimers of warranty and limitations of liability specific to any 222 | jurisdiction. 223 | 224 | 4. Inability to Comply Due to Statute or Regulation 225 | 226 | If it is impossible for You to comply with any of the terms of this License 227 | with respect to some or all of the Covered Software due to statute, 228 | judicial order, or regulation then You must: (a) comply with the terms of 229 | this License to the maximum extent possible; and (b) describe the 230 | limitations and the code they affect. Such description must be placed in a 231 | text file included with all distributions of the Covered Software under 232 | this License. Except to the extent prohibited by statute or regulation, 233 | such description must be sufficiently detailed for a recipient of ordinary 234 | skill to be able to understand it. 235 | 236 | 5. Termination 237 | 238 | 5.1. The rights granted under this License will terminate automatically if You 239 | fail to comply with any of its terms. However, if You become compliant, 240 | then the rights granted under this License from a particular Contributor 241 | are reinstated (a) provisionally, unless and until such Contributor 242 | explicitly and finally terminates Your grants, and (b) on an ongoing 243 | basis, if such Contributor fails to notify You of the non-compliance by 244 | some reasonable means prior to 60 days after You have come back into 245 | compliance. Moreover, Your grants from a particular Contributor are 246 | reinstated on an ongoing basis if such Contributor notifies You of the 247 | non-compliance by some reasonable means, this is the first time You have 248 | received notice of non-compliance with this License from such 249 | Contributor, and You become compliant prior to 30 days after Your receipt 250 | of the notice. 251 | 252 | 5.2. If You initiate litigation against any entity by asserting a patent 253 | infringement claim (excluding declaratory judgment actions, 254 | counter-claims, and cross-claims) alleging that a Contributor Version 255 | directly or indirectly infringes any patent, then the rights granted to 256 | You by any and all Contributors for the Covered Software under Section 257 | 2.1 of this License shall terminate. 258 | 259 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 260 | license agreements (excluding distributors and resellers) which have been 261 | validly granted by You or Your distributors under this License prior to 262 | termination shall survive termination. 263 | 264 | 6. Disclaimer of Warranty 265 | 266 | Covered Software is provided under this License on an "as is" basis, 267 | without warranty of any kind, either expressed, implied, or statutory, 268 | including, without limitation, warranties that the Covered Software is free 269 | of defects, merchantable, fit for a particular purpose or non-infringing. 270 | The entire risk as to the quality and performance of the Covered Software 271 | is with You. Should any Covered Software prove defective in any respect, 272 | You (not any Contributor) assume the cost of any necessary servicing, 273 | repair, or correction. This disclaimer of warranty constitutes an essential 274 | part of this License. No use of any Covered Software is authorized under 275 | this License except under this disclaimer. 276 | 277 | 7. Limitation of Liability 278 | 279 | Under no circumstances and under no legal theory, whether tort (including 280 | negligence), contract, or otherwise, shall any Contributor, or anyone who 281 | distributes Covered Software as permitted above, be liable to You for any 282 | direct, indirect, special, incidental, or consequential damages of any 283 | character including, without limitation, damages for lost profits, loss of 284 | goodwill, work stoppage, computer failure or malfunction, or any and all 285 | other commercial damages or losses, even if such party shall have been 286 | informed of the possibility of such damages. This limitation of liability 287 | shall not apply to liability for death or personal injury resulting from 288 | such party's negligence to the extent applicable law prohibits such 289 | limitation. Some jurisdictions do not allow the exclusion or limitation of 290 | incidental or consequential damages, so this exclusion and limitation may 291 | not apply to You. 292 | 293 | 8. Litigation 294 | 295 | Any litigation relating to this License may be brought only in the courts 296 | of a jurisdiction where the defendant maintains its principal place of 297 | business and such litigation shall be governed by laws of that 298 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 299 | in this Section shall prevent a party's ability to bring cross-claims or 300 | counter-claims. 301 | 302 | 9. Miscellaneous 303 | 304 | This License represents the complete agreement concerning the subject 305 | matter hereof. If any provision of this License is held to be 306 | unenforceable, such provision shall be reformed only to the extent 307 | necessary to make it enforceable. Any law or regulation which provides that 308 | the language of a contract shall be construed against the drafter shall not 309 | be used to construe this License against a Contributor. 310 | 311 | 312 | 10. Versions of the License 313 | 314 | 10.1. New Versions 315 | 316 | Mozilla Foundation is the license steward. Except as provided in Section 317 | 10.3, no one other than the license steward has the right to modify or 318 | publish new versions of this License. Each version will be given a 319 | distinguishing version number. 320 | 321 | 10.2. Effect of New Versions 322 | 323 | You may distribute the Covered Software under the terms of the version 324 | of the License under which You originally received the Covered Software, 325 | or under the terms of any subsequent version published by the license 326 | steward. 327 | 328 | 10.3. Modified Versions 329 | 330 | If you create software not governed by this License, and you want to 331 | create a new license for such software, you may create and use a 332 | modified version of this License if you rename the license and remove 333 | any references to the name of the license steward (except to note that 334 | such modified license differs from this License). 335 | 336 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 337 | Licenses If You choose to distribute Source Code Form that is 338 | Incompatible With Secondary Licenses under the terms of this version of 339 | the License, the notice described in Exhibit B of this License must be 340 | attached. 341 | 342 | Exhibit A - Source Code Form License Notice 343 | 344 | This Source Code Form is subject to the 345 | terms of the Mozilla Public License, v. 346 | 2.0. If a copy of the MPL was not 347 | distributed with this file, You can 348 | obtain one at 349 | http://mozilla.org/MPL/2.0/. 350 | 351 | If it is not possible or desirable to put the notice in a particular file, 352 | then You may include the notice in a location (such as a LICENSE file in a 353 | relevant directory) where a recipient would be likely to look for such a 354 | notice. 355 | 356 | You may add additional accurate notices of copyright ownership. 357 | 358 | Exhibit B - "Incompatible With Secondary Licenses" Notice 359 | 360 | This Source Code Form is "Incompatible 361 | With Secondary Licenses", as defined by 362 | the Mozilla Public License, v. 2.0. 363 | 364 | -------------------------------------------------------------------------------- /NewHiearachy.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vild/DWin/4bcc512a87b2f4ece37aca729930e51d93e181d8/NewHiearachy.dia -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DWin - *D always win* 2 | 3 | **PLEASE NOTE THAT DWIN IS IN A PRE-ALPHA STATE. NOT READY FOR PRODUCTION USE!** 4 | 5 | ## Description 6 | DWin is a tiling window manager written in the lovely language called [D](//dlang.org). 7 | 8 | It currently uses X11 as it's backend but will also be ported to wayland in the future. 9 | 10 | ##Usage 11 | 12 | ``` 13 | DWin is a tiled based window manager written in the lovely language called D 14 | -d --display The display to run it on 15 | -n --no-xephyr Run it native, without Xephyr 16 | -h --help This help information. 17 | ``` 18 | 19 | To start DWin natively make a ~/.xinitrc file with the following content 20 | 21 | ``` 22 | dwin -d 0 -n 23 | ``` 24 | 25 | And then run `startx` 26 | 27 | You can then: 28 | 29 | - Spawn xterm: Ctrl + Enter 30 | - Spawn xeyes: Ctrl + Backspace 31 | - Move a window: Ctrl + Mouse1 32 | - Resize a window: Ctrl + Mouse3 33 | - Close a window: Ctrl + Shift + Escape 34 | - Move to a workspace on the left: Ctrl + 1 35 | - Move to a workspace on the right: Ctrl + 2 36 | - Promote window to OnTop: Ctrl + P 37 | - Promote window to OnTop: Ctrl + O 38 | - Print window hierarchy: Ctrl + F5 39 | - Kill DWin: Escape 40 | 41 | ## Authors 42 | Dan "Wild" Printzell 43 | 44 | ## License 45 | [Mozilla Public License, version 2.0](LICENSE) 46 | -------------------------------------------------------------------------------- /d-linter.ini: -------------------------------------------------------------------------------- 1 | ; Configurue which static analysis checks are enabled 2 | [analysis.config.StaticAnalysisConfig] 3 | ; Check variable, class, struct, interface, union, and function names against t 4 | ; he Phobos style guide 5 | style_check="false" 6 | ; Check for array literals that cause unnecessary allocation 7 | enum_array_literal_check="true" 8 | ; Check for poor exception handling practices 9 | exception_check="true" 10 | ; Check for use of the deprecated 'delete' keyword 11 | delete_check="true" 12 | ; Check for use of the deprecated floating point operators 13 | float_operator_check="true" 14 | ; Check number literals for readability 15 | number_style_check="true" 16 | ; Checks that opEquals, opCmp, toHash, and toString are either const, immutable 17 | ; , or inout. 18 | object_const_check="true" 19 | ; Checks for .. expressions where the left side is larger than the right. 20 | backwards_range_check="true" 21 | ; Checks for if statements whose 'then' block is the same as the 'else' block 22 | if_else_same_check="true" 23 | ; Checks for some problems with constructors 24 | constructor_check="true" 25 | ; Checks for unused variables and function parameters 26 | unused_variable_check="true" 27 | ; Checks for unused labels 28 | unused_label_check="true" 29 | ; Checks for duplicate attributes 30 | duplicate_attribute="true" 31 | ; Checks that opEquals and toHash are both defined or neither are defined 32 | opequals_tohash_check="true" 33 | ; Checks for subtraction from .length properties 34 | length_subtraction_check="true" 35 | ; Checks for methods or properties whose names conflict with built-in propertie 36 | ; s 37 | builtin_property_names_check="true" 38 | ; Checks for confusing code in inline asm statements 39 | asm_style_check="true" 40 | ; Checks for confusing logical operator precedence 41 | logical_precedence_check="true" 42 | ; Checks for undocumented public declarations 43 | undocumented_declaration_check="true" 44 | ; Checks for poor placement of function attributes 45 | function_attribute_check="true" 46 | ; Checks for use of the comma operator 47 | comma_expression_check="true" 48 | ; Checks for local imports that are too broad 49 | local_import_check="true" 50 | ; Checks for variables that could be declared immutable 51 | could_be_immutable_check="true" 52 | ; Checks for redundant expressions in if statements 53 | redundant_if_check="true" 54 | ; Checks for redundant parenthesis 55 | redundant_parens_check="true" 56 | ; Checks for labels with the same name as variables 57 | label_var_same_name_check="true" 58 | -------------------------------------------------------------------------------- /dscanner.ini: -------------------------------------------------------------------------------- 1 | ; Configurue which static analysis checks are enabled 2 | [analysis.config.StaticAnalysisConfig] 3 | ; Check variable, class, struct, interface, union, and function names against t 4 | ; he Phobos style guide 5 | style_check="false" 6 | ; Check for array literals that cause unnecessary allocation 7 | enum_array_literal_check="true" 8 | ; Check for poor exception handling practices 9 | exception_check="true" 10 | ; Check for use of the deprecated 'delete' keyword 11 | delete_check="true" 12 | ; Check for use of the deprecated floating point operators 13 | float_operator_check="true" 14 | ; Check number literals for readability 15 | number_style_check="true" 16 | ; Checks that opEquals, opCmp, toHash, and toString are either const, immutable 17 | ; , or inout. 18 | object_const_check="true" 19 | ; Checks for .. expressions where the left side is larger than the right. 20 | backwards_range_check="true" 21 | ; Checks for if statements whose 'then' block is the same as the 'else' block 22 | if_else_same_check="true" 23 | ; Checks for some problems with constructors 24 | constructor_check="true" 25 | ; Checks for unused variables and function parameters 26 | unused_variable_check="true" 27 | ; Checks for unused labels 28 | unused_label_check="true" 29 | ; Checks for duplicate attributes 30 | duplicate_attribute="true" 31 | ; Checks that opEquals and toHash are both defined or neither are defined 32 | opequals_tohash_check="true" 33 | ; Checks for subtraction from .length properties 34 | length_subtraction_check="true" 35 | ; Checks for methods or properties whose names conflict with built-in propertie 36 | ; s 37 | builtin_property_names_check="true" 38 | ; Checks for confusing code in inline asm statements 39 | asm_style_check="true" 40 | ; Checks for confusing logical operator precedence 41 | logical_precedence_check="true" 42 | ; Checks for undocumented public declarations 43 | undocumented_declaration_check="true" 44 | ; Checks for poor placement of function attributes 45 | function_attribute_check="true" 46 | ; Checks for use of the comma operator 47 | comma_expression_check="true" 48 | ; Checks for local imports that are too broad 49 | local_import_check="true" 50 | ; Checks for variables that could be declared immutable 51 | could_be_immutable_check="true" 52 | ; Checks for redundant expressions in if statements 53 | redundant_if_check="true" 54 | ; Checks for redundant parenthesis 55 | redundant_parens_check="true" 56 | ; Checks for mismatched argument and parameter names 57 | mismatched_args_check="true" 58 | ; Checks for labels with the same name as variables 59 | label_var_same_name_check="true" 60 | ; Checks for lines longer than 120 characters 61 | long_line_check="true" 62 | ; Checks for assignment to auto-ref function parameters 63 | auto_ref_assignment_check="true" 64 | ; Checks for incorrect infinite range definitions 65 | incorrect_infinite_range_check="true" 66 | ; Checks for asserts that are always true 67 | useless_assert_check="true" 68 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dwin", 3 | "description": "A dynamic window manager written in D", 4 | "copyright": "Copyright © 2015, Dan Printzell", 5 | "libs": [ 6 | "xcb-ewmh", 7 | "xcb-icccm", 8 | "xcb-keysyms", 9 | "xcb-xinerama" 10 | ], 11 | "authors": [ 12 | "Dan Printzell" 13 | ], 14 | "dependencies": { 15 | "xcb-d": "~>2.1.0" 16 | } 17 | } -------------------------------------------------------------------------------- /dwin.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Encoding=UTF-8 3 | Name=DWin 4 | Comment=A dynamic window manager written in D 5 | Exec=/usr/bin/dwin -n -d 0 6 | Type=XSession -------------------------------------------------------------------------------- /scripts/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Alt + Return": "exec lxterminal", 3 | "Alt + Space": "exec dmenu_run -l 20 -w 200 -y 18 -fn -lucy-tewi-medium-r-normal--11-90-75-75-m-60-iso10646-1 -nb '#232c31' -nf '#6c7a80' -sf '#ac4142' -sb '#232c31' -o 0.95" 4 | } 5 | -------------------------------------------------------------------------------- /scripts/jsonConfig.ds: -------------------------------------------------------------------------------- 1 | Info.AddCtor("jsonConfig.ds", function() { 2 | var config = new Config("config.json"); 3 | }); 4 | 5 | class Config { 6 | this(var file) { 7 | mapKeys(load(file)); 8 | } 9 | 10 | function load(file) { 11 | return Data.fromJson(IO.ReadFile(file)); 12 | } 13 | 14 | function mapKeys(data) { 15 | foreach (key, val; data) { 16 | var split = Data.Split(val); 17 | 18 | var args = Data.Join(split[1 .. $], " "); 19 | 20 | if (split[0] == "exec") 21 | BindManager.Map(key, function(var pressed) { 22 | if (pressed) 23 | System.SpawnProcess(args); 24 | }); 25 | else if (split[0] == "call") 26 | BindManager.Map(key, function(var pressed) { 27 | if (pressed) 28 | eval(Data.Join(args, " ")); 29 | }); 30 | } 31 | } 32 | }; -------------------------------------------------------------------------------- /scripts/lemonbar.ds: -------------------------------------------------------------------------------- 1 | Info.AddCtor("lemonbar.ds", function() { 2 | System.PipeProcess("lemonbar", "lemonbar -f 'DejaVu Sans Mono:size=24'"); // -g x32 3 | Engine.RegisterTick(lemonbar_tick); 4 | }); 5 | 6 | var lemonbar_cache; 7 | 8 | function lemonbar_tick() { 9 | var scrs = Engine.GetScreens(); 10 | var scr = scrs[0]; 11 | var cur = scr.CurrentWorkspace; 12 | var wrks = scr.Workspaces; 13 | var wrk = wrks[cur]; 14 | 15 | var title = ""; 16 | if (wrk.ActiveWindow) 17 | title = wrk.ActiveWindow.Title; 18 | var date = System.GetDate(); 19 | 20 | var output = "%{l}" ~ wrk.Name ~ "%{c}" ~ title ~ "%{r}" ~ date; 21 | if (output != lemonbar_cache) { 22 | System.WritePipeProcess("lemonbar", output); 23 | lemonbar_cache = output; 24 | } 25 | } -------------------------------------------------------------------------------- /scripts/printTree.ds: -------------------------------------------------------------------------------- 1 | Info.AddCtor("printTree.ds", function() { 2 | var printTree = new PrintTree(); 3 | BindManager.Map("Ctrl + F5", function(var pressed) { 4 | if (pressed) 5 | printTree.Print(Engine.GetScreens()); 6 | }); 7 | }); 8 | 9 | class PrintTree { 10 | function getIndent(var indent) { 11 | var out = ""; 12 | for (var i = 0; i < indent; i += 1) 13 | out ~= " "; 14 | return out; 15 | } 16 | 17 | function printScreen(var screen, var indent) { 18 | var ind = getIndent(indent); 19 | Log.Info(ind ~ "Screen: " ~ screen.Name); 20 | Log.Info(ind ~ "* OnTop: "); 21 | printLayout(screen.OnTop, indent + 1); 22 | Log.Info(ind ~ "* Workspaces: "); 23 | foreach (workspace; screen.Workspaces) 24 | printWorkspace(workspace, indent + 1); 25 | } 26 | 27 | function printWorkspace(var workspace, var indent) { 28 | var ind = getIndent(indent); 29 | Log.Info(ind ~ "* Name: " ~ workspace.Name); 30 | Log.Info(ind ~ "* ActiveWindow: "); 31 | printWindow(workspace.ActiveWindow, indent + 1); 32 | Log.Info(ind ~ "* OnTop: "); 33 | printLayout(workspace.OnTop, indent + 1); 34 | Log.Info(ind ~ "* Root: "); 35 | printLayout(workspace.Root, indent + 1); 36 | } 37 | 38 | function printContainer(var con, var indent) { 39 | if (con.IsWindow) 40 | printWindow(con, indent); 41 | else 42 | printLayout(con, indent); 43 | } 44 | 45 | function printLayout(var layout, var indent) { 46 | var ind = getIndent(indent); 47 | Log.Info(ind ~ "* toString: " ~ layout.ToString); 48 | Log.Info(ind ~ "* Visible: " ~ layout.IsVisible); 49 | foreach (container; layout.Containers) 50 | printContainer(container, indent + 1); 51 | } 52 | 53 | function printWindow(var window, var indent) { 54 | var ind = getIndent(indent); 55 | Log.Info(ind ~ "Window: " ~ window.ToString ~ " Visible: " ~ window.IsVisible); 56 | } 57 | 58 | function Print(var screens) { 59 | Log.Info("===Printing Tree==="); 60 | foreach (screen; screens) 61 | printScreen(screen, 0); 62 | } 63 | }; -------------------------------------------------------------------------------- /source/app.d: -------------------------------------------------------------------------------- 1 | import std.getopt; 2 | import dwin.dwin; 3 | import std.process; 4 | import std.conv; 5 | 6 | int main(string[] args) { 7 | import std.stdio : writeln, writefln; 8 | 9 | int display = 8; 10 | bool noXephyr = false; 11 | 12 | auto result = getopt(args, "d|display", "The display to run it on", &display, "n|no-xephyr", 13 | "Run it native, without Xephyr", &noXephyr); 14 | 15 | if (result.helpWanted) { 16 | defaultGetoptPrinter("DWin is a tiled based window manager written in the lovely language called D", result.options); 17 | return 0; 18 | } 19 | 20 | writeln("Display: ", display, " noXephyr: ", noXephyr); 21 | 22 | string title = "DWin - D always win!"; 23 | string size = "1920x1080"; 24 | 25 | /* 26 | This is hardcoded for now! 27 | Output from `setxkbmap -query` 28 | rules: evdev 29 | model: pc105 30 | layout: se 31 | options: terminate:ctrl_alt_bksp 32 | 33 | Turn it into this: 34 | -keybd ephyr,,,xkbmodel=pc105,xkblayout=se,xkbrules=evdev,xkboption=terminate:ctrl_alt_bksp 35 | */ 36 | Pid Xephyr; 37 | if (!noXephyr) { 38 | import core.thread : Thread; 39 | import core.time : seconds; 40 | 41 | Xephyr = spawnProcess([`Xephyr`, `-keybd`, `ephyr,,,xkbmodel=pc105,xkblayout=se,xkbrules=evdev,xkboption=`, 42 | `-name`, title, `-ac`, `-br`, `-noreset`, `+extension`, `RANDR`, `+xinerama`, `-screen`, size, `:` ~ to!string(display)]); 43 | environment["DISPLAY"] = ":" ~ to!string(display); 44 | Thread.sleep(1.seconds); 45 | } 46 | scope (exit) 47 | if (Xephyr) 48 | kill(Xephyr); 49 | 50 | spawnProcess(["feh", "--bg-scale", "https://wallpaperscraft.com/image/black_light_dark_figures_73356_1920x1080.jpg"]); 51 | 52 | auto dwin = new DWin(display); 53 | scope (exit) 54 | dwin.destroy; 55 | 56 | dwin.Run(); 57 | 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /source/arsd/README.md: -------------------------------------------------------------------------------- 1 | Adam D. Ruppes Scripting Language 2 | ================================= 3 | 4 | Source 5 | ------ 6 | 7 | [https://github.com/adamdruppe/arsd](https://github.com/adamdruppe/arsd) 8 | 9 | 10 | License 11 | ------- 12 | Boost Version 1.0 - [http://www.boost.org/LICENSE_1_0.txt](http://www.boost.org/LICENSE_1_0.txt) -------------------------------------------------------------------------------- /source/arsd/jsvar.d: -------------------------------------------------------------------------------- 1 | /* 2 | FIXME: 3 | pointer to member functions can give a way to wrap things 4 | 5 | we'll pass it an opaque object as this and it will unpack and call the method 6 | 7 | we can also auto-generate getters and setters for properties with this method 8 | 9 | and constructors, so the script can create class objects too 10 | */ 11 | 12 | /** 13 | jsvar provides a D type called 'var' that works similarly to the same in Javascript. 14 | 15 | It is weakly and dynamically typed, but interops pretty easily with D itself: 16 | 17 | var a = 10; 18 | a ~= "20"; 19 | assert(a == "1020"); 20 | 21 | var a = function(int b, int c) { return b+c; }; 22 | // note the second set of () is because of broken @property 23 | assert(a()(10,20) == 30); 24 | 25 | var a = var.emptyObject; 26 | a.foo = 30; 27 | assert(a["foo"] == 30); 28 | 29 | var b = json!q{ 30 | "foo":12, 31 | "bar":{"hey":[1,2,3,"lol"]} 32 | }; 33 | 34 | assert(b.bar.hey[1] == 2); 35 | 36 | 37 | You can also use var.fromJson, a static method, to quickly and easily 38 | read json or var.toJson to write it. 39 | 40 | Also, if you combine this with my new arsd.script module, you get pretty 41 | easy interop with a little scripting language that resembles a cross between 42 | D and Javascript - just like you can write in D itself using this type. 43 | 44 | 45 | Properties: 46 | * note that @property doesn't work right in D, so the opDispatch properties 47 | will require double parenthesis to call as functions. 48 | 49 | * Properties inside a var itself are set specially: 50 | obj.propName._object = new PropertyPrototype(getter, setter); 51 | 52 | D structs can be turned to vars, but it is a copy. 53 | 54 | Wrapping D native objects is coming later, the current ways suck. I really needed 55 | properties to do them sanely at all, and now I have it. A native wrapped object will 56 | also need to be set with _object prolly. 57 | */ 58 | module arsd.jsvar; 59 | 60 | version = new_std_json; 61 | 62 | import std.stdio; 63 | import std.traits; 64 | import std.conv; 65 | import std.json; 66 | 67 | // uda for wrapping classes 68 | enum Scriptable; 69 | 70 | /* 71 | PrototypeObject FIXME: 72 | make undefined variables reaction overloadable in PrototypeObject, not just a switch 73 | 74 | script FIXME: 75 | 76 | the Expression should keep scriptFilename and lineNumber around for error messages 77 | 78 | it should consistently throw on missing semicolons 79 | 80 | *) in operator 81 | 82 | *) nesting comments, `` string literals 83 | *) opDispatch overloading 84 | *) properties???// 85 | a.prop on the rhs => a.prop() 86 | a.prop on the lhs => a.prop(rhs); 87 | if opAssign, it can just do a.prop(a.prop().opBinary!op(rhs)); 88 | 89 | But, how do we mark properties in var? Can we make them work this way in D too? 90 | 0) add global functions to the object (or at least provide a convenience function to make a pre-populated global object) 91 | 1) ensure operator precedence is sane 92 | 2) a++ would prolly be nice, and def -a 93 | 4) switches? 94 | 10) __FILE__ and __LINE__ as default function arguments should work like in D 95 | 16) stack traces on script exceptions 96 | 17) an exception type that we can create in the script 97 | 98 | 14) import???????/ it could just attach a particular object to the local scope, and the module decl just giving the local scope a name 99 | there could be a super-global object that is the prototype of the "global" used here 100 | then you import, and it pulls moduleGlobal.prototype = superGlobal.modulename... or soemthing. 101 | 102 | to get the vars out in D, you'd have to be aware of this, since you pass the superglobal 103 | hmmm maybe not worth it 104 | 105 | though maybe to export vars there could be an explicit export namespace or something. 106 | 107 | 108 | 6) gotos? labels? labeled break/continue? 109 | 18) what about something like ruby's blocks or macros? parsing foo(arg) { code } is easy enough, but how would we use it? 110 | 111 | var FIXME: 112 | 113 | user defined operator overloading on objects, including opCall, opApply, and more 114 | flesh out prototype objects for Array, String, and Function 115 | 116 | looserOpEquals 117 | 118 | it would be nice if delegates on native types could work 119 | */ 120 | 121 | /* 122 | Script notes: 123 | 124 | the one type is var. It works just like the var type in D from arsd.jsvar. 125 | (it might be fun to try to add other types, and match D a little better here! We could allow implicit conversion to and from var, but not on the other types, they could get static checking. But for now it is only var. BTW auto is an alias for var right now) 126 | 127 | There is no comma operator, but you can use a scope as an expression: a++, b++; can be written as {a++;b++;} 128 | */ 129 | 130 | version (test_script) struct Foop { 131 | int a = 12; 132 | string n = "hate"; 133 | void speak() { 134 | writeln(n, " ", a); 135 | n = "love"; 136 | writeln(n, " is what it is now"); 137 | } 138 | 139 | void speak2() { 140 | writeln("speak2 ", n, " ", a); 141 | } 142 | } 143 | 144 | version (test_script) void main() { 145 | import arsd.script; 146 | 147 | writeln(interpret("x*x + 3*x;", var(["x" : 3]))); 148 | 149 | { 150 | var a = var.emptyObject; 151 | a.qweq = 12; 152 | } 153 | 154 | // the WrappedNativeObject is disgusting 155 | // but works. sort of. 156 | /* 157 | Foop foop2; 158 | 159 | var foop; 160 | foop._object = new WrappedNativeObject!Foop(foop2); 161 | 162 | foop.speak()(); 163 | foop.a = 25; 164 | writeln(foop.n); 165 | foop.speak2()(); 166 | return; 167 | */ 168 | 169 | import arsd.script; 170 | 171 | struct Test { 172 | int a = 10; 173 | string name = "ten"; 174 | } 175 | 176 | auto globals = var.emptyObject; 177 | globals.lol = 100; 178 | globals.rofl = 23; 179 | 180 | globals.arrtest = var.emptyArray; 181 | 182 | globals.write._function = (var _this, var[] args) { 183 | string s; 184 | foreach (a; args) 185 | s ~= a.get!string; 186 | writeln("script said: ", s); 187 | return var(null); 188 | }; 189 | 190 | // call D defined functions in script 191 | globals.func = (var a, var b) { writeln("Hello, world! You are : ", a, " and ", b); }; 192 | 193 | globals.ex = () { throw new ScriptRuntimeException("test", 1); }; 194 | 195 | globals.fun = { return var({ writeln("hello inside!"); }); }; 196 | 197 | import std.file; 198 | 199 | writeln(interpret(readText("scripttest_code.d"), globals)); 200 | 201 | globals.ten = 10.0; 202 | globals.five = 5.0; 203 | writeln(interpret(q{ 204 | var a = json!q{ }; 205 | a.b = json!q{ }; 206 | a.b.c = 10; 207 | a; 208 | }, globals)); 209 | 210 | /* 211 | globals.minigui = json!q{}; 212 | import arsd.minigui; 213 | globals.minigui.createWindow = { 214 | var v; 215 | auto mw = new MainWindow(); 216 | v._object = new OpaqueNativeObject!(MainWindow)(mw); 217 | v.loop = { mw.loop(); }; 218 | return v; 219 | }; 220 | */ 221 | 222 | repl(globals); 223 | 224 | writeln("BACK IN D!"); 225 | globals.c()(10); // call script defined functions in D (note: this runs the interpreter) 226 | 227 | //writeln(globals._getMember("lol", false)); 228 | return; 229 | 230 | var k, l; 231 | 232 | var j = json!q{ 233 | "hello": { 234 | "data":[1,2,"giggle",4] 235 | }, 236 | "world":20 237 | }; 238 | 239 | writeln(j.hello.data[2]); 240 | 241 | Test t; 242 | var rofl = t; 243 | writeln(rofl.name); 244 | writeln(rofl.a); 245 | 246 | rofl.a = "20"; 247 | rofl.name = "twenty"; 248 | 249 | t = rofl.get!Test; 250 | writeln(t); 251 | 252 | var a1 = 10; 253 | a1 -= "5"; 254 | a1 /= 2; 255 | 256 | writeln(a1); 257 | 258 | var a = 10; 259 | var b = 20; 260 | a = b; 261 | 262 | b = 30; 263 | a += 100.2; 264 | writeln(a); 265 | 266 | var c = var.emptyObject; 267 | c.a = b; 268 | 269 | var d = c; 270 | d.b = 50; 271 | 272 | writeln(c.b); 273 | 274 | writeln(d.toJson()); 275 | 276 | var e = a + b; 277 | writeln(a, " + ", b, " = ", e); 278 | 279 | e = function(var lol) { writeln("hello with ", lol, "!"); return lol + 10; }; 280 | 281 | writeln(e("15")); 282 | 283 | if (var("ass") > 100) 284 | writeln(var("10") / "3"); 285 | } 286 | 287 | template json(string s) { 288 | // ctfe doesn't support the unions std.json uses :( 289 | //enum json = var.fromJsonObject(s); 290 | 291 | // FIXME we should at least validate string s at compile time 292 | var json() { 293 | return var.fromJson("{" ~ s ~ "}"); 294 | } 295 | } 296 | 297 | // literals 298 | 299 | // var a = varArray(10, "cool", 2); 300 | // assert(a[0] == 10); assert(a[1] == "cool"); assert(a[2] == 2); 301 | var varArray(T...)(T t) { 302 | var a = var.emptyArray; 303 | foreach (arg; t) 304 | a ~= var(arg); 305 | return a; 306 | } 307 | 308 | // var a = varObject("cool", 10, "bar", "baz"); 309 | // assert(a.cool == 10 && a.bar == "baz"); 310 | var varObject(T...)(T t) { 311 | var a = var.emptyObject; 312 | 313 | string lastString; 314 | foreach (idx, arg; t) { 315 | static if (idx % 2 == 0) { 316 | lastString = arg; 317 | } else { 318 | assert(lastString !is null); 319 | a[lastString] = arg; 320 | lastString = null; 321 | } 322 | } 323 | return a; 324 | } 325 | 326 | private real stringToNumber(string s) { 327 | real r; 328 | try { 329 | r = to!real(s); 330 | } 331 | catch (Exception e) { 332 | r = real.nan; 333 | } 334 | 335 | return r; 336 | } 337 | 338 | private bool realIsInteger(real r) { 339 | return (r == cast(long)r); 340 | } 341 | 342 | // helper template for operator overloading 343 | private var _op(alias _this, alias this2, string op, T)(T t) if (op == "~") { 344 | static if (is(T == var)) { 345 | if (t.payloadType() == var.Type.Array) 346 | return _op!(_this, this2, op)(t._payload._array); 347 | else if (t.payloadType() == var.Type.String) 348 | return _op!(_this, this2, op)(t._payload._string); 349 | //else 350 | //return _op!(_this, this2, op)(t.get!string); 351 | } 352 | 353 | if (this2.payloadType() == var.Type.Array) { 354 | auto l = this2._payload._array; 355 | static if (isArray!T && !isSomeString!T) 356 | foreach (item; t) 357 | l ~= var(item); 358 | else 359 | l ~= var(t); 360 | 361 | _this._type = var.Type.Array; 362 | _this._payload._array = l; 363 | return _this; 364 | } else if (this2.payloadType() == var.Type.String) { 365 | auto l = this2._payload._string; 366 | l ~= var(t).get!string; // is this right? 367 | _this._type = var.Type.String; 368 | _this._payload._string = l; 369 | return _this; 370 | } else { 371 | auto l = this2.get!string; 372 | l ~= var(t).get!string; 373 | _this._type = var.Type.String; 374 | _this._payload._string = l; 375 | return _this; 376 | } 377 | 378 | assert(0); 379 | 380 | } 381 | 382 | // FIXME: maybe the bitops should be moved out to another function like ~ is 383 | private var _op(alias _this, alias this2, string op, T)(T t) if (op != "~") { 384 | static if (is(T == var)) { 385 | if (t.payloadType() == var.Type.Integral) 386 | return _op!(_this, this2, op)(t._payload._integral); 387 | if (t.payloadType() == var.Type.Floating) 388 | return _op!(_this, this2, op)(t._payload._floating); 389 | if (t.payloadType() == var.Type.String) 390 | return _op!(_this, this2, op)(t._payload._string); 391 | assert(0, to!string(t.payloadType())); 392 | } else { 393 | if (this2.payloadType() == var.Type.Integral) { 394 | auto l = this2._payload._integral; 395 | static if (isIntegral!T) { 396 | mixin("l " ~ op ~ "= t;"); 397 | _this._type = var.Type.Integral; 398 | _this._payload._integral = l; 399 | return _this; 400 | } else static if (isFloatingPoint!T) { 401 | static if (op == "&" || op == "|" || op == "^") { 402 | this2._type = var.Type.Integral; 403 | long f = l; 404 | mixin("f " ~ op ~ "= cast(long) t;"); 405 | _this._type = var.Type.Integral; 406 | _this._payload._integral = f; 407 | } else { 408 | this2._type = var.Type.Floating; 409 | real f = l; 410 | mixin("f " ~ op ~ "= t;"); 411 | _this._type = var.Type.Floating; 412 | _this._payload._floating = f; 413 | } 414 | return _this; 415 | } else static if (isSomeString!T) { 416 | auto rhs = stringToNumber(t); 417 | if (realIsInteger(rhs)) { 418 | mixin("l " ~ op ~ "= cast(long) rhs;"); 419 | _this._type = var.Type.Integral; 420 | _this._payload._integral = l; 421 | } else { 422 | static if (op == "&" || op == "|" || op == "^") { 423 | long f = l; 424 | mixin("f " ~ op ~ "= cast(long) rhs;"); 425 | _this._type = var.Type.Integral; 426 | _this._payload._integral = f; 427 | } else { 428 | real f = l; 429 | mixin("f " ~ op ~ "= rhs;"); 430 | _this._type = var.Type.Floating; 431 | _this._payload._floating = f; 432 | } 433 | } 434 | return _this; 435 | 436 | } 437 | } else if (this2.payloadType() == var.Type.Floating) { 438 | auto f = this._payload._floating; 439 | 440 | static if (isIntegral!T || isFloatingPoint!T) { 441 | static if (op == "&" || op == "|" || op == "^") { 442 | long argh = cast(long)f; 443 | mixin("argh " ~ op ~ "= cast(long) t;"); 444 | _this._type = var.Type.Integral; 445 | _this._payload._integral = argh; 446 | } else { 447 | mixin("f " ~ op ~ "= t;"); 448 | _this._type = var.Type.Floating; 449 | _this._payload._floating = f; 450 | } 451 | return _this; 452 | } else static if (isSomeString!T) { 453 | auto rhs = stringToNumber(t); 454 | 455 | static if (op == "&" || op == "|" || op == "^") { 456 | long pain = cast(long)f; 457 | mixin("pain " ~ op ~ "= cast(long) rhs;"); 458 | _this._type = var.Type.Integral; 459 | _this._payload._floating = pain; 460 | } else { 461 | mixin("f " ~ op ~ "= rhs;"); 462 | _this._type = var.Type.Floating; 463 | _this._payload._floating = f; 464 | } 465 | return _this; 466 | } else 467 | assert(0); 468 | } else if (this2.payloadType() == var.Type.String) { 469 | static if (op == "&" || op == "|" || op == "^") { 470 | long r = cast(long)stringToNumber(this2._payload._string); 471 | long rhs; 472 | } else { 473 | real r = stringToNumber(this2._payload._string); 474 | real rhs; 475 | } 476 | 477 | static if (isSomeString!T) { 478 | rhs = cast(typeof(rhs))stringToNumber(t); 479 | } else { 480 | rhs = to!(typeof(rhs))(t); 481 | } 482 | 483 | mixin("r " ~ op ~ "= rhs;"); 484 | 485 | static if (is(typeof(r) == real)) { 486 | _this._type = var.Type.Floating; 487 | _this._payload._floating = r; 488 | } else static if (is(typeof(r) == long)) { 489 | _this._type = var.Type.Integral; 490 | _this._payload._integral = r; 491 | } else 492 | static assert(0); 493 | return _this; 494 | } else { 495 | // the operation is nonsensical, we should throw or ignore it 496 | var i = 0; 497 | return i; 498 | } 499 | } 500 | 501 | assert(0); 502 | } 503 | 504 | struct var { 505 | public this(T)(T t) { 506 | static if (is(T == var)) 507 | this = t; 508 | else 509 | this.opAssign(t); 510 | } 511 | 512 | public var _copy() { 513 | final switch (payloadType()) { 514 | case Type.Integral: 515 | case Type.Boolean: 516 | case Type.Floating: 517 | case Type.Function: 518 | case Type.String: 519 | // since strings are immutable, we can pretend they are value types too 520 | return this; // value types don't need anything special to be copied 521 | 522 | case Type.Array: 523 | var cp; 524 | cp = this._payload._array[]; 525 | return cp; 526 | case Type.Object: 527 | var cp; 528 | if (this._payload._object !is null) 529 | cp._object = this._payload._object.copy; 530 | return cp; 531 | } 532 | } 533 | 534 | public bool opCast(T : bool)() { 535 | final switch (this._type) { 536 | case Type.Object: 537 | return this._payload._object !is null; 538 | case Type.Array: 539 | return this._payload._array.length != 0; 540 | case Type.String: 541 | return this._payload._string.length != 0; 542 | case Type.Integral: 543 | return this._payload._integral != 0; 544 | case Type.Floating: 545 | return this._payload._floating != 0; 546 | case Type.Boolean: 547 | return this._payload._boolean; 548 | case Type.Function: 549 | return this._payload._function !is null; 550 | } 551 | } 552 | 553 | public int opApply(scope int delegate(ref var) dg) { 554 | foreach (i, item; this) 555 | if (auto result = dg(item)) 556 | return result; 557 | return 0; 558 | } 559 | 560 | public int opApply(scope int delegate(var, ref var) dg) { 561 | if (this.payloadType() == Type.Array) { 562 | foreach (i, ref v; this._payload._array) 563 | if (auto result = dg(var(i), v)) 564 | return result; 565 | } else if (this.payloadType() == Type.Object && this._payload._object !is null) { 566 | // FIXME: if it offers input range primitives, we should use them 567 | // FIXME: user defined opApply on the object 568 | foreach (k, ref v; this._payload._object) 569 | if (auto result = dg(var(k), v)) 570 | return result; 571 | } else if (this.payloadType() == Type.String) { 572 | // this is to prevent us from allocating a new string on each character, hopefully limiting that massively 573 | static immutable string chars = makeAscii!(); 574 | 575 | foreach (i, dchar c; this._payload._string) { 576 | var lol = ""; 577 | if (c < 128) 578 | lol._payload._string = chars[c .. c + 1]; 579 | else 580 | lol._payload._string = to!string(""d ~ c); // blargh, how slow can we go? 581 | if (auto result = dg(var(i), lol)) 582 | return result; 583 | } 584 | } 585 | // throw invalid foreach aggregate 586 | 587 | return 0; 588 | } 589 | 590 | public T opCast(T)() { 591 | return this.get!T; 592 | } 593 | 594 | public auto ref putInto(T)(ref T t) { 595 | return t = this.get!T; 596 | } 597 | 598 | // if it is var, we'll just blit it over 599 | public var opAssign(T)(T t) if (!is(T == var)) { 600 | static if (isFloatingPoint!T) { 601 | this._type = Type.Floating; 602 | this._payload._floating = t; 603 | } else static if (isIntegral!T) { 604 | this._type = Type.Integral; 605 | this._payload._integral = t; 606 | } else static if (isCallable!T) { 607 | this._type = Type.Function; 608 | static if (is(T == typeof(this._payload._function))) { 609 | this._payload._function = t; 610 | } else 611 | this._payload._function = delegate var(var _this, var[] args) { 612 | var ret; 613 | 614 | ParameterTypeTuple!T fargs; 615 | foreach (idx, a; fargs) { 616 | if (idx == args.length) 617 | break; 618 | cast(Unqual!(typeof(a)))fargs[idx] = args[idx].get!(typeof(a)); 619 | } 620 | 621 | static if (is(ReturnType!t == void)) { 622 | t(fargs); 623 | } else { 624 | ret = t(fargs); 625 | } 626 | 627 | return ret; 628 | }; 629 | } else static if (isSomeString!T) { 630 | this._type = Type.String; 631 | this._payload._string = to!string(t); 632 | } else static if (is(T : PrototypeObject)) { 633 | // support direct assignment of pre-made implementation objects 634 | // so prewrapped stuff can be easily passed. 635 | this._type = Type.Object; 636 | this._payload._object = t; 637 | } else static if (is(T == class)) { 638 | // auto-wrap other classes with reference semantics 639 | this._type = Type.Object; 640 | this._payload._object = wrapNativeObject(t); 641 | } else static if (is(T == struct) || isAssociativeArray!T) { 642 | // copy structs and assoc arrays by value into a var object 643 | this._type = Type.Object; 644 | auto obj = new PrototypeObject(); 645 | this._payload._object = obj; 646 | 647 | static if (is(T == struct)) 648 | foreach (member; __traits(allMembers, T)) { 649 | static if (__traits(compiles, __traits(getMember, t, member))) { 650 | static if (is(typeof(__traits(getMember, t, member)) == function)) { 651 | // skipping these because the delegate we get isn't going to work anyway; the object may be dead and certainly won't be updated 652 | //this[member] = &__traits(getMember, proxyObject, member); 653 | } else 654 | this[member] = __traits(getMember, t, member); 655 | } 656 | } 657 | else { 658 | // assoc array 659 | foreach (l, v; t) { 660 | this[var(l)] = var(v); 661 | } 662 | } 663 | } else static if (isArray!T) { 664 | this._type = Type.Array; 665 | var[] arr; 666 | arr.length = t.length; 667 | static if (!is(T == void[])) // we can't append a void array but it is nice to support x = []; 668 | foreach (i, item; t) 669 | arr[i] = var(item); 670 | this._payload._array = arr; 671 | } else static if (is(T == bool)) { 672 | this._type = Type.Boolean; 673 | this._payload._boolean = t; 674 | } else static if (isSomeChar!T) { 675 | this._type = Type.String; 676 | this._payload._string = ""; 677 | import std.utf; 678 | 679 | char[4] ugh; 680 | auto size = encode(ugh, t); 681 | this._payload._string = ugh[0 .. size].idup; 682 | } // else static assert(0, "unsupported type"); 683 | 684 | return this; 685 | } 686 | 687 | public size_t opDollar() { 688 | return this.length().get!size_t; 689 | } 690 | 691 | public var opOpAssign(string op, T)(T t) { 692 | if (payloadType() == Type.Object) { 693 | var* operator = this._payload._object._peekMember("opOpAssign", true); 694 | if (operator !is null && operator._type == Type.Function) 695 | return operator.call(this, op, t); 696 | } 697 | 698 | return _op!(this, this, op, T)(t); 699 | } 700 | 701 | public var opBinary(string op, T)(T t) { 702 | var n; 703 | if (payloadType() == Type.Object) { 704 | var* operator = this._payload._object._peekMember("opBinary", true); 705 | if (operator !is null && operator._type == Type.Function) { 706 | return operator.call(this, op, t); 707 | } 708 | } 709 | return _op!(n, this, op, T)(t); 710 | } 711 | 712 | public var opBinaryRight(string op, T)(T s) { 713 | return var(s).opBinary!op(this); 714 | } 715 | 716 | // this in foo 717 | public var* opBinary(string op : "in", T)(T s) { 718 | var rhs = var(s); 719 | return rhs.opBinaryRight!"in"(this); 720 | } 721 | 722 | // foo in this 723 | public var* opBinaryRight(string op : "in", T)(T s) { 724 | // this needs to be an object 725 | return var(s).get!string in this._object._properties; 726 | } 727 | 728 | public var apply(var _this, var[] args) { 729 | if (this.payloadType() == Type.Function) { 730 | assert(this._payload._function !is null); 731 | return this._payload._function(_this, args); 732 | } else if (this.payloadType() == Type.Object) { 733 | assert(this._payload._object !is null, this.toString()); 734 | var* operator = this._payload._object._peekMember("opCall", true); 735 | if (operator !is null && operator._type == Type.Function) 736 | return operator.apply(_this, args); 737 | } 738 | 739 | version (jsvar_throw) 740 | throw new DynamicTypeException(this, Type.Function); 741 | 742 | var ret; 743 | return ret; 744 | } 745 | 746 | public var call(T...)(var _this, T t) { 747 | var[] args; 748 | foreach (a; t) { 749 | args ~= var(a); 750 | } 751 | return this.apply(_this, args); 752 | } 753 | 754 | public var opCall(T...)(T t) { 755 | return this.call(this, t); 756 | } 757 | 758 | public string toString() { 759 | return this.get!string; 760 | } 761 | 762 | public T get(T)() if (!is(T == void)) { 763 | static if (is(T == var)) { 764 | return this; 765 | } else static if (__traits(compiles, T(this))) { 766 | return T(this); 767 | } else static if (__traits(compiles, new T(this))) { 768 | return new T(this); 769 | } else 770 | final switch (payloadType) { 771 | case Type.Boolean: 772 | static if (is(T == bool)) 773 | return this._payload._boolean; 774 | else static if (isFloatingPoint!T || isIntegral!T) 775 | return cast(T)(this._payload._boolean ? 1 : 0); // the cast is for enums, I don't like this so FIXME 776 | else static if (isSomeString!T) 777 | return this._payload._boolean ? "true" : "false"; 778 | else 779 | return T.init; 780 | case Type.Object: 781 | static if (isAssociativeArray!T) { 782 | T ret; 783 | if (this._payload._object !is null) 784 | foreach (k, v; this._payload._object._properties) 785 | ret[to!(KeyType!T)(k)] = v.get!(ValueType!T); 786 | 787 | return ret; 788 | } else static if (is(T : PrototypeObject)) { 789 | // they are requesting an implementation object, just give it to them 790 | return cast(T)this._payload._object; 791 | } else static if (is(T == struct) || is(T == class)) { 792 | // first, we'll try to give them back the native object we have, if we have one 793 | static if (is(T : Object)) { 794 | if (auto wno = cast(WrappedNativeObject)this._payload._object) { 795 | auto no = cast(T)wno.getObject(); 796 | if (no !is null) 797 | return no; 798 | } 799 | } 800 | 801 | // failing that, generic struct or class getting: try to fill in the fields by name 802 | T t; 803 | bool initialized = true; 804 | static if (is(T == class)) { 805 | static if (__traits(compiles, new T())) 806 | t = new T(); 807 | else 808 | initialized = false; 809 | } 810 | 811 | if (initialized) 812 | foreach (i, a; t.tupleof) { 813 | cast(Unqual!(typeof((a))))t.tupleof[i] = this[t.tupleof[i].stringof[2 .. $]].get!(typeof(a)); 814 | } 815 | 816 | return t; 817 | } else static if (isSomeString!T) { 818 | if (this._object !is null) 819 | return this._object.toString(); 820 | return "null"; 821 | } else 822 | return T.init; 823 | case Type.Integral: 824 | static if (isFloatingPoint!T || isIntegral!T) 825 | return to!T(this._payload._integral); 826 | else static if (isSomeString!T) 827 | return to!string(this._payload._integral); 828 | else 829 | return T.init; 830 | case Type.Floating: 831 | static if (isFloatingPoint!T || isIntegral!T) 832 | return to!T(this._payload._floating); 833 | else static if (isSomeString!T) 834 | return to!string(this._payload._floating); 835 | else 836 | return T.init; 837 | case Type.String: 838 | static if (__traits(compiles, to!T(this._payload._string))) { 839 | try { 840 | return to!T(this._payload._string); 841 | } 842 | catch (Exception e) { 843 | return T.init; 844 | } 845 | } else 846 | return T.init; 847 | case Type.Array: 848 | import std.range; 849 | 850 | auto pl = this._payload._array; 851 | static if (isSomeString!T) { 852 | return to!string(pl); 853 | } else static if (isArray!T) { 854 | T ret; 855 | static if (is(ElementType!T == void)) { 856 | static assert(0, "try wrapping the function to get rid of void[] args"); 857 | //alias getType = ubyte; 858 | } else 859 | alias getType = ElementType!T; 860 | foreach (item; pl) 861 | ret ~= item.get!(getType); 862 | return ret; 863 | } else 864 | return T.init; 865 | // is it sane to translate anything else? 866 | case Type.Function: 867 | static if (isSomeString!T) 868 | return ""; 869 | else static if (isDelegate!T) { 870 | // making a local copy because otherwise the delegate might refer to a struct on the stack and get corrupted later or something 871 | auto func = this._payload._function; 872 | 873 | // the static helper lets me pass specific variables to the closure 874 | static T helper(typeof(func) func) { 875 | return delegate ReturnType!T(ParameterTypeTuple!T args) { 876 | var[] arr; 877 | foreach (arg; args) 878 | arr ~= var(arg); 879 | var ret = func(var(null), arr); 880 | static if (is(ReturnType!T == void)) 881 | return; 882 | else 883 | return ret.get!(ReturnType!T); 884 | }; 885 | } 886 | 887 | return helper(func); 888 | 889 | } else 890 | return T.init; 891 | // FIXME: we just might be able to do better for both of these 892 | //break; 893 | } 894 | } 895 | 896 | public T nullCoalesce(T)(T t) { 897 | if (_type == Type.Object && _payload._object is null) 898 | return t; 899 | return this.get!T; 900 | } 901 | 902 | public int opCmp(T)(T t) { 903 | auto f = this.get!real; 904 | static if (is(T == var)) 905 | auto r = t.get!real; 906 | else 907 | auto r = t; 908 | return cast(int)(f - r); 909 | } 910 | 911 | public bool opEquals(T)(T t) { 912 | return this.opEquals(var(t)); 913 | } 914 | 915 | public bool opEquals(T : var)(T t) const { 916 | // FIXME: should this be == or === ? 917 | if (this._type != t._type) 918 | return false; 919 | final switch (this._type) { 920 | case Type.Object: 921 | return _payload._object is t._payload._object; 922 | case Type.Integral: 923 | return _payload._integral == t._payload._integral; 924 | case Type.Boolean: 925 | return _payload._boolean == t._payload._boolean; 926 | case Type.Floating: 927 | return _payload._floating == t._payload._floating; // FIXME: approxEquals? 928 | case Type.String: 929 | return _payload._string == t._payload._string; 930 | case Type.Function: 931 | return _payload._function is t._payload._function; 932 | case Type.Array: 933 | return _payload._array == t._payload._array; 934 | } 935 | assert(0); 936 | } 937 | 938 | public enum Type { 939 | Object, 940 | Array, 941 | Integral, 942 | Floating, 943 | String, 944 | Function, 945 | Boolean 946 | } 947 | 948 | public Type payloadType() { 949 | return _type; 950 | } 951 | 952 | private Type _type; 953 | 954 | private union Payload { 955 | PrototypeObject _object; 956 | var[] _array; 957 | long _integral; 958 | real _floating; 959 | string _string; 960 | bool _boolean; 961 | var delegate(var _this, var[] args) _function; 962 | } 963 | 964 | public void _function(var delegate(var, var[]) f) { 965 | this._payload._function = f; 966 | this._type = Type.Function; 967 | } 968 | 969 | /* 970 | public void _function(var function(var, var[]) f) { 971 | var delegate(var, var[]) dg; 972 | dg.ptr = null; 973 | dg.funcptr = f; 974 | this._function = dg; 975 | } 976 | */ 977 | 978 | public void _object(PrototypeObject obj) { 979 | this._type = Type.Object; 980 | this._payload._object = obj; 981 | } 982 | 983 | public PrototypeObject _object() { 984 | if (this._type == Type.Object) 985 | return this._payload._object; 986 | return null; 987 | } 988 | 989 | package Payload _payload; 990 | 991 | private void _requireType(Type t, string file = __FILE__, size_t line = __LINE__) { 992 | if (this.payloadType() != t) 993 | throw new DynamicTypeException(this, t, file, line); 994 | } 995 | 996 | public var opSlice(var e1, var e2) { 997 | return this.opSlice(e1.get!ptrdiff_t, e2.get!ptrdiff_t); 998 | } 999 | 1000 | public var opSlice(ptrdiff_t e1, ptrdiff_t e2) { 1001 | if (this.payloadType() == Type.Array) { 1002 | if (e1 > _payload._array.length) 1003 | e1 = _payload._array.length; 1004 | if (e2 > _payload._array.length) 1005 | e2 = _payload._array.length; 1006 | return var(_payload._array[e1 .. e2]); 1007 | } 1008 | if (this.payloadType() == Type.String) { 1009 | if (e1 > _payload._string.length) 1010 | e1 = _payload._string.length; 1011 | if (e2 > _payload._string.length) 1012 | e2 = _payload._string.length; 1013 | return var(_payload._string[e1 .. e2]); 1014 | } 1015 | if (this.payloadType() == Type.Object) { 1016 | var operator = this["opSlice"]; 1017 | if (operator._type == Type.Function) { 1018 | return operator.call(this, e1, e2); 1019 | } 1020 | } 1021 | 1022 | // might be worth throwing here too 1023 | return var(null); 1024 | } 1025 | 1026 | public @property ref var opDispatch(string name, string file = __FILE__, size_t line = __LINE__)() { 1027 | return this[name]; 1028 | } 1029 | 1030 | public @property ref var opDispatch(string name, string file = __FILE__, size_t line = __LINE__, T)(T r) { 1031 | return this.opIndexAssign!T(r, name); 1032 | } 1033 | 1034 | public ref var opIndex(var name, string file = __FILE__, size_t line = __LINE__) { 1035 | return opIndex(name.get!string, file, line); 1036 | } 1037 | 1038 | public ref var opIndexAssign(T)(T t, var name, string file = __FILE__, size_t line = __LINE__) { 1039 | return opIndexAssign(t, name.get!string, file, line); 1040 | } 1041 | 1042 | public ref var opIndex(string name, string file = __FILE__, size_t line = __LINE__) { 1043 | // if name is numeric, we should convert to int 1044 | if (name.length && name[0] >= '0' && name[0] <= '9') 1045 | return opIndex(to!size_t(name), file, line); 1046 | 1047 | if (this.payloadType() != Type.Object && name == "prototype") 1048 | return prototype(); 1049 | 1050 | if (name == "typeof") { 1051 | var* tmp = new var; 1052 | *tmp = to!string(this.payloadType()); 1053 | return *tmp; 1054 | } 1055 | 1056 | if (name == "toJson") { 1057 | var* tmp = new var; 1058 | *tmp = to!string(this.toJson()); 1059 | return *tmp; 1060 | } 1061 | 1062 | if (name == "length" && this.payloadType() == Type.String) { 1063 | var* tmp = new var; 1064 | *tmp = _payload._string.length; 1065 | return *tmp; 1066 | } 1067 | if (name == "length" && this.payloadType() == Type.Array) { 1068 | var* tmp = new var; 1069 | *tmp = _payload._array.length; 1070 | return *tmp; 1071 | } 1072 | if (name == "__prop" && this.payloadType() == Type.Object) { 1073 | var* tmp = new var; 1074 | (*tmp)._function = delegate var(var _this, var[] args) { 1075 | if (args.length == 0) 1076 | return var(null); 1077 | if (args.length == 1) { 1078 | auto peek = this._payload._object._peekMember(args[0].get!string, false); 1079 | if (peek is null) 1080 | return var(null); 1081 | else 1082 | return *peek; 1083 | } 1084 | if (args.length == 2) { 1085 | auto peek = this._payload._object._peekMember(args[0].get!string, false); 1086 | if (peek is null) { 1087 | this._payload._object._properties[args[0].get!string] = args[1]; 1088 | return var(null); 1089 | } else { 1090 | *peek = args[1]; 1091 | return *peek; 1092 | } 1093 | 1094 | } 1095 | throw new Exception("too many args"); 1096 | }; 1097 | return *tmp; 1098 | } 1099 | 1100 | PrototypeObject from; 1101 | if (this.payloadType() == Type.Object) 1102 | from = _payload._object; 1103 | else { 1104 | var pt = this.prototype(); 1105 | assert(pt.payloadType() == Type.Object); 1106 | from = pt._payload._object; 1107 | } 1108 | 1109 | if (from is null) 1110 | throw new DynamicTypeException(var(null), Type.Object, file, line); 1111 | return from._getMember(name, true, false, file, line); 1112 | } 1113 | 1114 | public ref var opIndexAssign(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) { 1115 | if (name.length && name[0] >= '0' && name[0] <= '9') 1116 | return opIndexAssign(t, to!size_t(name), file, line); 1117 | _requireType(Type.Object); // FIXME? 1118 | if (_payload._object is null) 1119 | throw new DynamicTypeException(var(null), Type.Object, file, line); 1120 | 1121 | return this._payload._object._setMember(name, var(t), false, false, false, file, line); 1122 | } 1123 | 1124 | public ref var opIndexAssignNoOverload(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) { 1125 | if (name.length && name[0] >= '0' && name[0] <= '9') 1126 | return opIndexAssign(t, to!size_t(name), file, line); 1127 | _requireType(Type.Object); // FIXME? 1128 | if (_payload._object is null) 1129 | throw new DynamicTypeException(var(null), Type.Object, file, line); 1130 | 1131 | return this._payload._object._setMember(name, var(t), false, false, true, file, line); 1132 | } 1133 | 1134 | public ref var opIndex(size_t idx, string file = __FILE__, size_t line = __LINE__) { 1135 | if (_type == Type.Array) { 1136 | auto arr = this._payload._array; 1137 | if (idx < arr.length) 1138 | return arr[idx]; 1139 | } else if (_type == Type.Object) { 1140 | // objects might overload opIndex 1141 | var* n = new var(); 1142 | if ("opIndex" in this) 1143 | *n = this["opIndex"](idx); 1144 | return *n; 1145 | } 1146 | version (jsvar_throw) 1147 | throw new DynamicTypeException(this, Type.Array, file, line); 1148 | var* n = new var(); 1149 | return *n; 1150 | } 1151 | 1152 | public ref var opIndexAssign(T)(T t, size_t idx, string file = __FILE__, size_t line = __LINE__) { 1153 | if (_type == Type.Array) { 1154 | alias arr = this._payload._array; 1155 | if (idx >= this._payload._array.length) 1156 | this._payload._array.length = idx + 1; 1157 | this._payload._array[idx] = t; 1158 | return this._payload._array[idx]; 1159 | } 1160 | version (jsvar_throw) 1161 | throw new DynamicTypeException(this, Type.Array, file, line); 1162 | var* n = new var(); 1163 | return *n; 1164 | } 1165 | 1166 | ref var _getOwnProperty(string name, string file = __FILE__, size_t line = __LINE__) { 1167 | if (_type == Type.Object) { 1168 | if (_payload._object !is null) { 1169 | auto peek = this._payload._object._peekMember(name, false); 1170 | if (peek !is null) 1171 | return *peek; 1172 | } 1173 | } 1174 | version (jsvar_throw) 1175 | throw new DynamicTypeException(this, Type.Object, file, line); 1176 | var* n = new var(); 1177 | return *n; 1178 | } 1179 | 1180 | @property static var emptyObject(PrototypeObject prototype = null) { 1181 | var v; 1182 | v._type = Type.Object; 1183 | v._payload._object = new PrototypeObject(); 1184 | v._payload._object.prototype = prototype; 1185 | return v; 1186 | } 1187 | 1188 | @property PrototypeObject prototypeObject() { 1189 | var v = prototype(); 1190 | if (v._type == Type.Object) 1191 | return v._payload._object; 1192 | return null; 1193 | } 1194 | 1195 | // what I call prototype is more like what Mozilla calls __proto__, but tbh I think this is better so meh 1196 | @property ref var prototype() { 1197 | static var _arrayPrototype; 1198 | static var _functionPrototype; 1199 | static var _stringPrototype; 1200 | 1201 | final switch (payloadType()) { 1202 | case Type.Array: 1203 | assert(_arrayPrototype._type == Type.Object); 1204 | if (_arrayPrototype._payload._object is null) { 1205 | _arrayPrototype._object = new PrototypeObject(); 1206 | } 1207 | 1208 | return _arrayPrototype; 1209 | case Type.Function: 1210 | assert(_functionPrototype._type == Type.Object); 1211 | if (_functionPrototype._payload._object is null) { 1212 | _functionPrototype._object = new PrototypeObject(); 1213 | } 1214 | 1215 | return _functionPrototype; 1216 | case Type.String: 1217 | assert(_stringPrototype._type == Type.Object); 1218 | if (_stringPrototype._payload._object is null) { 1219 | auto p = new PrototypeObject(); 1220 | _stringPrototype._object = p; 1221 | 1222 | var replaceFunction; 1223 | replaceFunction._type = Type.Function; 1224 | replaceFunction._function = (var _this, var[] args) { 1225 | string s = _this.toString(); 1226 | import std.array : replace; 1227 | 1228 | return var(std.array.replace(s, args[0].toString(), args[1].toString())); 1229 | }; 1230 | 1231 | p._properties["replace"] = replaceFunction; 1232 | } 1233 | 1234 | return _stringPrototype; 1235 | case Type.Object: 1236 | if (_payload._object) 1237 | return _payload._object._prototype; 1238 | // FIXME: should we do a generic object prototype? 1239 | break; 1240 | case Type.Integral: 1241 | case Type.Floating: 1242 | case Type.Boolean: 1243 | // these types don't have prototypes 1244 | } 1245 | 1246 | var* v = new var(null); 1247 | return *v; 1248 | } 1249 | 1250 | @property static var emptyArray() { 1251 | var v; 1252 | v._type = Type.Array; 1253 | return v; 1254 | } 1255 | 1256 | static var fromJson(string json) { 1257 | auto decoded = parseJSON(json); 1258 | return var.fromJsonValue(decoded); 1259 | } 1260 | 1261 | static var fromJsonValue(JSONValue v) { 1262 | var ret; 1263 | 1264 | final switch (v.type) { 1265 | case JSON_TYPE.STRING: 1266 | ret = v.str; 1267 | break; 1268 | case JSON_TYPE.UINTEGER: 1269 | ret = v.uinteger; 1270 | break; 1271 | case JSON_TYPE.INTEGER: 1272 | ret = v.integer; 1273 | break; 1274 | case JSON_TYPE.FLOAT: 1275 | ret = v.floating; 1276 | break; 1277 | case JSON_TYPE.OBJECT: 1278 | ret = var.emptyObject; 1279 | foreach (k, val; v.object) { 1280 | ret[k] = var.fromJsonValue(val); 1281 | } 1282 | break; 1283 | case JSON_TYPE.ARRAY: 1284 | ret = var.emptyArray; 1285 | ret._payload._array.length = v.array.length; 1286 | foreach (idx, item; v.array) { 1287 | ret._payload._array[idx] = var.fromJsonValue(item); 1288 | } 1289 | break; 1290 | case JSON_TYPE.TRUE: 1291 | ret = true; 1292 | break; 1293 | case JSON_TYPE.FALSE: 1294 | ret = false; 1295 | break; 1296 | case JSON_TYPE.NULL: 1297 | ret = null; 1298 | break; 1299 | } 1300 | 1301 | return ret; 1302 | } 1303 | 1304 | string toJson() { 1305 | auto v = toJsonValue(); 1306 | return toJSON(&v); 1307 | } 1308 | 1309 | JSONValue toJsonValue() { 1310 | JSONValue val; 1311 | final switch (payloadType()) { 1312 | case Type.Boolean: 1313 | version (new_std_json) 1314 | val = this._payload._boolean; 1315 | else { 1316 | if (this._payload._boolean) 1317 | val.type = JSON_TYPE.TRUE; 1318 | else 1319 | val.type = JSON_TYPE.FALSE; 1320 | } 1321 | break; 1322 | case Type.Object: 1323 | version (new_std_json) { 1324 | if (_payload._object is null) { 1325 | val = null; 1326 | } else { 1327 | val = _payload._object.toJsonValue(); 1328 | } 1329 | } else { 1330 | if (_payload._object is null) { 1331 | val.type = JSON_TYPE.NULL; 1332 | } else { 1333 | val.type = JSON_TYPE.OBJECT; 1334 | foreach (k, v; _payload._object._properties) 1335 | val.object[k] = v.toJsonValue(); 1336 | } 1337 | } 1338 | break; 1339 | case Type.String: 1340 | version (new_std_json) { 1341 | } else { 1342 | val.type = JSON_TYPE.STRING; 1343 | } 1344 | val.str = _payload._string; 1345 | break; 1346 | case Type.Integral: 1347 | version (new_std_json) { 1348 | } else { 1349 | val.type = JSON_TYPE.INTEGER; 1350 | } 1351 | val.integer = _payload._integral; 1352 | break; 1353 | case Type.Floating: 1354 | version (new_std_json) { 1355 | } else { 1356 | val.type = JSON_TYPE.FLOAT; 1357 | } 1358 | val.floating = _payload._floating; 1359 | break; 1360 | case Type.Array: 1361 | auto a = _payload._array; 1362 | JSONValue[] tmp; 1363 | tmp.length = a.length; 1364 | foreach (i, v; a) { 1365 | tmp[i] = v.toJsonValue(); 1366 | } 1367 | 1368 | version (new_std_json) { 1369 | val = tmp; 1370 | } else { 1371 | val.type = JSON_TYPE.ARRAY; 1372 | val.array = tmp; 1373 | } 1374 | break; 1375 | case Type.Function: 1376 | version (new_std_json) 1377 | val = null; 1378 | else 1379 | val.type = JSON_TYPE.NULL; // ideally we would just skip it entirely... 1380 | break; 1381 | } 1382 | return val; 1383 | } 1384 | } 1385 | 1386 | class PrototypeObject { 1387 | string name; 1388 | var _prototype; 1389 | 1390 | package PrototypeObject _secondary; // HACK don't use this 1391 | 1392 | PrototypeObject prototype() { 1393 | if (_prototype.payloadType() == var.Type.Object) 1394 | return _prototype._payload._object; 1395 | return null; 1396 | } 1397 | 1398 | PrototypeObject prototype(PrototypeObject set) { 1399 | this._prototype._object = set; 1400 | return set; 1401 | } 1402 | 1403 | override string toString() { 1404 | 1405 | var* ts = _peekMember("toString", true); 1406 | if (ts) { 1407 | var _this; 1408 | _this._object = this; 1409 | return (*ts).call(_this).get!string; 1410 | } 1411 | 1412 | JSONValue val; 1413 | version (new_std_json) { 1414 | JSONValue[string] tmp; 1415 | foreach (k, v; this._properties) 1416 | tmp[k] = v.toJsonValue(); 1417 | val.object = tmp; 1418 | } else { 1419 | val.type = JSON_TYPE.OBJECT; 1420 | foreach (k, v; this._properties) 1421 | val.object[k] = v.toJsonValue(); 1422 | } 1423 | 1424 | return toJSON(&val); 1425 | } 1426 | 1427 | var[string] _properties; 1428 | 1429 | PrototypeObject copy() { 1430 | auto n = new PrototypeObject(); 1431 | n.prototype = this.prototype; 1432 | n.name = this.name; 1433 | foreach (k, v; _properties) { 1434 | n._properties[k] = v._copy; 1435 | } 1436 | return n; 1437 | } 1438 | 1439 | PrototypeObject copyPropertiesFrom(PrototypeObject p) { 1440 | foreach (k, v; p._properties) { 1441 | this._properties[k] = v._copy; 1442 | } 1443 | return this; 1444 | } 1445 | 1446 | var* _peekMember(string name, bool recurse) { 1447 | if (name == "prototype") 1448 | return &_prototype; 1449 | 1450 | auto curr = this; 1451 | 1452 | // for the secondary hack 1453 | bool triedOne = false; 1454 | // for the secondary hack 1455 | PrototypeObject possibleSecondary; 1456 | 1457 | tryAgain: do { 1458 | auto prop = name in curr._properties; 1459 | if (prop is null) { 1460 | // the secondary hack is to do more scoping in the script, it is really hackish 1461 | if (possibleSecondary is null) 1462 | possibleSecondary = curr._secondary; 1463 | 1464 | if (!recurse) 1465 | break; 1466 | else 1467 | curr = curr.prototype; 1468 | } else 1469 | return prop; 1470 | } 1471 | while (curr); 1472 | 1473 | if (possibleSecondary !is null) { 1474 | curr = possibleSecondary; 1475 | if (!triedOne) { 1476 | triedOne = true; 1477 | goto tryAgain; 1478 | } 1479 | } 1480 | 1481 | return null; 1482 | } 1483 | 1484 | // FIXME: maybe throw something else 1485 | /*package*/ 1486 | ref var _getMember(string name, bool recurse, bool throwOnFailure, string file = __FILE__, size_t line = __LINE__) { 1487 | var* mem = _peekMember(name, recurse); 1488 | 1489 | if (mem !is null) { 1490 | // If it is a property, we need to call the getter on it 1491 | if ((*mem).payloadType == var.Type.Object && cast(PropertyPrototype)(*mem)._payload._object) { 1492 | auto prop = cast(PropertyPrototype)(*mem)._payload._object; 1493 | return prop.get; 1494 | } 1495 | return *mem; 1496 | } 1497 | 1498 | mem = _peekMember("opIndex", recurse); 1499 | if (mem !is null) { 1500 | auto n = new var; 1501 | *n = ((*mem)(name)); 1502 | return *n; 1503 | } 1504 | 1505 | // if we're here, the property was not found, so let's implicitly create it 1506 | if (throwOnFailure) 1507 | throw new Exception("no such property " ~ name, file, line); 1508 | var n; 1509 | this._properties[name] = n; 1510 | return this._properties[name]; 1511 | } 1512 | 1513 | // FIXME: maybe throw something else 1514 | /*package*/ 1515 | ref var _setMember(string name, var t, bool recurse, bool throwOnFailure, bool suppressOverloading, 1516 | string file = __FILE__, size_t line = __LINE__) { 1517 | var* mem = _peekMember(name, recurse); 1518 | 1519 | if (mem !is null) { 1520 | // Property check - the setter should be proxied over to it 1521 | if ((*mem).payloadType == var.Type.Object && cast(PropertyPrototype)(*mem)._payload._object) { 1522 | auto prop = cast(PropertyPrototype)(*mem)._payload._object; 1523 | return prop.set(t); 1524 | } 1525 | *mem = t; 1526 | return *mem; 1527 | } 1528 | 1529 | if (!suppressOverloading) { 1530 | mem = _peekMember("opIndexAssign", true); 1531 | if (mem !is null) { 1532 | auto n = new var; 1533 | *n = ((*mem)(t, name)); 1534 | return *n; 1535 | } 1536 | } 1537 | 1538 | // if we're here, the property was not found, so let's implicitly create it 1539 | if (throwOnFailure) 1540 | throw new Exception("no such property " ~ name, file, line); 1541 | this._properties[name] = t; 1542 | return this._properties[name]; 1543 | } 1544 | 1545 | JSONValue toJsonValue() { 1546 | JSONValue val; 1547 | JSONValue[string] tmp; 1548 | foreach (k, v; this._properties) 1549 | tmp[k] = v.toJsonValue(); 1550 | val = tmp; 1551 | return val; 1552 | } 1553 | 1554 | public int opApply(scope int delegate(var, ref var) dg) { 1555 | foreach (k, v; this._properties) { 1556 | if (v.payloadType == var.Type.Object && cast(PropertyPrototype)v._payload._object) 1557 | v = (cast(PropertyPrototype)v._payload._object).get; 1558 | if (auto result = dg(var(k), v)) 1559 | return result; 1560 | } 1561 | return 0; 1562 | } 1563 | } 1564 | 1565 | // A property is a special type of object that can only be set by assigning 1566 | // one of these instances to foo.child._object. When foo.child is accessed and it 1567 | // is an instance of PropertyPrototype, it will return the getter. When foo.child is 1568 | // set (excluding direct assignments through _type), it will call the setter. 1569 | class PropertyPrototype : PrototypeObject { 1570 | var delegate() getter; 1571 | void delegate(var) setter; 1572 | this(var delegate() getter, void delegate(var) setter) { 1573 | this.getter = getter; 1574 | this.setter = setter; 1575 | } 1576 | 1577 | override string toString() { 1578 | return get.toString(); 1579 | } 1580 | 1581 | ref var get() { 1582 | var* g = new var(); 1583 | *g = getter(); 1584 | return *g; 1585 | } 1586 | 1587 | ref var set(var t) { 1588 | setter(t); 1589 | return get; 1590 | } 1591 | 1592 | override JSONValue toJsonValue() { 1593 | return get.toJsonValue(); 1594 | } 1595 | } 1596 | 1597 | class DynamicTypeException : Exception { 1598 | this(var v, var.Type required, string file = __FILE__, size_t line = __LINE__) { 1599 | import std.string; 1600 | 1601 | if (v.payloadType() == required) 1602 | super(format("Tried to use null as a %s", required), file, line); 1603 | else 1604 | super(format("Tried to use %s as a %s", v.payloadType(), required), file, line); 1605 | } 1606 | } 1607 | 1608 | template makeAscii() { 1609 | string helper() { 1610 | string s; 1611 | foreach (i; 0 .. 128) 1612 | s ~= cast(char)i; 1613 | return s; 1614 | } 1615 | 1616 | enum makeAscii = helper(); 1617 | } 1618 | 1619 | // just a base class we can reference when looking for native objects 1620 | class WrappedNativeObject : PrototypeObject { 1621 | TypeInfo wrappedType; 1622 | abstract Object getObject(); 1623 | } 1624 | 1625 | template helper(alias T) { 1626 | alias helper = T; 1627 | } 1628 | 1629 | /// Wraps a class. If you are manually managing the memory, remember the jsvar may keep a reference to the object; don't free it! 1630 | /// 1631 | /// To use this: var a = wrapNativeObject(your_d_object); OR var a = your_d_object; 1632 | /// 1633 | /// By default, it will wrap all methods and members with a public or greater protection level. The second template parameter can filter things differently. FIXME implement this 1634 | /// 1635 | /// That may be done automatically with opAssign in the future. 1636 | WrappedNativeObject wrapNativeObject(Class)(Class obj) if (is(Class == class)) { 1637 | return new class WrappedNativeObject { 1638 | override Object getObject() { 1639 | return obj; 1640 | } 1641 | 1642 | this() { 1643 | wrappedType = typeid(obj); 1644 | // wrap the other methods 1645 | // and wrap members as scriptable properties 1646 | 1647 | foreach (memberName; __traits(allMembers, Class)) { 1648 | static if (is(typeof(__traits(getMember, obj, memberName)) type)) 1649 | static if (is(typeof(__traits(getMember, obj, memberName)))) { 1650 | static if (is(type == function)) { 1651 | _properties[memberName] = &__traits(getMember, obj, memberName); 1652 | } else { 1653 | // if it has a type but is not a function, it is prolly a member 1654 | _properties[memberName] = new PropertyPrototype(() => var(__traits(getMember, obj, memberName)), (var v) { 1655 | __traits(getMember, obj, memberName) = v.get!(type); 1656 | }); 1657 | } 1658 | } 1659 | } 1660 | } 1661 | }; 1662 | } 1663 | 1664 | /// Wraps a struct by reference. The pointer is stored - be sure the struct doesn't get freed or go out of scope! 1665 | /// 1666 | /// BTW: structs by value can be put in vars with var.opAssign and var.get. It will generate an object with the same fields. The difference is changes to the jsvar won't be reflected in the original struct and native methods won't work if you do it that way. 1667 | WrappedNativeObject wrapNativeObject(Struct)(Struct* obj) if (is(Struct == struct)) { 1668 | return null; // FIXME 1669 | } 1670 | -------------------------------------------------------------------------------- /source/dwin/backend/bindmanager.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.bindmanager; 2 | 3 | import dwin.log; 4 | 5 | alias Key = uint; 6 | 7 | //XXX: This is the same as XCB_MOD_MASK. 8 | enum Modifier : ushort { 9 | None = 0, 10 | Shift = 1 << 0, 11 | Lock = 1 << 1, 12 | Control = 1 << 2, 13 | Mod1 = 1 << 3, 14 | Mod2 = 1 << 4, 15 | Mod3 = 1 << 5, 16 | Mod4 = 1 << 6, 17 | Mod5 = 1 << 7 18 | } 19 | 20 | enum MouseButton : ubyte { 21 | None = 0, 22 | Button1, 23 | Button2, 24 | Button3, 25 | Button4, 26 | Button5 27 | } 28 | 29 | abstract class BindManager { 30 | alias MapBind = void delegate(bool isPressed); 31 | struct KeyBind { 32 | Key key; 33 | Modifier modifier; 34 | MouseButton mouseButton; 35 | 36 | this(Modifier modifier, Key key, MouseButton mouseButton) { 37 | this.modifier = modifier; 38 | this.key = key; 39 | this.mouseButton = mouseButton; 40 | } 41 | 42 | @property bool IsValid() { 43 | return !!key || !!mouseButton; 44 | } 45 | 46 | string toString() { 47 | import std.format : format; 48 | 49 | return format("KeyBind(%s, %x, %s)", modifier, key, mouseButton); 50 | } 51 | } 52 | 53 | abstract void Rebind(); 54 | 55 | void Map(string keys, MapBind func) { 56 | KeyBind keyBind = toKeyBind(keys); 57 | if (!keyBind.IsValid) { 58 | Log.MainLogger.Error("Invalid mapping (%s): '%s'", keyBind, keys); 59 | return; 60 | } 61 | 62 | Map(keyBind, func); 63 | } 64 | 65 | void Unmap(string keys) { 66 | KeyBind keyBind = toKeyBind(keys); 67 | if (!keyBind.IsValid) { 68 | Log.MainLogger.Error("Invalid mapping (%s): '%s'", keyBind, keys); 69 | return; 70 | } 71 | 72 | Unmap(keyBind); 73 | } 74 | 75 | bool IsBinded(string keys) { 76 | KeyBind keyBind = toKeyBind(keys); 77 | if (!keyBind.IsValid) { 78 | Log.MainLogger.Error("Invalid mapping (%s): '%s'", keyBind, keys); 79 | return false; 80 | } 81 | 82 | return IsBinded(keyBind); 83 | } 84 | 85 | bool IsBinded(KeyBind keys) { 86 | return !!(keys in mappings); 87 | } 88 | 89 | abstract void Map(KeyBind keyBind, MapBind func); 90 | abstract void Unmap(KeyBind keyBind); 91 | protected: 92 | IKeyParser keyParser; 93 | MapBind[KeyBind] mappings; 94 | 95 | KeyBind toKeyBind(string keys) { 96 | import std.array; 97 | import std.string; 98 | import std.algorithm; 99 | 100 | auto split = keys.split("+").map!(strip); 101 | 102 | MouseButton mouse = keyParser.ParseMouseButton(split[$ - 1]); 103 | Key key = (mouse == MouseButton.None) ? keyParser.ParseKey(split[$ - 1]) : Key(0); 104 | Modifier mod = Modifier.None; 105 | 106 | foreach (m; split[0 .. $ - 1]) { 107 | const Modifier mo = keyParser.ParseModifier(m); 108 | if (!mo) 109 | return KeyBind(Modifier.None, Key(0), MouseButton.None); 110 | mod |= mo; 111 | } 112 | 113 | return KeyBind(cast(Modifier)(mod & ~keyParser.NumlockMask), key, mouse); 114 | } 115 | } 116 | 117 | interface IKeyParser { 118 | Key ParseKey(string key); 119 | Modifier ParseModifier(string mod); 120 | MouseButton ParseMouseButton(string button); 121 | @property uint NumlockMask(); 122 | @property uint MouseMasks(); 123 | } 124 | -------------------------------------------------------------------------------- /source/dwin/backend/container.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.container; 2 | 3 | import dwin.backend.layout; 4 | import dwin.backend.window; 5 | import dwin.backend.screen; 6 | 7 | enum PositionPart { 8 | First, 9 | Second, 10 | Third 11 | } 12 | 13 | struct GridPosition { 14 | PositionPart x; 15 | PositionPart y; 16 | } 17 | 18 | abstract class Container { 19 | public: 20 | abstract void Add(Container container); 21 | abstract void Remove(Container container); 22 | abstract void Move(short x, short y) { 23 | } 24 | 25 | abstract void Resize(ushort width, ushort height); 26 | abstract void MoveResize(short x, short y, ushort width, ushort height) { 27 | Move(x, y); 28 | } 29 | 30 | abstract void Show(bool eventBased = true); 31 | abstract void Hide(bool eventBased = true); 32 | 33 | abstract void Update(); 34 | abstract void Focus(); 35 | 36 | GridPosition GetGridPosition(int x, int y) { 37 | const int pointX = x / (width / 4) + 1; 38 | const int pointY = y / (height / 4) + 1; 39 | 40 | GridPosition pos; 41 | 42 | /* 43 | --------------- 44 | |1,1| 2,1 |3,1| 45 | |---|-----|---| 46 | |1,2| 2,2 |3,2| 47 | |---|-----|---| 48 | |1,3| 2,3 |3,3| 49 | --------------- 50 | */ 51 | 52 | if (!!(pointX & 0b100)) 53 | pos.x = PositionPart.Third; 54 | else if (!!(pointX & 0b10)) 55 | pos.x = PositionPart.Second; 56 | else if (!!(pointX & 0b1)) 57 | pos.x = PositionPart.First; 58 | 59 | if (!!(pointY & 0b100)) 60 | pos.y = PositionPart.Third; 61 | else if (!!(pointY & 0b10)) 62 | pos.y = PositionPart.Second; 63 | else if (!!(pointY & 0b1)) 64 | pos.y = PositionPart.First; 65 | 66 | return pos; 67 | } 68 | 69 | @property ref.Screen Screen() { 70 | return screen; 71 | } 72 | 73 | @property ref Layout Parent() { 74 | return parent; 75 | } 76 | 77 | @property ref bool Dead() { 78 | return dead; 79 | } 80 | 81 | @property short X() { 82 | return x; 83 | } 84 | 85 | @property short X(short x) { 86 | Move(x, y); 87 | return this.x; 88 | } 89 | 90 | @property short Y() { 91 | return y; 92 | } 93 | 94 | @property short Y(short y) { 95 | Move(x, y); 96 | return this.y; 97 | } 98 | 99 | @property ushort Width() { 100 | return width; 101 | } 102 | 103 | @property ushort Width(ushort width) { 104 | Resize(width, height); 105 | return this.width; 106 | } 107 | 108 | @property ushort Height() { 109 | return height; 110 | } 111 | 112 | @property ushort Height(ushort height) { 113 | Resize(width, height); 114 | return this.height; 115 | } 116 | 117 | @property abstract bool IsVisible(); 118 | 119 | protected: 120 | .Screen screen; 121 | Layout parent; 122 | bool dead; 123 | short x; 124 | short y; 125 | ushort width; 126 | ushort height; 127 | } 128 | -------------------------------------------------------------------------------- /source/dwin/backend/engine.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.engine; 2 | 3 | import dwin.event; 4 | 5 | import dwin.backend.container; 6 | import dwin.backend.layout; 7 | import dwin.backend.mouse; 8 | import dwin.backend.screen; 9 | import dwin.backend.window; 10 | import dwin.backend.workspace; 11 | 12 | import dwin.backend.bindmanager; 13 | 14 | abstract class Engine { 15 | public: 16 | abstract void DoEvent(); 17 | 18 | Window FindWindow(short x, short y) { 19 | import dwin.backend.layout : Layout; 20 | import dwin.backend.container : Container; 21 | import dwin.backend.workspace : Workspace; 22 | 23 | Window traverseWin(Window window, short x, short y) { 24 | window.Update(); 25 | if (x >= window.X && x <= window.X + window.Width && y >= window.Y && y <= window.Y + window.Height) 26 | return window; 27 | else 28 | return null; 29 | } 30 | 31 | Window traverseCon(Container con, short x, short y) { 32 | if (!con.IsVisible) 33 | return null; 34 | if (auto window = cast(Window)con) { 35 | if (auto win = traverseWin(window, x, y)) 36 | return win; 37 | } else if (auto layout = cast(Layout)con) 38 | foreach (container; layout.Containers) 39 | if (auto win = traverseCon(container, x, y)) 40 | return win; 41 | return null; 42 | } 43 | 44 | Window traverseWorkspace(Workspace workspace, short x, short y) { 45 | if (auto win = traverseCon(workspace.OnTop, x, y)) 46 | return win; 47 | if (auto win = traverseCon(workspace.Root, x, y)) 48 | return win; 49 | return null; 50 | } 51 | 52 | Window traverseLayout(Layout layout, short x, short y) { 53 | if (!layout.IsVisible) 54 | return null; 55 | foreach (container; layout.Containers) 56 | if (auto win = traverseCon(container, x, y)) 57 | return win; 58 | return null; 59 | } 60 | 61 | Window traverseScreen(Screen screen, short x, short y) { 62 | if (auto win = traverseLayout(screen.OnTop, x, y)) 63 | return win; 64 | foreach (workspace; screen.Workspaces) 65 | if (auto win = traverseWorkspace(workspace, x, y)) 66 | return win; 67 | return null; 68 | } 69 | 70 | foreach (screen; screens) 71 | if (auto win = traverseScreen(screen, x, y)) 72 | return win; 73 | return null; 74 | } 75 | 76 | Screen FindScreen(short x, short y) { 77 | foreach (screen; screens) 78 | if (screen.X <= x && screen.X + screen.Width >= x && screen.Y <= y && screen.Y + screen.Height >= y) 79 | return screen; 80 | return screens[0]; 81 | } 82 | 83 | void RegisterTick(void delegate() cb) { 84 | tickCallbacks ~= cb; 85 | } 86 | 87 | @property.Mouse Mouse() { 88 | return mouse; 89 | } 90 | 91 | @property.BindManager BindManager() { 92 | return bindManager; 93 | } 94 | 95 | @property Window Root() { 96 | return root; 97 | } 98 | 99 | @property Screen[] Screens() { 100 | return screens; 101 | } 102 | 103 | @property ref auto OnNewWindow() { 104 | return onNewWindow; 105 | } 106 | 107 | @property ref auto OnRemoveWindow() { 108 | return onRemoveWindow; 109 | } 110 | 111 | @property ref auto OnRequestShowWindow() { 112 | return onRequestShowWindow; 113 | } 114 | 115 | @property ref auto OnNotifyHideWindow() { 116 | return onNotifyHideWindow; 117 | } 118 | 119 | @property ref auto OnRequestMoveWindow() { 120 | return onRequestMoveWindow; 121 | } 122 | 123 | @property ref auto OnRequestResizeWindow() { 124 | return onRequestResizeWindow; 125 | } 126 | 127 | @property ref auto OnRequestBorderSizeWindow() { 128 | return onRequestBorderSizeWindow; 129 | } 130 | 131 | @property ref auto OnRequestSiblingWindow() { 132 | return onRequestSiblingWindow; 133 | } 134 | 135 | @property ref auto OnRequestStackModeWindow() { 136 | return onRequestStackModeWindow; 137 | } 138 | 139 | @property ref auto OnMouseMotion() { 140 | return onMouseMotion; 141 | } 142 | 143 | protected: 144 | .Mouse mouse; 145 | .BindManager bindManager; 146 | 147 | void delegate()[] tickCallbacks; 148 | 149 | Window root; 150 | Screen[] screens; 151 | Window[] windows; 152 | 153 | Event!(Window) onNewWindow; 154 | Event!(Window) onRemoveWindow; 155 | Event!(Window) onRequestShowWindow; 156 | Event!(Window) onNotifyHideWindow; 157 | Event!(Window, short, short) onRequestMoveWindow; 158 | Event!(Window, ushort, ushort) onRequestResizeWindow; 159 | Event!(Window, ushort) onRequestBorderSizeWindow; 160 | Event!(Window, Window) onRequestSiblingWindow; 161 | Event!(Window, ubyte) onRequestStackModeWindow; 162 | Event!(short, short, uint) onMouseMotion; 163 | } 164 | -------------------------------------------------------------------------------- /source/dwin/backend/layout.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.layout; 2 | 3 | import dwin.backend.engine; 4 | import dwin.backend.container; 5 | import dwin.backend.mouse; 6 | 7 | enum LayoutType { 8 | Float, 9 | Tile, 10 | Tab 11 | } 12 | 13 | abstract class Layout : Container { 14 | public: 15 | this(Engine engine) { 16 | this.engine = engine; 17 | visible = false; 18 | } 19 | 20 | override void Add(Container container) { 21 | containers ~= container; 22 | container.Parent = this; 23 | } 24 | 25 | override void Remove(Container container) { 26 | import std.algorithm.searching : countUntil; 27 | 28 | const long idx = containers.countUntil(container); 29 | if (idx == -1) 30 | return; 31 | for (ulong i = idx; i < containers.length - 1; i++) // container.length - 1 can't be underflow because the assert makes sure it's atleast 1 32 | containers[i] = containers[i + 1]; 33 | containers.length--; 34 | } 35 | 36 | abstract void MouseMovePressed(Container target, Mouse mouse); 37 | abstract void MouseResizeReleased(Container target, Mouse mouse); 38 | abstract void MouseMoveReleased(Container target, Mouse mouse); 39 | abstract void MouseResizePressed(Container target, Mouse mouse); 40 | abstract void MouseMotion(Container target, Mouse mouse); 41 | abstract void RequestShow(Container container); 42 | abstract void NotifyHide(Container container); 43 | 44 | override void Update() { 45 | } 46 | 47 | override void Show(bool eventBased = true) { 48 | visible = true; 49 | foreach (container; Containers) 50 | container.Show(eventBased); 51 | } 52 | 53 | override void Hide(bool eventBased = true) { 54 | visible = false; 55 | foreach (container; Containers) 56 | container.Hide(eventBased); 57 | } 58 | 59 | override void Move(short x, short y) { 60 | this.x = x; 61 | this.y = y; 62 | } 63 | 64 | override void Resize(ushort width, ushort height) { 65 | this.width = width; 66 | this.height = height; 67 | } 68 | 69 | override void MoveResize(short x, short y, ushort width, ushort height) { 70 | this.x = x; 71 | this.y = y; 72 | this.width = width; 73 | this.height = height; 74 | } 75 | 76 | override void Focus() { 77 | } 78 | 79 | @property Container[] Containers() { 80 | return containers; 81 | } 82 | 83 | @property override bool IsVisible() { 84 | return visible; 85 | } 86 | 87 | protected: 88 | Engine engine; 89 | bool visible; 90 | Container[] containers; 91 | } 92 | -------------------------------------------------------------------------------- /source/dwin/backend/mouse.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.mouse; 2 | 3 | import dwin.backend.mousestyle; 4 | 5 | enum MouseStyles { 6 | Normal, 7 | Resizing, 8 | Moving 9 | } 10 | 11 | abstract class Mouse { 12 | public: 13 | abstract void Update(); 14 | abstract void Move(short x, short y); 15 | 16 | /// This will _NOT_ update the real position of the mouse 17 | void Set(short x, short y) { 18 | this.x = x; 19 | this.y = y; 20 | } 21 | 22 | @property short X(short x) { 23 | this.x = x; 24 | Move(x, y); 25 | return this.x; 26 | } 27 | 28 | @property short X() { 29 | return this.x; 30 | } 31 | 32 | @property short Y(short y) { 33 | this.y = y; 34 | Move(x, y); 35 | return this.y; 36 | } 37 | 38 | @property short Y() { 39 | return this.y; 40 | } 41 | 42 | @property bool[5] Buttons() { 43 | return buttons; 44 | } 45 | 46 | @property void Style(MouseStyles style) { 47 | styles[style].Apply(); 48 | } 49 | 50 | override string toString() { 51 | import std.format : format; 52 | 53 | return format("Mouse[X: %s, Y: %s, Buttons: '%(%s, %)']", x, y, buttons); 54 | } 55 | 56 | protected: 57 | short x; 58 | short y; 59 | bool[5] buttons; 60 | 61 | MouseStyle[MouseStyles.max + 1] styles; 62 | } 63 | -------------------------------------------------------------------------------- /source/dwin/backend/mousestyle.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.mousestyle; 2 | 3 | abstract class MouseStyle { 4 | abstract void Apply(); 5 | } 6 | -------------------------------------------------------------------------------- /source/dwin/backend/screen.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.screen; 2 | 3 | import dwin.backend.engine; 4 | import dwin.backend.container; 5 | import dwin.backend.window; 6 | import dwin.backend.workspace; 7 | 8 | import dwin.layout.floatinglayout; 9 | 10 | class Screen { 11 | public: 12 | this(Engine engine, string name, short x, short y, ushort width, ushort height, Workspace[] workspaces = null) { 13 | this.engine = engine; 14 | this.name = name; 15 | this.x = x; 16 | this.y = y; 17 | this.width = width; 18 | this.height = height; 19 | onTop = new FloatingLayout(engine); 20 | onTop.MoveResize(x, y, width, height); 21 | onTop.Show(); 22 | if (!workspaces) 23 | workspaces = [new Workspace(engine, "Workspace1"), new Workspace(engine, "Workspace2"), new Workspace(engine, "Workspace3")]; 24 | this.workspaces = workspaces; 25 | foreach (workspace; this.workspaces) 26 | workspace.MoveResize(x, y, width, height); 27 | 28 | workspaces[this.currentWorkspace].Show(); 29 | } 30 | 31 | void Add(Container container) { 32 | workspaces[currentWorkspace].Add(container); 33 | } 34 | 35 | void Remove(Container container) { 36 | workspaces[currentWorkspace].Remove(container); 37 | } 38 | 39 | void AddOnTop(Container container) { 40 | onTop.Add(container); 41 | } 42 | 43 | void RemoveOnTop(Container container) { 44 | onTop.Remove(container); 45 | } 46 | 47 | void MoveResize(short x, short y, ushort width, ushort height) { 48 | this.x = x; 49 | this.y = y; 50 | this.width = width; 51 | this.height = height; 52 | foreach (workspace; workspaces) 53 | workspace.MoveResize(x, y, width, height); 54 | } 55 | 56 | @property ref string Name() { 57 | return name; 58 | } 59 | 60 | @property short X() { 61 | return x; 62 | } 63 | 64 | @property short Y() { 65 | return y; 66 | } 67 | 68 | @property ushort Width() { 69 | return width; 70 | } 71 | 72 | @property ushort Height() { 73 | return height; 74 | } 75 | 76 | @property long CurrentWorkspace() { 77 | return currentWorkspace; 78 | } 79 | 80 | @property long CurrentWorkspace(long currentWorkspace) { 81 | long mod(long a, long b) { 82 | import std.math : abs; 83 | 84 | return (a >= 0) ? a % b : (b - abs(a % b)) % b; 85 | } 86 | 87 | currentWorkspace = mod(currentWorkspace, workspaces.length); 88 | 89 | workspaces[currentWorkspace].Show(false); 90 | workspaces[this.currentWorkspace].Hide(false); 91 | this.currentWorkspace = currentWorkspace; 92 | 93 | return this.currentWorkspace; 94 | } 95 | 96 | @property ref Workspace[] Workspaces() { 97 | return workspaces; 98 | } 99 | 100 | @property FloatingLayout OnTop() { 101 | return onTop; 102 | } 103 | 104 | override string toString() { 105 | import std.format : format; 106 | 107 | return format("Screen[Name: %s, X: %s, Y: %s, Width: %s, Height: %s]", name, x, y, width, height); 108 | } 109 | 110 | protected: 111 | Engine engine; 112 | string name; 113 | short x; 114 | short y; 115 | ushort width; 116 | ushort height; 117 | 118 | long currentWorkspace; 119 | 120 | Workspace[] workspaces; 121 | FloatingLayout onTop; 122 | } 123 | -------------------------------------------------------------------------------- /source/dwin/backend/window.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.window; 2 | 3 | import dwin.backend.layout; 4 | import dwin.backend.container; 5 | import dwin.backend.workspace; 6 | import dwin.log; 7 | 8 | struct Strut { 9 | uint left; 10 | uint right; 11 | uint top; 12 | uint bottom; 13 | 14 | uint left_start_y; 15 | uint left_end_y; 16 | uint right_start_y; 17 | uint right_end_y; 18 | uint top_start_x; 19 | uint top_end_x; 20 | uint bottom_start_x; 21 | uint bottom_end_x; 22 | } 23 | 24 | abstract class Window : Container { 25 | public: 26 | override void Add(Container container) { 27 | Log.MainLogger.Error("Trying to add a container to a window, redirecting it to the parent!"); 28 | parent.Add(container); 29 | } 30 | 31 | override void Remove(Container container) { 32 | Log.MainLogger.Error("Trying to remove a container from a window, redirecting it to the parent!"); 33 | parent.Remove(container); 34 | } 35 | 36 | //abstract void Update(); 37 | //abstract void Move(short x, short y); 38 | //abstract void Resize(ushort width, ushort height); 39 | //abstract void MoveResize(short x, short y, ushort width, ushort height); 40 | //abstract void Show(bool eventBased = true); 41 | //abstract void Hide(bool eventBased = true); 42 | //abstract void Focus(); 43 | abstract void Close(); 44 | 45 | @property abstract string Title(); 46 | //@property abstract bool IsVisible(); 47 | 48 | override string toString() { 49 | import std.format : format; 50 | 51 | return format("%s %s: %s", cast(void*)this, typeid(this).name, Title); 52 | } 53 | 54 | @property.Strut Strut() { 55 | return strut; 56 | } 57 | 58 | @property uint Desktop() { 59 | return desktop; 60 | } 61 | 62 | @property ref.Workspace Workspace() { 63 | return workspace; 64 | } 65 | 66 | @property abstract bool IsDock(); 67 | @property abstract bool IsSticky(); 68 | 69 | protected: 70 | .Strut strut; 71 | uint desktop; 72 | .Workspace workspace; 73 | } 74 | -------------------------------------------------------------------------------- /source/dwin/backend/workspace.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.workspace; 2 | 3 | import dwin.backend.engine; 4 | import dwin.backend.container; 5 | import dwin.backend.window; 6 | import dwin.backend.layout; 7 | import dwin.layout.floatinglayout; 8 | import dwin.layout.tilinglayout; 9 | 10 | class Workspace { 11 | public: 12 | this(Engine engine, string name) { 13 | this.engine = engine; 14 | this.name = name; 15 | onTop = new FloatingLayout(engine); 16 | root = new TilingLayout(engine); 17 | } 18 | 19 | void Add(Container container) { 20 | if (auto window = cast(Window)container) { 21 | window.Workspace = this; 22 | activeWindow = window; 23 | } 24 | 25 | root.Add(container); 26 | 27 | if (auto layout = cast(TilingLayout)root) 28 | layout.ShouldDie(); 29 | } 30 | 31 | void Remove(Container container) { 32 | root.Remove(container); 33 | if (auto window = cast(Window)container) { 34 | window.Workspace = null; 35 | if (activeWindow == window) 36 | activeWindow = null; 37 | } 38 | if (auto layout = cast(TilingLayout)root) 39 | layout.ShouldDie(); 40 | } 41 | 42 | void AddOnTop(Container container) { 43 | onTop.Add(container); 44 | } 45 | 46 | void RemoveOnTop(Container container) { 47 | onTop.Remove(container); 48 | } 49 | 50 | void Show(bool eventBased = true) { 51 | onTop.Show(eventBased); 52 | root.Show(eventBased); 53 | } 54 | 55 | void Hide(bool eventBased = true) { 56 | onTop.Hide(eventBased); 57 | root.Hide(eventBased); 58 | } 59 | 60 | void Move(short x, short y) { 61 | onTop.Move(x, y); 62 | root.Move(x, y); 63 | } 64 | 65 | void Resize(ushort width, ushort height) { 66 | onTop.Resize(width, height); 67 | root.Resize(width, height); 68 | } 69 | 70 | void MoveResize(short x, short y, ushort width, ushort height) { 71 | onTop.MoveResize(x, y, width, height); 72 | root.MoveResize(x, y, width, height); 73 | } 74 | 75 | @property ref string Name() { 76 | return name; 77 | } 78 | 79 | @property Layout Root() { 80 | return root; 81 | } 82 | 83 | @property FloatingLayout OnTop() { 84 | return onTop; 85 | } 86 | 87 | @property ref Window ActiveWindow() { 88 | return activeWindow; 89 | } 90 | 91 | protected: 92 | Engine engine; 93 | string name; 94 | Layout root; 95 | FloatingLayout onTop; 96 | Window activeWindow; 97 | } 98 | -------------------------------------------------------------------------------- /source/dwin/backend/xcb/atom.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.xcb.atom; 2 | 3 | import xcb.xcb; 4 | import dwin.backend.xcb.xcb; 5 | import dwin.backend.xcb.xcbwindow; 6 | import std.string : toStringz; 7 | import std.conv : to; 8 | 9 | struct Atom { 10 | this(XCB xcb, string name, bool createOnMissing = true) { 11 | atom = XCB_ATOM_NONE; 12 | this.xcb = xcb; 13 | //dfmt off 14 | xcb_intern_atom_cookie_t c = xcb_intern_atom_unchecked( 15 | xcb.Connection, 16 | !createOnMissing, 17 | cast(ushort)name.length, 18 | name.toStringz 19 | ); 20 | //dfmt on 21 | if (auto reply = xcb_intern_atom_reply(xcb.Connection, c, null)) { 22 | atom = reply.atom; 23 | xcb_free(reply); 24 | } 25 | } 26 | 27 | this(xcb_atom_t atom) { 28 | this.atom = atom; 29 | } 30 | 31 | Atom[] GetAtom(XCBWindow window) { 32 | Atom[] ret; 33 | //dfmt off 34 | xcb_get_property_cookie_t c = xcb_get_property_unchecked( 35 | xcb.Connection, 36 | 0, 37 | window.InternalWindow, 38 | atom, 39 | XCB_ATOM_ATOM, 40 | 0, 41 | 0 42 | ); 43 | //dfmt on 44 | if (auto reply = xcb_get_property_reply(xcb.Connection, c, null)) { 45 | xcb_atom_t[] tmp = (cast(xcb_atom_t*)xcb_get_property_value(reply))[0 .. xcb_get_property_value_length(reply)]; 46 | foreach (atom; tmp) 47 | ret ~= Atom(atom); 48 | xcb_free(reply); 49 | } 50 | return ret; 51 | } 52 | 53 | XCBWindow[] GetWindow(XCBWindow window) { 54 | XCBWindow[] ret; 55 | //dfmt off 56 | xcb_get_property_cookie_t c = xcb_get_property_unchecked( 57 | xcb.Connection, 58 | 0, 59 | window.InternalWindow, 60 | atom, 61 | XCB_ATOM_WINDOW, 62 | 0, 63 | 0 64 | ); 65 | //dfmt on 66 | if (auto reply = xcb_get_property_reply(xcb.Connection, c, null)) { 67 | xcb_window_t[] tmp = (cast(xcb_window_t*)xcb_get_property_value(reply))[0 .. xcb_get_property_value_length(reply)]; 68 | foreach (win; tmp) 69 | ret ~= new XCBWindow(xcb, win); 70 | xcb_free(reply); 71 | } 72 | return ret; 73 | } 74 | 75 | string GetString(XCBWindow window) { 76 | string ret = null; 77 | //dfmt off 78 | xcb_get_property_cookie_t c = xcb_get_property_unchecked( 79 | xcb.Connection, 80 | 0, 81 | window.InternalWindow, 82 | atom, 83 | XCB_ATOM_STRING, 84 | 0, 85 | 0 86 | ); 87 | //dfmt on 88 | if (auto reply = xcb_get_property_reply(xcb.Connection, c, null)) { 89 | char[] tmp = (cast(char*)xcb_get_property_value(reply))[0 .. xcb_get_property_value_length(reply)]; 90 | ret = tmp.to!string; 91 | xcb_free(reply); 92 | } 93 | return ret; 94 | } 95 | 96 | void Change(XCBWindow window, Atom[] value) { 97 | //dfmt off 98 | xcb_change_property( 99 | xcb.Connection, 100 | XCB_PROP_MODE_REPLACE, 101 | window.InternalWindow, 102 | atom, 103 | XCB_ATOM_ATOM, 104 | 32, 105 | cast(uint)value.length, 106 | cast(ubyte*)value.ptr 107 | ); 108 | //dfmt on 109 | } 110 | 111 | void Change(XCBWindow window, Atom value) { 112 | //dfmt off 113 | xcb_change_property( 114 | xcb.Connection, 115 | XCB_PROP_MODE_REPLACE, 116 | window.InternalWindow, 117 | atom, 118 | XCB_ATOM_ATOM, 119 | 32, 120 | 1, 121 | cast(ubyte*)&value 122 | ); 123 | //dfmt on 124 | } 125 | 126 | void Change(XCBWindow window, XCBWindow value) { 127 | //dfmt off 128 | xcb_change_property( 129 | xcb.Connection, 130 | XCB_PROP_MODE_REPLACE, 131 | window.InternalWindow, 132 | atom, 133 | XCB_ATOM_WINDOW, 134 | 32, 135 | 1, 136 | cast(ubyte*)&value 137 | ); 138 | //dfmt on 139 | } 140 | 141 | void Change(XCBWindow window, string value) { 142 | //dfmt off 143 | xcb_change_property( 144 | xcb.Connection, 145 | XCB_PROP_MODE_REPLACE, 146 | window.InternalWindow, 147 | atom, 148 | XCB_ATOM_STRING, 149 | 8, 150 | cast(uint)value.length, 151 | value.toStringz 152 | ); 153 | //dfmt on 154 | } 155 | 156 | void Delete(XCBWindow window) { 157 | xcb_delete_property(xcb.Connection, window.InternalWindow, atom); 158 | } 159 | 160 | @property bool IsValid() { 161 | return atom != XCB_ATOM_NONE; 162 | } 163 | 164 | bool opEquals(xcb_atom_t other) { 165 | return atom == other; 166 | } 167 | 168 | alias atom this; 169 | xcb_atom_t atom; 170 | XCB xcb; 171 | } 172 | -------------------------------------------------------------------------------- /source/dwin/backend/xcb/event.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.xcb.event; 2 | 3 | enum XCBEvent { 4 | XCB_NULL_EVENT = 0, // aka XCB_NONE 5 | XCB_KEY_PRESS = 2, 6 | XCB_KEY_RELEASE = 3, 7 | XCB_BUTTON_PRESS = 4, 8 | XCB_BUTTON_RELEASE = 5, 9 | XCB_MOTION_NOTIFY = 6, 10 | XCB_ENTER_NOTIFY = 7, 11 | XCB_LEAVE_NOTIFY = 8, 12 | XCB_FOCUS_IN = 9, 13 | XCB_FOCUS_OUT = 10, 14 | XCB_KEYMAP_NOTIFY = 11, 15 | XCB_EXPOSE = 12, 16 | XCB_GRAPHICS_EXPOSURE = 13, 17 | XCB_NO_EXPOSURE = 14, 18 | XCB_VISIBILITY_NOTIFY = 15, 19 | XCB_CREATE_NOTIFY = 16, 20 | XCB_DESTROY_NOTIFY = 17, 21 | XCB_UNMAP_NOTIFY = 18, 22 | XCB_MAP_NOTIFY = 19, 23 | XCB_MAP_REQUEST = 20, 24 | XCB_REPARENT_NOTIFY = 21, 25 | XCB_CONFIGURE_NOTIFY = 22, 26 | XCB_CONFIGURE_REQUEST = 23, 27 | XCB_GRAVITY_NOTIFY = 24, 28 | XCB_RESIZE_REQUEST = 25, 29 | XCB_CIRCULATE_NOTIFY = 26, 30 | XCB_CIRCULATE_REQUEST = 27, 31 | XCB_PROPERTY_NOTIFY = 28, 32 | XCB_SELECTION_CLEAR = 29, 33 | XCB_SELECTION_REQUEST = 30, 34 | XCB_SELECTION_NOTIFY = 31, 35 | XCB_COLORMAP_NOTIFY = 32, 36 | XCB_CLIENT_MESSAGE = 33, 37 | XCB_MAPPING_NOTIFY = 34, 38 | XCB_GE_GENERIC = 35, 39 | XCB_REQUEST = 1, 40 | XCB_VALUE = 2, 41 | XCB_WINDOW = 3, 42 | XCB_PIXMAP = 4, 43 | XCB_ATOM = 5, 44 | XCB_CURSOR = 6, 45 | XCB_FONT = 7, 46 | XCB_MATCH = 8, 47 | XCB_DRAWABLE = 9, 48 | XCB_ACCESS = 10, 49 | XCB_ALLOC = 11, 50 | XCB_COLORMAP = 12, 51 | XCB_G_CONTEXT = 13, 52 | XCB_ID_CHOICE = 14, 53 | XCB_NAME = 15, 54 | XCB_LENGTH = 16, 55 | XCB_IMPLEMENTATION = 17, 56 | XCB_CREATE_WINDOW = 1, 57 | XCB_CHANGE_WINDOW_ATTRIBUTES = 2, 58 | XCB_GET_WINDOW_ATTRIBUTES = 3, 59 | XCB_DESTROY_WINDOW = 4, 60 | XCB_DESTROY_SUBWINDOWS = 5, 61 | XCB_CHANGE_SAVE_SET = 6, 62 | XCB_REPARENT_WINDOW = 7, 63 | XCB_MAP_WINDOW = 8, 64 | XCB_MAP_SUBWINDOWS = 9, 65 | XCB_UNMAP_WINDOW = 10, 66 | XCB_UNMAP_SUBWINDOWS = 11, 67 | XCB_CONFIGURE_WINDOW = 12, 68 | XCB_CIRCULATE_WINDOW = 13, 69 | XCB_GET_GEOMETRY = 14, 70 | XCB_QUERY_TREE = 15, 71 | XCB_INTERN_ATOM = 16, 72 | XCB_GET_ATOM_NAME = 17, 73 | XCB_CHANGE_PROPERTY = 18, 74 | XCB_DELETE_PROPERTY = 19, 75 | XCB_GET_PROPERTY = 20, 76 | XCB_LIST_PROPERTIES = 21, 77 | XCB_SET_SELECTION_OWNER = 22, 78 | XCB_GET_SELECTION_OWNER = 23, 79 | XCB_CONVERT_SELECTION = 24, 80 | XCB_SEND_EVENT = 25, 81 | XCB_GRAB_POINTER = 26, 82 | XCB_UNGRAB_POINTER = 27, 83 | XCB_GRAB_BUTTON = 28, 84 | XCB_UNGRAB_BUTTON = 29, 85 | XCB_CHANGE_ACTIVE_POINTER_GRAB = 30, 86 | XCB_GRAB_KEYBOARD = 31, 87 | XCB_UNGRAB_KEYBOARD = 32, 88 | XCB_GRAB_KEY = 33, 89 | XCB_UNGRAB_KEY = 34, 90 | XCB_ALLOW_EVENTS = 35, 91 | XCB_GRAB_SERVER = 36, 92 | XCB_UNGRAB_SERVER = 37, 93 | XCB_QUERY_POINTER = 38, 94 | XCB_GET_MOTION_EVENTS = 39, 95 | XCB_TRANSLATE_COORDINATES = 40, 96 | XCB_WARP_POINTER = 41, 97 | XCB_SET_INPUT_FOCUS = 42, 98 | XCB_GET_INPUT_FOCUS = 43, 99 | XCB_QUERY_KEYMAP = 44, 100 | XCB_OPEN_FONT = 45, 101 | XCB_CLOSE_FONT = 46, 102 | XCB_QUERY_FONT = 47, 103 | XCB_QUERY_TEXT_EXTENTS = 48, 104 | XCB_LIST_FONTS = 49, 105 | XCB_LIST_FONTS_WITH_INFO = 50, 106 | XCB_SET_FONT_PATH = 51, 107 | XCB_GET_FONT_PATH = 52, 108 | XCB_CREATE_PIXMAP = 53, 109 | XCB_FREE_PIXMAP = 54, 110 | XCB_CREATE_GC = 55, 111 | XCB_CHANGE_GC = 56, 112 | XCB_COPY_GC = 57, 113 | XCB_SET_DASHES = 58, 114 | XCB_SET_CLIP_RECTANGLES = 59, 115 | XCB_FREE_GC = 60, 116 | XCB_CLEAR_AREA = 61, 117 | XCB_COPY_AREA = 62, 118 | XCB_COPY_PLANE = 63, 119 | XCB_POLY_POINT = 64, 120 | XCB_POLY_LINE = 65, 121 | XCB_POLY_SEGMENT = 66, 122 | XCB_POLY_RECTANGLE = 67, 123 | XCB_POLY_ARC = 68, 124 | XCB_FILL_POLY = 69, 125 | XCB_POLY_FILL_RECTANGLE = 70, 126 | XCB_POLY_FILL_ARC = 71, 127 | XCB_PUT_IMAGE = 72, 128 | XCB_GET_IMAGE = 73, 129 | XCB_POLY_TEXT_8 = 74, 130 | XCB_POLY_TEXT_16 = 75, 131 | XCB_IMAGE_TEXT_8 = 76, 132 | XCB_IMAGE_TEXT_16 = 77, 133 | XCB_CREATE_COLORMAP = 78, 134 | XCB_FREE_COLORMAP = 79, 135 | XCB_COPY_COLORMAP_AND_FREE = 80, 136 | XCB_INSTALL_COLORMAP = 81, 137 | XCB_UNINSTALL_COLORMAP = 82, 138 | XCB_LIST_INSTALLED_COLORMAPS = 83, 139 | XCB_ALLOC_COLOR = 84, 140 | XCB_ALLOC_NAMED_COLOR = 85, 141 | XCB_ALLOC_COLOR_CELLS = 86, 142 | XCB_ALLOC_COLOR_PLANES = 87, 143 | XCB_FREE_COLORS = 88, 144 | XCB_STORE_COLORS = 89, 145 | XCB_STORE_NAMED_COLOR = 90, 146 | XCB_QUERY_COLORS = 91, 147 | XCB_LOOKUP_COLOR = 92, 148 | XCB_CREATE_CURSOR = 93, 149 | XCB_CREATE_GLYPH_CURSOR = 94, 150 | XCB_FREE_CURSOR = 95, 151 | XCB_RECOLOR_CURSOR = 96, 152 | XCB_QUERY_BEST_SIZE = 97, 153 | XCB_QUERY_EXTENSION = 98, 154 | XCB_LIST_EXTENSIONS = 99, 155 | XCB_CHANGE_KEYBOARD_MAPPING = 100, 156 | XCB_GET_KEYBOARD_MAPPING = 101, 157 | XCB_CHANGE_KEYBOARD_CONTROL = 102, 158 | XCB_GET_KEYBOARD_CONTROL = 103, 159 | XCB_BELL = 104, 160 | XCB_CHANGE_POINTER_CONTROL = 105, 161 | XCB_GET_POINTER_CONTROL = 106, 162 | XCB_SET_SCREEN_SAVER = 107, 163 | XCB_GET_SCREEN_SAVER = 108, 164 | XCB_CHANGE_HOSTS = 109, 165 | XCB_LIST_HOSTS = 110, 166 | XCB_SET_ACCESS_CONTROL = 111, 167 | XCB_SET_CLOSE_DOWN_MODE = 112, 168 | XCB_KILL_CLIENT = 113, 169 | XCB_ROTATE_PROPERTIES = 114, 170 | XCB_FORCE_SCREEN_SAVER = 115, 171 | XCB_SET_POINTER_MAPPING = 116, 172 | XCB_GET_POINTER_MAPPING = 117, 173 | XCB_SET_MODIFIER_MAPPING = 118, 174 | XCB_GET_MODIFIER_MAPPING = 119, 175 | XCB_NO_OPERATION = 127 176 | } 177 | -------------------------------------------------------------------------------- /source/dwin/backend/xcb/xcb.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.xcb.xcb; 2 | 3 | import dwin.log; 4 | import dwin.event; 5 | 6 | import xcb.xcb; 7 | import xcb.keysyms; 8 | import xcb.ewmh; 9 | import xcb.xinerama; 10 | import dwin.backend.engine; 11 | import dwin.backend.screen; 12 | import dwin.backend.window; 13 | import dwin.backend.xcb.atom; 14 | import dwin.backend.xcb.xcbbindmanager; 15 | import dwin.backend.xcb.event; 16 | import dwin.backend.xcb.xcbwindow; 17 | import dwin.backend.xcb.xcbmouse; 18 | import dwin.util.data; 19 | 20 | import std.traits; 21 | import std.algorithm.searching; 22 | import std.algorithm.mutation; 23 | public import core.stdc.stdlib : xcb_free = free; 24 | import std.conv; 25 | 26 | class XCB : Engine { 27 | public: 28 | struct AtomName { 29 | ulong id; 30 | string name; 31 | } 32 | 33 | //dfmt off 34 | enum WMAtoms : AtomName { 35 | Protocols = AtomName(0, "WM_PROTOCOLS"), 36 | DeleteWindow = AtomName(1, "WM_DELETE_WINDOW"), 37 | State = AtomName(2, "WM_STATE"), 38 | TakeFocus = AtomName(3, "WM_TAKE_FOCUS") 39 | } 40 | 41 | enum NETAtoms : AtomName { 42 | Supported = AtomName(0, "_NET_SUPPORTED"), 43 | WMName = AtomName(1, "_NET_WM_NAME"), 44 | WMState = AtomName(2, "_NET_WM_STATE"), 45 | WMFullscreen = AtomName(3, "_NET_WM_STATE_FULLSCREEN"), 46 | ActiveWindow = AtomName(4, "_NET_ACTIVE_WINDOW"), 47 | WMWindowType = AtomName(5, "_NET_WM_WINDOW_TYPE"), 48 | WMWindowTypeDialog = AtomName(6, "_NET_WM_WINDOW_TYPE_DIALOG"), 49 | ClientList = AtomName(7, "_NET_CLIENT_LIST"), 50 | WMStrut = AtomName(8, "_NET_WM_STRUT"), 51 | WMStrutPartial = AtomName(9, "_NET_WM_STRUT_PARTIAL") 52 | } 53 | //dfmt on 54 | 55 | this(int display) { 56 | log = Log.MainLogger; 57 | connection = xcb_connect((":" ~ to!string(display)).toStringz, &defaultScreen); 58 | conError err = cast(conError)xcb_connection_has_error(connection); 59 | if (err) 60 | log.Fatal("Error while connecting to X11, %s", err); 61 | log.Info("Successfully connect to X11!"); 62 | 63 | symbols = xcb_key_symbols_alloc(connection); 64 | auto cookie = xcb_ewmh_init_atoms(connection, &ewmhConnection); 65 | if (!xcb_ewmh_init_atoms_replies(&ewmhConnection, cookie, null)) 66 | log.Fatal("Could not connect to ewmh!"); 67 | 68 | root = new XCBWindow(this, getScreen(defaultScreen).root); 69 | mouse = new XCBMouse(this); 70 | 71 | bindManager = new XCBBindManager(this); 72 | 73 | checkOtherWM(); 74 | setup(); 75 | } 76 | 77 | ~this() { 78 | xcb_key_symbols_free(symbols); 79 | xcb_disconnect(connection); 80 | } 81 | 82 | override void DoEvent() { 83 | scope (exit) 84 | foreach (cb; tickCallbacks) 85 | cb(); 86 | const xcb_generic_event_t* e = xcb_poll_for_event(connection); 87 | if (!e) 88 | return; 89 | XCBEvent ev = cast(XCBEvent)(e.response_type & ~0x80); 90 | 91 | switch (ev) with (XCBEvent) { 92 | default: 93 | log.Error("Event caught: %s\tNo action done!", ev); 94 | break; 95 | 96 | case XCB_NULL_EVENT: // Do nothing 97 | break; 98 | 99 | case XCB_ENTER_NOTIFY: 100 | auto notify = cast(xcb_enter_notify_event_t*)e; 101 | Window window = findWindow(notify.event); 102 | if (window && window.Workspace) 103 | window.Workspace.ActiveWindow = window; 104 | break; 105 | 106 | case XCB_PROPERTY_NOTIFY: 107 | auto notify = cast(xcb_property_notify_event_t*)e; 108 | 109 | if (notify.state == XCB_PROPERTY_DELETE) 110 | break; // Ignore 111 | 112 | log.Debug("notify: %s", *notify); 113 | 114 | auto reply = xcb_get_atom_name_reply(connection, xcb_get_atom_name(connection, notify.atom), null); 115 | if (reply) { 116 | auto length = xcb_get_atom_name_name_length(reply); 117 | auto name = xcb_get_atom_name_name(reply); 118 | 119 | log.Debug("\tAtom: %s", name[0 .. length]); 120 | } 121 | 122 | /*foreach (wmAtom; EnumMembers!WMAtoms) 123 | if (lookupWMAtoms[wmAtom.id] == notify.atom) 124 | log.Debug("\t WMAtom: %s", wmAtom.name); 125 | 126 | foreach (netAtom; EnumMembers!NETAtoms) 127 | if (lookupNETAtoms[netAtom.id] == notify.atom) 128 | log.Debug("\t NETAtom: %s", netAtom.name);*/ 129 | 130 | Window window = findWindow(notify.window); 131 | if (window) 132 | window.Update(); 133 | break; 134 | 135 | case XCB_MAPPING_NOTIFY: 136 | auto notify = cast(xcb_mapping_notify_event_t*)e; 137 | 138 | xcb_refresh_keyboard_mapping(symbols, notify); 139 | if (notify.request == XCB_MAPPING_NOTIFY) 140 | BindManager.Rebind(); 141 | break; 142 | 143 | case XCB_MOTION_NOTIFY: 144 | auto notify = cast(xcb_motion_notify_event_t*)e; 145 | onMouseMotion(notify.root_x, notify.root_y, notify.time); 146 | xcb_allow_events(connection, XCB_ALLOW_REPLAY_POINTER, notify.time); 147 | break; 148 | 149 | case XCB_BUTTON_PRESS: 150 | xcb_button_press_event_t* press = cast(xcb_button_press_event_t*)e; 151 | //auto window = findWindow(press.child); 152 | BindManager.HandleButtonPressEvent(press); 153 | xcb_allow_events(connection, XCB_ALLOW_REPLAY_POINTER, press.time); 154 | break; 155 | 156 | case XCB_BUTTON_RELEASE: 157 | xcb_button_release_event_t* release = cast(xcb_button_release_event_t*)e; 158 | //auto window = findWindow(release.child); 159 | BindManager.HandleButtonReleaseEvent(release); 160 | xcb_allow_events(connection, XCB_ALLOW_REPLAY_POINTER, release.time); 161 | break; 162 | 163 | case XCB_KEY_PRESS: 164 | BindManager.HandleKeyPressEvent(cast(xcb_key_press_event_t*)e); 165 | break; 166 | 167 | case XCB_KEY_RELEASE: 168 | BindManager.HandleKeyReleaseEvent(cast(xcb_key_release_event_t*)e); 169 | break; 170 | 171 | case XCB_CREATE_NOTIFY: 172 | auto notify = cast(xcb_create_notify_event_t*)e; 173 | auto window = new XCBWindow(this, notify.window); 174 | uint values = XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW; 175 | xcb_change_window_attributes(connection, notify.window, XCB_CW_EVENT_MASK, &values); 176 | 177 | log.Error("CreateNotify: %s %s", *notify, window); 178 | windows ~= window; 179 | onNewWindow(window); 180 | break; 181 | 182 | case XCB_DESTROY_NOTIFY: 183 | auto notify = cast(xcb_destroy_notify_event_t*)e; 184 | 185 | ulong idx; 186 | auto window = findWindow(notify.window, &idx); 187 | if (!window) 188 | break; 189 | log.Error("DestroyNotify: %s %s", *notify, window); 190 | window.Dead = true; 191 | onRemoveWindow(window); 192 | if (window.Workspace && window.Workspace.ActiveWindow == window) 193 | window.Workspace.ActiveWindow = null; 194 | window.destroy; 195 | windows = windows.remove(idx); 196 | break; 197 | 198 | case XCB_MAP_NOTIFY: // Skip check of this because every time we call xcb_map_window, it will trigger this event 199 | auto notify = cast(xcb_map_notify_event_t*)e; 200 | if (notify.override_redirect) 201 | log.Warning("Map notify with override_redirect!"); 202 | break; 203 | 204 | case XCB_MAP_REQUEST: 205 | auto map = cast(xcb_map_request_event_t*)e; 206 | auto window = findWindow(map.window); 207 | log.Error("MapRequest: %s %s", *map, window); 208 | if (window) 209 | onRequestShowWindow(window); 210 | break; 211 | 212 | case XCB_UNMAP_NOTIFY: 213 | auto unmap = cast(xcb_unmap_notify_event_t*)e; 214 | auto window = findWindow(unmap.window); 215 | log.Error("UnmapNotify: %s %s", *unmap, window); 216 | if (window) 217 | onNotifyHideWindow(window); 218 | break; 219 | 220 | case XCB_CONFIGURE_NOTIFY: 221 | auto notify = cast(xcb_configure_notify_event_t*)e; 222 | if (notify.window == Root.InternalWindow) { 223 | log.Error("Didn't handle window configure notify, PLEASE FIX"); //TODO: Fix this 224 | if (Root.Width != notify.width || Root.Height != notify.height) 225 | log.Info("Root window went from %sx%s to %sx%s", Root.Width, Root.Height, notify.width, notify.height); 226 | } 227 | break; 228 | 229 | case XCB_CONFIGURE_REQUEST: 230 | auto request = cast(xcb_configure_request_event_t*)e; 231 | XCBWindow window = findWindow(request.window); 232 | if (!window) 233 | break; 234 | 235 | bool check1 = !!(request.value_mask & XCB_CONFIG_WINDOW_X); 236 | bool check2 = !!(request.value_mask & XCB_CONFIG_WINDOW_Y); 237 | 238 | if (check1 || check2) 239 | onRequestMoveWindow(window, (check1) ? request.x : window.X, (check2) ? request.y : window.Y); 240 | 241 | check1 = !!(request.value_mask & XCB_CONFIG_WINDOW_WIDTH); 242 | check2 = !!(request.value_mask & XCB_CONFIG_WINDOW_HEIGHT); 243 | if (check1 || check2) 244 | onRequestResizeWindow(window, (check1) ? request.width : window.Width, (check2) ? request.height : window.Height); 245 | 246 | if (request.value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) 247 | onRequestBorderSizeWindow(window, request.border_width); 248 | if (request.value_mask & XCB_CONFIG_WINDOW_SIBLING) { 249 | auto sibling = findWindow(request.sibling); 250 | onRequestSiblingWindow(window, sibling); 251 | } 252 | if (request.value_mask & XCB_CONFIG_WINDOW_STACK_MODE) 253 | onRequestStackModeWindow(window, request.stack_mode); 254 | break; 255 | 256 | case XCB_CLIENT_MESSAGE: 257 | auto msg = cast(xcb_client_message_event_t*)e; 258 | log.Info("ClientMessage: format: %s, window: %s, type: %s, data: %s", msg.format, msg.window, msg.type, msg.data); 259 | break; 260 | } 261 | 262 | Flush(); 263 | xcb_free(cast(void*)e); 264 | } 265 | 266 | void Flush() { 267 | xcb_flush(connection); 268 | } 269 | 270 | @property uint RootEventMask() { 271 | return rootEventMask; 272 | } 273 | 274 | @property xcb_connection_t* Connection() { 275 | return connection; 276 | } 277 | 278 | @property int DefaultScreen() { 279 | return defaultScreen; 280 | } 281 | 282 | @property Atom[] LookupWMAtoms() { 283 | return lookupWMAtoms; 284 | } 285 | 286 | @property Atom[] LookupNETAtoms() { 287 | return lookupNETAtoms; 288 | } 289 | 290 | @property xcb_key_symbols_t* Symbols() { 291 | return symbols; 292 | } 293 | 294 | @property xcb_ewmh_connection_t* EWMH() { 295 | return &ewmhConnection; 296 | } 297 | 298 | override @property XCBWindow Root() { 299 | return cast(XCBWindow)root; 300 | } 301 | 302 | override @property XCBBindManager BindManager() { 303 | return cast(XCBBindManager)bindManager; 304 | } 305 | 306 | private: 307 | enum conError { 308 | Error = 1, 309 | ClosedExtNotSupported, 310 | ClosedMemInsufficient, 311 | ClosedReqLenExceed, 312 | ClosedParseErr, 313 | ClosedInvalidScreen 314 | } 315 | 316 | //dfmt off 317 | uint rootEventMask = 318 | XCB_EVENT_MASK_STRUCTURE_NOTIFY | // CirculateNotify, ConfigureNotify, DestroyNotify, GravityNotify, MapNotify, ReparentNotify, UnmapNotify 319 | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | // CirculateNotify, ConfigureNotify, CreateNotify, DestroyNotify, GravityNotify, MapNotify, ReparentNotify, UnmapNotify 320 | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | // CirculateRequest, ConfigureRequest, MapRequest 321 | XCB_EVENT_MASK_PROPERTY_CHANGE // PropertyNotify 322 | ; 323 | //dfmt on 324 | 325 | Log log; 326 | xcb_connection_t* connection; 327 | int defaultScreen; 328 | xcb_key_symbols_t* symbols; 329 | xcb_ewmh_connection_t ewmhConnection; 330 | 331 | Atom[EnumCount!(WMAtoms)()] lookupWMAtoms; 332 | Atom[EnumCount!(NETAtoms)()] lookupNETAtoms; 333 | 334 | XCBWindow findWindow(xcb_window_t id, ulong* idx = null) { 335 | foreach (i, win; windows) 336 | if (auto window = cast(XCBWindow)win) 337 | if (window.InternalWindow == id) { 338 | if (idx != null) 339 | *idx = i; 340 | return window; 341 | } 342 | 343 | return null; 344 | } 345 | 346 | xcb_screen_t* getScreen(int screen) { 347 | for (auto it = xcb_setup_roots_iterator(xcb_get_setup(connection)); it.rem; --screen, xcb_screen_next(&it)) 348 | if (screen == 0) 349 | return it.data; 350 | return null; 351 | } 352 | 353 | void setup() { 354 | import std.format : format; 355 | 356 | foreach (wmAtom; EnumMembers!WMAtoms) 357 | lookupWMAtoms[wmAtom.id] = Atom(this, wmAtom.name); 358 | 359 | foreach (netAtom; EnumMembers!NETAtoms) 360 | lookupNETAtoms[netAtom.id] = Atom(this, netAtom.name); 361 | 362 | foreach (netAtom; EnumMembers!NETAtoms) 363 | log.Debug("%s => %s", netAtom.name, lookupNETAtoms[netAtom.id].atom); 364 | 365 | lookupNETAtoms[NETAtoms.Supported.id].Change(Root, lookupNETAtoms); 366 | 367 | lookupNETAtoms[NETAtoms.ClientList.id].Delete(Root); 368 | 369 | if (xcb_xinerama_is_active_reply(connection, xcb_xinerama_is_active(connection), null).state) { 370 | xcb_xinerama_query_screens_reply_t* reply = xcb_xinerama_query_screens_reply(connection, 371 | xcb_xinerama_query_screens(connection), null); 372 | 373 | auto it = xcb_xinerama_query_screens_screen_info_iterator(reply); 374 | for (; it.rem > 0; xcb_xinerama_screen_info_next(&it)) 375 | screens ~= new Screen(this, format("Screen %d", reply.number - it.rem), it.data.x_org, it.data.y_org, 376 | it.data.width, it.data.height); 377 | 378 | xcb_free(reply); 379 | } else { 380 | Root.Update(); 381 | screens ~= new Screen(this, "Root Screen", Root.X, Root.Y, Root.Width, Root.Height); 382 | } 383 | 384 | Flush(); 385 | } 386 | 387 | void checkOtherWM() { 388 | xcb_generic_error_t* error = xcb_request_check(connection, xcb_change_window_attributes_checked(connection, 389 | Root.InternalWindow, XCB_CW_EVENT_MASK, &rootEventMask)); 390 | if (error) { 391 | //dfmt off 392 | log.Fatal( 393 | "XCB error: %s, sequence: %s, resource id: %s, major code: %s, minor code: %s", 394 | cast(XCBErrorCode)error.error_code, 395 | error.sequence, 396 | error.resource_id, 397 | error.major_code, 398 | error.minor_code); 399 | //dfmt on 400 | xcb_free(error); 401 | } 402 | } 403 | 404 | } 405 | 406 | enum XCBErrorCode { 407 | Success, 408 | BadRequest, 409 | BadValue, 410 | BadWindow, 411 | BadPixmap, 412 | BadAtom, 413 | BadCursor, 414 | BadFont, 415 | BadMatch, 416 | BadDrawable, 417 | BadAccess, 418 | BadAlloc, 419 | BadColor, 420 | BadGC, 421 | BadIDChoice, 422 | BadName, 423 | BadLength, 424 | BadImplementation, 425 | Unknown 426 | } 427 | -------------------------------------------------------------------------------- /source/dwin/backend/xcb/xcbbindmanager.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.xcb.xcbbindmanager; 2 | 3 | import xcb.xcb; 4 | import xcb.xproto; 5 | import xcb.keysyms; 6 | 7 | import dwin.backend.xcb.xcb; 8 | import dwin.backend.xcb.keyparser; 9 | import dwin.backend.bindmanager; 10 | 11 | import dwin.log; 12 | 13 | final class XCBBindManager : BindManager { 14 | public: 15 | this(XCB xcb) { 16 | this.xcb = xcb; 17 | keyParser = new XCBKeyParser(); 18 | Rebind(); 19 | } 20 | 21 | override void Rebind() { 22 | (cast(XCBKeyParser)keyParser).Refresh(xcb); 23 | immutable uint[] modifiers = [0, XCB_MOD_MASK_LOCK, keyParser.NumlockMask, keyParser.NumlockMask | XCB_MOD_MASK_LOCK]; 24 | 25 | ungrabKey(XCB_GRAB_ANY, cast(Modifier)XCB_MOD_MASK_ANY); 26 | ungrabButton(cast(MouseButton)XCB_BUTTON_INDEX_ANY, cast(Modifier)XCB_MOD_MASK_ANY); 27 | foreach (keyBind, func; mappings) { 28 | if (keyBind.mouseButton == MouseButton.None) { 29 | xcb_keycode_t* code = xcb_key_symbols_get_keycode(xcb.Symbols, keyBind.key); 30 | if (!code) 31 | continue; 32 | 33 | foreach (mod; modifiers) 34 | grabKey(true, cast(Modifier)(keyBind.modifier | mod), *code, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); 35 | 36 | xcb_free(code); 37 | } else { 38 | foreach (mod; modifiers) 39 | grabButton(true, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION, 40 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, keyBind.mouseButton, 41 | cast(Modifier)(keyBind.modifier | mod)); 42 | } 43 | } 44 | xcb.Flush(); 45 | } 46 | 47 | override void Map(KeyBind keyBind, MapBind func) { 48 | if (!keyBind.IsValid) { 49 | Log.MainLogger.Error("Invalid mapping (%s)", keyBind); 50 | return; 51 | } 52 | 53 | mappings[keyBind] = func; 54 | Rebind(); 55 | } 56 | 57 | override void Unmap(KeyBind keyBind) { 58 | if (!keyBind.IsValid) { 59 | Log.MainLogger.Error("Invalid mapping (%s)", keyBind); 60 | return; 61 | } 62 | 63 | mappings.remove(keyBind); 64 | } 65 | 66 | void HandleKeyPressEvent(xcb_key_press_event_t* e) { 67 | xcb_keysym_t key = xcb_key_press_lookup_keysym(xcb.Symbols, e, 0); 68 | KeyBind keyBind = KeyBind(cast(Modifier)(e.state & ~(keyParser.NumlockMask | keyParser.MouseMasks)), Key(key), MouseButton.None); 69 | if (auto map = keyBind in mappings) 70 | (*map)(true); 71 | } 72 | 73 | void HandleKeyReleaseEvent(xcb_key_release_event_t* e) { 74 | xcb_keysym_t key = xcb_key_press_lookup_keysym(xcb.Symbols, e, 0); 75 | KeyBind keyBind = KeyBind(cast(Modifier)(e.state & ~(keyParser.NumlockMask | keyParser.MouseMasks)), Key(key), MouseButton.None); 76 | if (auto map = keyBind in mappings) 77 | (*map)(false); 78 | } 79 | 80 | void HandleButtonPressEvent(xcb_button_press_event_t* e) { 81 | KeyBind keyBind = KeyBind(cast(Modifier)(e.state & ~(keyParser.NumlockMask | keyParser.MouseMasks)), 82 | keyParser.ParseKey("None"), cast(MouseButton)e.detail); 83 | 84 | xcb.Mouse.Set(e.root_x, e.root_y); 85 | if (auto map = keyBind in mappings) 86 | (*map)(true); 87 | } 88 | 89 | void HandleButtonReleaseEvent(xcb_button_release_event_t* e) { 90 | KeyBind keyBind = KeyBind(cast(Modifier)(e.state & ~(keyParser.NumlockMask | keyParser.MouseMasks)), 91 | keyParser.ParseKey("None"), cast(MouseButton)e.detail); 92 | 93 | xcb.Mouse.Set(e.root_x, e.root_y); 94 | if (auto map = keyBind in mappings) 95 | (*map)(false); 96 | } 97 | 98 | private: 99 | XCB xcb; 100 | 101 | auto grabKey(bool owner_events, Modifier modifiers, xcb_keycode_t key, ubyte pointerMode, ubyte keyboardMode) { 102 | //dfmt off 103 | return xcb_grab_key( 104 | xcb.Connection, 105 | owner_events, 106 | xcb.Root.InternalWindow, 107 | modifiers, 108 | key, 109 | pointerMode, 110 | keyboardMode 111 | ); 112 | //dfmt on 113 | } 114 | 115 | auto ungrabKey(xcb_keycode_t key, Modifier modifiers) { 116 | //dfmt off 117 | return xcb_ungrab_key( 118 | xcb.Connection, 119 | key, 120 | xcb.Root.InternalWindow, 121 | modifiers 122 | ); 123 | //dfmt on 124 | } 125 | 126 | auto grabButton(bool owner_events, ushort event_mask, ubyte pointerMode, ubyte keyboardMode, xcb_cursor_t cursor, 127 | MouseButton button, Modifier modifiers) { 128 | //dfmt off 129 | return xcb_grab_button( 130 | xcb.Connection, 131 | owner_events, 132 | xcb.Root.InternalWindow, 133 | event_mask, 134 | pointerMode, 135 | keyboardMode, 136 | xcb.Root.InternalWindow, 137 | cursor, 138 | button, 139 | modifiers 140 | ); 141 | //dfmt on 142 | } 143 | 144 | auto ungrabButton(MouseButton button, Modifier modifiers) { 145 | return xcb_ungrab_button(xcb.Connection, button, xcb.Root.InternalWindow, modifiers); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /source/dwin/backend/xcb/xcbmouse.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.xcb.xcbmouse; 2 | 3 | import dwin.backend.mouse; 4 | import xcb.xcb; 5 | import dwin.backend.xcb.xcb; 6 | import dwin.backend.xcb.xcbmousestyle; 7 | 8 | class XCBMouse : Mouse { 9 | public: 10 | this(XCB xcb) { 11 | this.xcb = xcb; 12 | 13 | styles[MouseStyles.Normal] = new XCBMouseStyle(xcb, XCBMouseIcons.XC_left_ptr); 14 | styles[MouseStyles.Resizing] = new XCBMouseStyle(xcb, XCBMouseIcons.XC_sizing); 15 | styles[MouseStyles.Moving] = new XCBMouseStyle(xcb, XCBMouseIcons.XC_fleur); 16 | 17 | styles[MouseStyles.Normal].Apply(); 18 | } 19 | 20 | override void Update() { 21 | xcb_query_pointer_reply_t* reply = xcb_query_pointer_reply(xcb.Connection, xcb_query_pointer(xcb.Connection, 22 | xcb.Root.InternalWindow), null); 23 | 24 | x = reply.root_x; 25 | y = reply.root_y; 26 | 27 | buttons[0] = !!(reply.mask & XCB_KEY_BUT_MASK_BUTTON_1); 28 | buttons[1] = !!(reply.mask & XCB_KEY_BUT_MASK_BUTTON_2); 29 | buttons[2] = !!(reply.mask & XCB_KEY_BUT_MASK_BUTTON_3); 30 | buttons[3] = !!(reply.mask & XCB_KEY_BUT_MASK_BUTTON_4); 31 | buttons[4] = !!(reply.mask & XCB_KEY_BUT_MASK_BUTTON_5); 32 | 33 | xcb_free(reply); 34 | } 35 | 36 | override void Move(short x, short y) { 37 | xcb_warp_pointer(xcb.Connection, XCB_NONE, xcb.Root.InternalWindow, 0, 0, 0, 0, x, y); 38 | } 39 | 40 | private: 41 | XCB xcb; 42 | } 43 | -------------------------------------------------------------------------------- /source/dwin/backend/xcb/xcbmousestyle.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.xcb.xcbmousestyle; 2 | 3 | import xcb.xcb; 4 | 5 | import dwin.backend.xcb.xcb; 6 | import dwin.backend.mousestyle; 7 | 8 | final class XCBMouseStyle : MouseStyle { 9 | public: 10 | this(XCB xcb, XCBMouseIcons icon) { 11 | import std.string : toStringz; 12 | 13 | this.xcb = xcb; 14 | font = xcb_generate_id(xcb.Connection); 15 | xcb_open_font(xcb.Connection, font, 6, "cursor".toStringz); 16 | 17 | cursor = xcb_generate_id(xcb.Connection); 18 | //dfmt off 19 | xcb_create_glyph_cursor(xcb.Connection, 20 | cursor, 21 | font, 22 | font, 23 | cast(ushort)icon, 24 | cast(ushort)(icon + 1), 25 | 0, 0, 0, 26 | ushort.max, ushort.max, ushort.max 27 | ); 28 | //dfmt on 29 | } 30 | 31 | ~this() { 32 | xcb_free_cursor(xcb.Connection, cursor); 33 | xcb_close_font(xcb.Connection, font); 34 | } 35 | 36 | override void Apply() { 37 | xcb.Root.ChangeAttributes(XCB_CW_CURSOR, &cursor); 38 | } 39 | 40 | @property xcb_cursor_t Cursor() { 41 | return cursor; 42 | } 43 | 44 | private: 45 | XCB xcb; 46 | xcb_font_t font; 47 | xcb_cursor_t cursor; 48 | } 49 | 50 | enum XCBMouseIcons : ushort { 51 | XC_X_cursor = 0, 52 | XC_arrow = 2, 53 | XC_based_arrow_down = 4, 54 | XC_based_arrow_up = 6, 55 | XC_boat = 8, 56 | XC_bogosity = 10, 57 | XC_bottom_left_corner = 12, 58 | XC_bottom_right_corner = 14, 59 | XC_bottom_side = 16, 60 | XC_bottom_tee = 18, 61 | XC_box_spiral = 20, 62 | XC_center_ptr = 22, 63 | XC_circle = 24, 64 | XC_clock = 26, 65 | XC_coffee_mug = 28, 66 | XC_cross = 30, 67 | XC_cross_reverse = 32, 68 | XC_crosshair = 34, 69 | XC_diamond_cross = 36, 70 | XC_dot = 38, 71 | XC_dotbox = 40, 72 | XC_double_arrow = 42, 73 | XC_draft_large = 44, 74 | XC_draft_small = 46, 75 | XC_draped_box = 48, 76 | XC_exchange = 50, 77 | XC_fleur = 52, 78 | XC_gobbler = 54, 79 | XC_gumby = 56, 80 | XC_hand1 = 58, 81 | XC_hand2 = 60, 82 | XC_heart = 62, 83 | XC_icon = 64, 84 | XC_iron_cross = 66, 85 | XC_left_ptr = 68, 86 | XC_left_side = 70, 87 | XC_left_tee = 72, 88 | XC_leftbutton = 74, 89 | XC_ll_angle = 76, 90 | XC_lr_angle = 78, 91 | XC_man = 80, 92 | XC_middlebutton = 82, 93 | XC_mouse = 84, 94 | XC_pencil = 86, 95 | XC_pirate = 88, 96 | XC_plus = 90, 97 | XC_question_arrow = 92, 98 | XC_right_ptr = 94, 99 | XC_right_side = 96, 100 | XC_right_tee = 98, 101 | XC_rightbutton = 100, 102 | XC_rtl_logo = 102, 103 | XC_sailboat = 104, 104 | XC_sb_down_arrow = 106, 105 | XC_sb_h_double_arrow = 108, 106 | XC_sb_left_arrow = 110, 107 | XC_sb_right_arrow = 112, 108 | XC_sb_up_arrow = 114, 109 | XC_sb_v_double_arrow = 116, 110 | XC_shuttle = 118, 111 | XC_sizing = 120, 112 | XC_spider = 122, 113 | XC_spraycan = 124, 114 | XC_star = 126, 115 | XC_target = 128, 116 | XC_tcross = 130, 117 | XC_top_left_arrow = 132, 118 | XC_top_left_corner = 134, 119 | XC_top_right_corner = 136, 120 | XC_top_side = 138, 121 | XC_top_tee = 140, 122 | XC_trek = 142, 123 | XC_ul_angle = 144, 124 | XC_umbrella = 146, 125 | XC_ur_angle = 148, 126 | XC_watch = 150, 127 | XC_xterm = 152, 128 | XC_num_glyphs = 154 129 | } 130 | -------------------------------------------------------------------------------- /source/dwin/backend/xcb/xcbwindow.d: -------------------------------------------------------------------------------- 1 | module dwin.backend.xcb.xcbwindow; 2 | 3 | import xcb.xcb; 4 | import xcb.ewmh; 5 | import xcb.icccm; 6 | 7 | import dwin.backend.window; 8 | import dwin.backend.xcb.xcb; 9 | import dwin.log; 10 | 11 | class XCBWindow : Window { 12 | public: 13 | this(XCB xcb, xcb_window_t window) { 14 | this.xcb = xcb; 15 | this.window = window; 16 | this.visible = false; 17 | this.dead = false; 18 | 19 | Update(); 20 | } 21 | 22 | @property override string Title() { 23 | Update(); 24 | return title; 25 | } 26 | 27 | @property override bool IsVisible() { 28 | return visible; 29 | } 30 | 31 | override void Update() { 32 | import std.string : fromStringz; 33 | 34 | if (dead) 35 | return; 36 | 37 | xcb_get_geometry_reply_t* geom = xcb_get_geometry_reply(xcb.Connection, xcb_get_geometry(xcb.Connection, window), null); 38 | if (geom) { 39 | x = geom.x; 40 | y = geom.y; 41 | width = geom.width; 42 | height = geom.height; 43 | xcb_free(geom); 44 | } 45 | 46 | xcb_ewmh_get_utf8_strings_reply_t ewmh_txt_prop; 47 | xcb_icccm_get_text_property_reply_t icccm_txt_prop; 48 | 49 | if ((visible && xcb_ewmh_get_wm_name_reply(xcb.EWMH, xcb_ewmh_get_wm_visible_name(xcb.EWMH, window), 50 | &ewmh_txt_prop, null)) || xcb_ewmh_get_wm_name_reply(xcb.EWMH, xcb_ewmh_get_wm_name(xcb.EWMH, window), 51 | &ewmh_txt_prop, null)) { 52 | title = ewmh_txt_prop.strings.fromStringz.idup; 53 | xcb_ewmh_get_utf8_strings_reply_wipe(&ewmh_txt_prop); 54 | } else if (xcb_icccm_get_wm_name_reply(xcb.Connection, xcb_icccm_get_wm_name(xcb.Connection, window), &icccm_txt_prop, null)) { 55 | title = icccm_txt_prop.name.fromStringz.idup; 56 | xcb_icccm_get_text_property_reply_wipe(&icccm_txt_prop); 57 | } else 58 | title = "ERROR TITLE"; 59 | 60 | xcb_get_property_reply_t* reply = xcb_get_property_reply(xcb.Connection, xcb_get_property(xcb.Connection, 0, 61 | window, xcb.LookupNETAtoms[xcb.NETAtoms.WMStrutPartial.id].atom, XCB_ATOM_CARDINAL, 0, 12), null); 62 | 63 | if (reply) { 64 | onlyStrutPartial = true; 65 | scope (exit) 66 | xcb_free(reply); 67 | uint* data = cast(uint*)xcb_get_property_value(reply); 68 | strut = .Strut(data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11]); 69 | } else if (!onlyStrutPartial) { 70 | reply = xcb_get_property_reply(xcb.Connection, xcb_get_property(xcb.Connection, 0, window, 71 | xcb.LookupNETAtoms[xcb.NETAtoms.WMStrut.id].atom, XCB_ATOM_CARDINAL, 0, 4), null); 72 | if (reply) { 73 | scope (exit) 74 | xcb_free(reply); 75 | uint* data = cast(uint*)xcb_get_property_value(reply); 76 | strut = .Strut(data[0], data[1], data[2], data[3]); 77 | } 78 | } 79 | 80 | reply = xcb_get_property_reply(xcb.Connection, xcb_get_property(xcb.Connection, 0, window, 81 | xcb.EWMH._NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 0, 1), null); 82 | if (reply) { 83 | scope (exit) 84 | xcb_free(reply); 85 | 86 | uint* data = cast(uint*)xcb_get_property_value(reply); 87 | desktop = *data; 88 | } 89 | 90 | reply = xcb_get_property_reply(xcb.Connection, xcb_get_property(xcb.Connection, 0, window, 91 | xcb.EWMH._NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 0, uint.max), null); 92 | if (reply) { 93 | scope (exit) 94 | xcb_free(reply); 95 | 96 | int length = xcb_get_property_value_length(reply); 97 | uint* data = cast(uint*)xcb_get_property_value(reply); 98 | windowTypes.length = 0; 99 | foreach (type; data[0 .. length]) 100 | windowTypes ~= type; 101 | } 102 | 103 | reply = xcb_get_property_reply(xcb.Connection, xcb_get_property(xcb.Connection, 0, window, 104 | xcb.EWMH._NET_WM_STATE, XCB_ATOM_ATOM, 0, uint.max), null); 105 | if (reply) { 106 | scope (exit) 107 | xcb_free(reply); 108 | 109 | int length = xcb_get_property_value_length(reply); 110 | uint* data = cast(uint*)xcb_get_property_value(reply); 111 | states.length = 0; 112 | foreach (s; data[0 .. length]) 113 | states ~= s; 114 | } 115 | 116 | } 117 | 118 | override void Move(short x, short y) { 119 | if (IsSticky) 120 | return; 121 | this.x = x; 122 | this.y = y; 123 | uint[] data = [x, y]; 124 | xcb_configure_window(xcb.Connection, window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, data.ptr); 125 | } 126 | 127 | override void Resize(ushort width, ushort height) { 128 | if (IsSticky) 129 | return; 130 | this.width = width; 131 | this.height = height; 132 | uint[] data = [width, height]; 133 | xcb_configure_window(xcb.Connection, window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, data.ptr); 134 | } 135 | 136 | override void MoveResize(short x, short y, ushort width, ushort height) { 137 | if (IsSticky) 138 | return; 139 | uint[] data = [x, y, width, height]; 140 | xcb_configure_window(xcb.Connection, window, 141 | XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, data.ptr); 142 | } 143 | 144 | override void Focus() { 145 | uint value = XCB_STACK_MODE_ABOVE; 146 | xcb_configure_window(xcb.Connection, window, XCB_CONFIG_WINDOW_STACK_MODE, &value); 147 | } 148 | 149 | override void Close() { 150 | xcb_icccm_get_wm_protocols_reply_t reply; 151 | auto Protocols = xcb.LookupWMAtoms[XCB.WMAtoms.Protocols.id]; 152 | auto DeleteWindow = xcb.LookupWMAtoms[XCB.WMAtoms.DeleteWindow.id]; 153 | 154 | if (!xcb_icccm_get_wm_protocols_reply(xcb.Connection, xcb_icccm_get_wm_protocols(xcb.Connection, window, 155 | Protocols), &reply, null)) 156 | xcb_kill_client(xcb.Connection, window); 157 | 158 | scope (exit) 159 | xcb_icccm_get_wm_protocols_reply_wipe(&reply); 160 | 161 | foreach (atom; reply.atoms[0 .. reply.atoms_len]) 162 | if (atom == DeleteWindow) { 163 | xcb_client_message_event_t e; 164 | e.response_type = XCB_CLIENT_MESSAGE; 165 | e.window = window; 166 | e.format = 32; 167 | e.sequence = 0; 168 | e.type = Protocols; 169 | e.data.data32[0] = DeleteWindow; 170 | e.data.data32[1] = XCB_CURRENT_TIME; 171 | xcb_send_event(xcb.Connection, 0, window, XCB_EVENT_MASK_NO_EVENT, cast(char*)&e); 172 | return; 173 | } 174 | } 175 | 176 | override void Show(bool eventBased = true) { 177 | if (eventBased) 178 | visible = true; 179 | 180 | if (visible && parent.IsVisible) 181 | Map(); 182 | } 183 | 184 | override void Hide(bool eventBased = true) { 185 | static bool lastWasEvent = true; 186 | 187 | if (lastWasEvent && eventBased) 188 | visible = false; 189 | 190 | lastWasEvent = eventBased; 191 | 192 | Unmap(); 193 | } 194 | 195 | void Map() { 196 | uint values_off = xcb.RootEventMask & ~XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; 197 | uint values_on = xcb.RootEventMask; 198 | xcb_change_window_attributes(xcb.Connection, xcb.Root.InternalWindow, XCB_CW_EVENT_MASK, &values_off); 199 | xcb_map_window(xcb.Connection, window); 200 | xcb_change_window_attributes(xcb.Connection, xcb.Root.InternalWindow, XCB_CW_EVENT_MASK, &values_on); 201 | } 202 | 203 | void Unmap() { 204 | uint values_off = xcb.RootEventMask & ~XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; 205 | uint values_on = xcb.RootEventMask; 206 | xcb_change_window_attributes(xcb.Connection, xcb.Root.InternalWindow, XCB_CW_EVENT_MASK, &values_off); 207 | xcb_unmap_window(xcb.Connection, window); 208 | xcb_change_window_attributes(xcb.Connection, xcb.Root.InternalWindow, XCB_CW_EVENT_MASK, &values_on); 209 | } 210 | 211 | void ChangeAttributes(uint mask, const uint* value) { 212 | xcb_change_window_attributes(xcb.Connection, window, mask, value); 213 | } 214 | 215 | @property xcb_window_t InternalWindow() { 216 | return window; 217 | } 218 | 219 | @property xcb_atom_t[] WindowTypes() { 220 | return windowTypes; 221 | } 222 | 223 | @property xcb_atom_t[] States() { 224 | return states; 225 | } 226 | 227 | @property override bool IsDock() { 228 | import std.algorithm.searching : canFind; 229 | 230 | return windowTypes.canFind(xcb.EWMH._NET_WM_WINDOW_TYPE_DOCK); 231 | } 232 | 233 | @property override bool IsSticky() { 234 | import std.algorithm.searching : canFind; 235 | 236 | return states.canFind(xcb.EWMH._NET_WM_STATE_STICKY) || 237 | states.canFind(xcb.EWMH._NET_WM_WINDOW_TYPE_TOOLBAR) || 238 | states.canFind(xcb.EWMH._NET_WM_WINDOW_TYPE_MENU) || 239 | states.canFind(xcb.EWMH._NET_WM_WINDOW_TYPE_SPLASH) || 240 | states.canFind(xcb.EWMH._NET_WM_WINDOW_TYPE_TOOLTIP) || 241 | states.canFind(xcb.EWMH._NET_WM_WINDOW_TYPE_NOTIFICATION) 242 | ; 243 | } 244 | 245 | private: 246 | XCB xcb; 247 | xcb_window_t window; 248 | string title; 249 | bool visible; 250 | bool onlyStrutPartial; 251 | xcb_atom_t[] windowTypes; 252 | xcb_atom_t[] states; 253 | } 254 | -------------------------------------------------------------------------------- /source/dwin/dwin.d: -------------------------------------------------------------------------------- 1 | module dwin.dwin; 2 | 3 | import std.container.array; 4 | import std.stdio; 5 | import std.process; 6 | 7 | import dwin.log; 8 | import dwin.event; 9 | import dwin.script.script; 10 | 11 | import dwin.backend.engine; 12 | import dwin.backend.window; 13 | import dwin.backend.screen; 14 | import dwin.backend.layout; 15 | import dwin.backend.workspace; 16 | import dwin.backend.container; 17 | 18 | final class DWin { 19 | public: 20 | this(int display) { 21 | import dwin.backend.xcb.xcb : XCB; 22 | 23 | log = Log.MainLogger(); 24 | 25 | engine = new XCB(display); 26 | script = new Script(this, getScriptFolder); 27 | 28 | setup(); 29 | sigchld(0); // Ignore when children dies 30 | script.RunCtors(); 31 | } 32 | 33 | void Run() { 34 | import core.thread : Thread; 35 | import core.time : msecs; 36 | 37 | quit = false; 38 | while (!quit) { 39 | engine.DoEvent(); 40 | try { 41 | Thread.sleep(10.msecs); 42 | } 43 | catch (Exception) { 44 | } 45 | } 46 | } 47 | 48 | @property.Engine Engine() { 49 | return engine; 50 | } 51 | 52 | private: 53 | Script script; 54 | bool quit; 55 | Log log; 56 | .Engine engine; 57 | Window window; 58 | 59 | uint lastMove; 60 | 61 | extern (C) static void sigchld(int) nothrow @nogc { 62 | import core.sys.posix.signal : signal, SIGCHLD, SIG_ERR; 63 | import core.sys.posix.sys.wait : waitpid, WNOHANG; 64 | 65 | signal(SIGCHLD, &sigchld); 66 | while (0 < waitpid(-1, null, WNOHANG)) { 67 | } 68 | } 69 | 70 | /* 71 | 72 | */ 73 | 74 | string getScriptFolder() { 75 | import std.file : exists, thisExePath; 76 | import std.array : split, join; 77 | import std.string : startsWith; 78 | import std.path : dirName; 79 | 80 | environment["DWIN_EXEPATH"] = thisExePath.dirName; 81 | 82 | //dfmt off 83 | static string[] searchPaths = [ 84 | "$PWD/.dwin/scripts/", 85 | "$XDG_CONFIG_HOME/dwin/scripts/", 86 | "$HOME/.dwin/scripts/", 87 | "$DWIN_EXEPATH/scripts/", 88 | "/usr/share/dwin/scripts/" 89 | ]; 90 | //dfmt on 91 | 92 | foreach (path; searchPaths) { 93 | string[] part = path.split("/"); 94 | 95 | foreach (ref p; part) { 96 | if (p.startsWith("$")) 97 | p = environment.get(p[1 .. $]); 98 | } 99 | 100 | path = part.join("/"); 101 | 102 | log.Info("Checking for script folder: %s", path); 103 | 104 | if (exists(path)) 105 | return path; 106 | } 107 | 108 | log.Fatal("Could not find a script folder!"); 109 | assert(0); 110 | } 111 | 112 | void onNewWindow(Window window) { 113 | auto m = engine.Mouse; 114 | m.Update(); 115 | auto scr = engine.FindScreen(m.X, m.Y); 116 | window.Move(scr.X, scr.Y); 117 | window.Screen = scr; 118 | scr.Add(window); 119 | } 120 | 121 | void onRemoveWindow(Window window) { 122 | log.Info("Remove Window: %s", window); 123 | 124 | //auto scr = engine.FindScreen(window.X, window.Y); 125 | //scr.Remove(window); 126 | window.Parent.Remove(window); 127 | } 128 | 129 | void onRequestShowWindow(Window window) { 130 | log.Info("Show: %s, Desktop: %s, IsDock: %s", window, window.Desktop == uint.max, window.IsDock); 131 | if (window.Desktop == uint.max) { 132 | auto scr = window.Screen; 133 | scr.Remove(window); 134 | scr.AddOnTop(window); 135 | if (window.IsDock) { 136 | log.Info("Window %s(%s) is now the dock! %s", window.Title, window.toString, window.Strut); 137 | foreach (Screen s; engine.Screens) { 138 | s.MoveResize(cast(short)(s.X + window.Strut.left), cast(short)(s.Y + window.Strut.top), 139 | cast(ushort)(s.Width - window.Strut.left - window.Strut.right), 140 | cast(ushort)(s.Height - window.Strut.top - window.Strut.bottom)); 141 | } 142 | } 143 | } 144 | window.Parent.RequestShow(window); 145 | } 146 | 147 | void onNotifyHideWindow(Window window) { 148 | log.Info("Hide: %s", window); 149 | window.Parent.NotifyHide(window); 150 | } 151 | 152 | void onRequestMoveWindow(Window window, short x, short y) { 153 | Window root = engine.Root; 154 | x = (x > root.Width - 32) ? cast(short)(root.Width - 32) : x; 155 | y = (y > root.Height - 32) ? cast(short)(root.Height - 32) : y; 156 | window.Move(x, y); 157 | } 158 | 159 | void onRequestResizeWindow(Window window, ushort width, ushort height) { 160 | window.Resize(width, height); 161 | } 162 | 163 | void onRequestBorderSizeWindow(Window window, ushort borderSize) { 164 | //TODO: implement? 165 | } 166 | 167 | void onRequestSiblingWindow(Window window, Window sibling) { 168 | //TODO: implement? 169 | } 170 | 171 | void onRequestStackModeWindow(Window window, ubyte stackmode) { 172 | //TODO: implement? 173 | } 174 | 175 | void onMouseMotion(short x, short y, uint timestamp) { 176 | timestamp /= 8; //TODO: Extract this to be a config flag 177 | auto m = engine.Mouse; 178 | m.Set(x, y); 179 | if (timestamp == lastMove) 180 | return; 181 | lastMove = timestamp; 182 | if (window) 183 | window.Parent.MouseMotion(window, m); 184 | } 185 | 186 | void setup() { 187 | engine.OnNewWindow ~= &onNewWindow; 188 | engine.OnRemoveWindow ~= &onRemoveWindow; 189 | engine.OnRequestShowWindow ~= &onRequestShowWindow; 190 | engine.OnNotifyHideWindow ~= &onNotifyHideWindow; 191 | engine.OnRequestMoveWindow ~= &onRequestMoveWindow; 192 | engine.OnRequestResizeWindow ~= &onRequestResizeWindow; 193 | engine.OnRequestBorderSizeWindow ~= &onRequestBorderSizeWindow; 194 | engine.OnRequestSiblingWindow ~= &onRequestSiblingWindow; 195 | engine.OnRequestStackModeWindow ~= &onRequestStackModeWindow; 196 | 197 | engine.OnMouseMotion ~= &onMouseMotion; 198 | 199 | engine.BindManager.Map("Ctrl + Alt + Shift + Escape", delegate(bool v) { quit = true; }); 200 | 201 | engine.BindManager.Map("Ctrl + Button1", delegate(bool v) { 202 | auto m = engine.Mouse; 203 | if (v) { 204 | window = engine.FindWindow(m.X, m.Y); 205 | if (window) 206 | window.Parent.MouseMovePressed(window, m); 207 | } else { 208 | if (window) 209 | window.Parent.MouseMoveReleased(window, m); 210 | window = null; 211 | } 212 | }); 213 | 214 | engine.BindManager.Map("Ctrl + Button3", delegate(bool v) { 215 | auto m = engine.Mouse; 216 | if (v) { 217 | window = engine.FindWindow(m.X, m.Y); 218 | if (window) 219 | window.Parent.MouseResizePressed(window, m); 220 | } else { 221 | if (window) 222 | window.Parent.MouseResizeReleased(window, m); 223 | window = null; 224 | } 225 | }); 226 | 227 | engine.BindManager.Map("Ctrl + Shift + Escape", delegate(bool v) { 228 | if (v) { 229 | auto m = engine.Mouse; 230 | m.Update(); 231 | Window window = engine.FindWindow(m.X, m.Y); 232 | if (!window) 233 | return; 234 | window.Hide(); 235 | window.Close(); 236 | } 237 | }); 238 | 239 | engine.BindManager.Map("Ctrl + 1", delegate(bool v) { 240 | if (v) { 241 | auto m = engine.Mouse; 242 | m.Update(); 243 | auto scr = engine.FindScreen(m.X, m.Y); 244 | scr.CurrentWorkspace(scr.CurrentWorkspace - 1); 245 | } 246 | }); 247 | 248 | engine.BindManager.Map("Ctrl + 2", delegate(bool v) { 249 | if (v) { 250 | auto m = engine.Mouse; 251 | m.Update(); 252 | auto scr = engine.FindScreen(m.X, m.Y); 253 | scr.CurrentWorkspace(scr.CurrentWorkspace + 1); 254 | } 255 | }); 256 | 257 | engine.BindManager.Map("Ctrl + p", delegate(bool v) { 258 | if (v) { 259 | auto m = engine.Mouse; 260 | m.Update(); 261 | Window window = engine.FindWindow(m.X, m.Y); 262 | if (!window) 263 | return; 264 | log.Info("Promoting: %s", window.Title); 265 | window.Parent.Remove(window); 266 | auto scr = window.Screen; 267 | scr.Workspaces[scr.CurrentWorkspace].AddOnTop(window); 268 | } 269 | }); 270 | 271 | engine.BindManager.Map("Ctrl + o", delegate(bool v) { 272 | if (v) { 273 | auto m = engine.Mouse; 274 | m.Update(); 275 | Window window = engine.FindWindow(m.X, m.Y); 276 | if (!window) 277 | return; 278 | log.Info("Demoting: %s", window.Title); 279 | window.Parent.Remove(window); 280 | auto scr = window.Screen; 281 | scr.Workspaces[scr.CurrentWorkspace].Add(window); 282 | } 283 | }); 284 | 285 | engine.BindManager.Map("Ctrl + x", delegate(bool v) { 286 | if (v) { 287 | auto m = engine.Mouse; 288 | m.Update(); 289 | Window window = engine.FindWindow(m.X, m.Y); 290 | log.Info("1: %s", window); 291 | if (!window) 292 | return; 293 | 294 | import dwin.layout.tilinglayout; 295 | 296 | log.Info("Trying to swap: %s", window.Parent); 297 | 298 | if (auto layout = cast(TilingLayout)window.Parent) 299 | layout.Swap(); 300 | } 301 | }); 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /source/dwin/event.d: -------------------------------------------------------------------------------- 1 | module dwin.event; 2 | 3 | struct Event(Args...) { 4 | public: 5 | /// Adds a callback $(PARAM cb) 6 | void opOpAssign(string op : "~")(cbFunction cb) { 7 | Add(cb); 8 | } 9 | 10 | /// Adds a callback $(PARAM cb) 11 | void opOpAssign(string op : "+")(cbFunction cb) { 12 | Add(cb); 13 | } 14 | 15 | /// Adds a callback $(PARAM cb) 16 | void Add(cbFunction cb) { 17 | callbacks ~= cb; 18 | } 19 | 20 | /// Removes a callback $(PARAM cb) 21 | void opOpAssign(string op : "-")(cbFunction cb) { 22 | Remove(cb); 23 | } 24 | 25 | /// Removes a callback $(PARAM cb) 26 | void Remove(cbFunction cb) { 27 | import std.algorithm.mutation : remove, SwapStrategy; 28 | 29 | callbacks = callbacks.remove!(a => a == cb, SwapStrategy.unstable); 30 | } 31 | 32 | /// Calls every functions with the arguments $(PARAM args) 33 | void opCall(Args args) { 34 | foreach (fn; callbacks) 35 | fn(args); 36 | } 37 | 38 | private: 39 | alias cbFunction = void delegate(Args); 40 | cbFunction[] callbacks; 41 | } 42 | -------------------------------------------------------------------------------- /source/dwin/layout/floatinglayout.d: -------------------------------------------------------------------------------- 1 | module dwin.layout.floatinglayout; 2 | 3 | import std.container.array; 4 | import std.algorithm.searching; 5 | import dwin.backend.engine; 6 | import dwin.backend.container; 7 | import dwin.backend.mouse; 8 | import dwin.backend.layout; 9 | import dwin.log; 10 | import dwin.util.data; 11 | import std.math; 12 | 13 | class FloatingLayout : Layout { 14 | public: 15 | this(Engine engine) { 16 | super(engine); 17 | } 18 | 19 | override void Add(Container container) { 20 | super.Add(container); 21 | container.Focus(); 22 | } 23 | 24 | override void Remove(Container container) { 25 | super.Remove(container); 26 | } 27 | 28 | override void RequestShow(Container container) { 29 | container.Show(); 30 | } 31 | 32 | override void NotifyHide(Container container) { 33 | container.Hide(); 34 | } 35 | 36 | override void Move(short x, short y) { 37 | super.Move(x, y); 38 | } 39 | 40 | override void Resize(ushort width, ushort height) { 41 | super.Resize(width, height); 42 | } 43 | 44 | override void MoveResize(short x, short y, ushort width, ushort height) { 45 | super.MoveResize(x, y, width, height); 46 | } 47 | 48 | override void MouseMovePressed(Container target, Mouse mouse) { 49 | if (state != HandlingState.None) 50 | return; 51 | state = HandlingState.Move; 52 | this.target = target; 53 | target.Update(); 54 | target.Focus(); 55 | mouse.Style = MouseStyles.Moving; 56 | pointerDiff = vec2(mouse.X - target.X, mouse.Y - target.Y); 57 | oldGeom = Geometry(target.X, target.Y, target.Width, target.Height); 58 | } 59 | 60 | override void MouseResizePressed(Container target, Mouse mouse) { 61 | if (state != HandlingState.None) 62 | return; 63 | state = HandlingState.Resize; 64 | this.target = target; 65 | target.Update(); 66 | target.Focus(); 67 | mouse.Style = MouseStyles.Resizing; 68 | 69 | pointerDiff = vec2(mouse.X - (target.X + target.Width / 2), mouse.Y - (target.Y + target.Height / 2)); 70 | oldGeom = Geometry(target.X, target.Y, target.Width, target.Height); 71 | 72 | gridPos = target.GetGridPosition(mouse.X - target.X, mouse.Y - target.Y); 73 | } 74 | 75 | override void MouseMoveReleased(Container target, Mouse mouse) { 76 | if (state != HandlingState.Move) 77 | return; 78 | state = HandlingState.None; 79 | target = null; 80 | mouse.Style = MouseStyles.Normal; 81 | } 82 | 83 | override void MouseResizeReleased(Container target, Mouse mouse) { 84 | if (state != HandlingState.Resize) 85 | return; 86 | 87 | MouseMotion(target, mouse); //Update it a last time! 88 | 89 | state = HandlingState.None; 90 | target = null; 91 | mouse.Style = MouseStyles.Normal; 92 | } 93 | 94 | override void MouseMotion(Container target, Mouse mouse) { 95 | if (!target) 96 | return; 97 | 98 | if (state == HandlingState.Move) 99 | move(target, mouse); 100 | else if (state == HandlingState.Resize) 101 | resize(target, mouse); 102 | } 103 | 104 | private: 105 | enum HandlingState { 106 | None, 107 | Move, 108 | Resize 109 | } 110 | 111 | HandlingState state; 112 | Container target; 113 | vec2 pointerDiff; 114 | Geometry oldGeom; 115 | 116 | GridPosition gridPos; 117 | 118 | void move(Container target, Mouse mouse) { 119 | short x = cast(short)(mouse.X - pointerDiff.x); 120 | short y = cast(short)(mouse.Y - pointerDiff.y); 121 | 122 | target.Move(x, y); 123 | } 124 | 125 | void resize(Container target, Mouse mouse) { 126 | const int minSize = 32; 127 | int x = target.X; 128 | int y = target.Y; 129 | int w = target.Width; 130 | int h = target.Height; 131 | const int oldx = x; 132 | const int oldy = y; 133 | const int oldw = w; 134 | const int oldh = h; 135 | 136 | if (gridPos.y == PositionPart.First) { 137 | if (gridPos.x == PositionPart.First) { 138 | x = (mouse.X) - (pointerDiff.x + oldGeom.width / 2); 139 | w += oldx - x; 140 | if (w < minSize) { 141 | x -= (minSize - w); 142 | w += minSize - w; 143 | } 144 | 145 | y = (mouse.Y) - (pointerDiff.y + oldGeom.height / 2); 146 | h += oldy - y; 147 | if (h < minSize) { 148 | y -= (minSize - h); 149 | h += minSize - h; 150 | } 151 | } else if (gridPos.x == PositionPart.Second) { 152 | y = (mouse.Y) - (pointerDiff.y + oldGeom.height / 2); 153 | h += oldy - y; 154 | if (h < minSize) { 155 | y -= (minSize - h); 156 | h += minSize - h; 157 | } 158 | } else /*if (gridPos.x == PositionPart.Third) */ { 159 | w = (mouse.X - oldGeom.x) - (pointerDiff.x - oldGeom.width / 2); 160 | if (w < minSize) 161 | w += minSize - w; 162 | 163 | y = (mouse.Y) - (pointerDiff.y + oldGeom.height / 2); 164 | h += oldy - y; 165 | if (h < minSize) { 166 | y -= (minSize - h); 167 | h += minSize - h; 168 | } 169 | } 170 | } else if (gridPos.y == PositionPart.Second) { 171 | if (gridPos.x == PositionPart.First) { 172 | x = (mouse.X) - (pointerDiff.x + oldGeom.width / 2); 173 | w += oldx - x; 174 | if (w < minSize) { 175 | x -= (minSize - w); 176 | w += minSize - w; 177 | } 178 | 179 | } else if (gridPos.x == PositionPart.Second) { 180 | 181 | } else /*if (gridPos.x == PositionPart.Third) */ { 182 | w = (mouse.X - oldGeom.x) - (pointerDiff.x - oldGeom.width / 2); 183 | if (w < minSize) 184 | w += minSize - w; 185 | } 186 | } else /*if (gridPos.y == PositionPart.Third) */ { 187 | if (gridPos.x == PositionPart.First) { 188 | x = (mouse.X) - (pointerDiff.x + oldGeom.width / 2); 189 | w += oldx - x; 190 | if (w < minSize) { 191 | x -= (minSize - w); 192 | w += minSize - w; 193 | } 194 | 195 | h = (mouse.Y - oldGeom.y) - (pointerDiff.y - oldGeom.height / 2); 196 | if (h < minSize) 197 | h += minSize - h; 198 | } else if (gridPos.x == PositionPart.Second) { 199 | h = (mouse.Y - oldGeom.y) - (pointerDiff.y - oldGeom.height / 2); 200 | if (h < minSize) 201 | h += minSize - h; 202 | } else /*if (gridPos.x == PositionPart.Third) */ { 203 | w = (mouse.X - oldGeom.x) - (pointerDiff.x - oldGeom.width / 2); 204 | if (w < minSize) 205 | w += minSize - w; 206 | 207 | h = (mouse.Y - oldGeom.y) - (pointerDiff.y - oldGeom.height / 2); 208 | if (h < minSize) 209 | h += minSize - h; 210 | } 211 | } 212 | 213 | target.MoveResize(cast(short)x, cast(short)y, cast(ushort)w, cast(ushort)h); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /source/dwin/layout/tilinglayout.d: -------------------------------------------------------------------------------- 1 | module dwin.layout.tilinglayout; 2 | 3 | import dwin.backend.engine; 4 | import dwin.backend.container; 5 | import dwin.backend.mouse; 6 | import dwin.backend.layout; 7 | import dwin.log; 8 | import dwin.util.data; 9 | import std.math; 10 | 11 | class TilingLayout : Layout { 12 | this(Engine engine, bool isHorizontal = true, Container left = null, Container right = null) { 13 | super(engine); 14 | this.isHorizontal = isHorizontal; 15 | 16 | visible = left || right; 17 | leftSizeRatio = 0.5; 18 | 19 | if (left) 20 | Add(left); 21 | if (right) 22 | Add(right); 23 | Refresh(); 24 | } 25 | 26 | override void Add(Container container) { 27 | container.Parent = this; 28 | if (!container.IsVisible) 29 | return; 30 | 31 | if (!left) { 32 | left = container; 33 | left.Parent = this; 34 | } else if (!right) { 35 | right = container; 36 | right.Parent = this; 37 | } else { 38 | if (auto layout = cast(TilingLayout)right) { 39 | layout.Add(container); 40 | return; 41 | } else { 42 | right = new TilingLayout(engine, !isHorizontal, right, container); 43 | right.Parent = this; 44 | } 45 | } 46 | 47 | Refresh(); 48 | } 49 | 50 | override void Remove(Container container) { 51 | // Check if left or right is the container 52 | if (left == container) { 53 | left = right; 54 | right = null; 55 | } else if (right == container) { 56 | right = null; 57 | } // else check if left and/or right is a TilingLayout and query them 58 | else { 59 | if (auto layout = cast(TilingLayout)left) 60 | layout.Remove(container); 61 | 62 | if (auto layout = cast(TilingLayout)right) 63 | layout.Remove(container); 64 | } 65 | Refresh(); 66 | } 67 | 68 | override void RequestShow(Container container) { 69 | container.Show(); 70 | if (!left) { 71 | left = container; 72 | left.Parent = this; 73 | } else if (!right) { 74 | right = container; 75 | right.Parent = this; 76 | } else { 77 | if (auto layout = cast(TilingLayout)right) { 78 | layout.Add(container); 79 | return; 80 | } else { 81 | right = new TilingLayout(engine, !isHorizontal, right, container); 82 | right.Parent = this; 83 | } 84 | } 85 | 86 | Refresh(); 87 | } 88 | 89 | override void NotifyHide(Container container) { 90 | if (IsVisible) 91 | container.Hide(); 92 | if (container.IsVisible) { 93 | // Check if left or right is the container 94 | if (left == container) { 95 | left = right; 96 | right = null; 97 | } else if (right == container) { 98 | right = null; 99 | } // else check if left and/or right is a TilingLayout and query them 100 | else { 101 | killOff(); 102 | if (auto layout = cast(TilingLayout)left) { 103 | layout.Remove(container); 104 | killOff(); 105 | return; 106 | } 107 | if (auto layout = cast(TilingLayout)right) { 108 | layout.Remove(container); 109 | killOff(); 110 | return; 111 | } 112 | } 113 | Refresh(); 114 | } 115 | } 116 | 117 | override void Show(bool eventBased = true) { 118 | super.Show(eventBased); 119 | } 120 | 121 | override void Hide(bool eventBased = true) { 122 | super.Hide(eventBased); 123 | } 124 | 125 | override void Move(short x, short y) { 126 | super.Move(x, y); 127 | Refresh(); 128 | } 129 | 130 | override void Resize(ushort width, ushort height) { 131 | super.Resize(width, height); 132 | Refresh(); 133 | } 134 | 135 | override void MoveResize(short x, short y, ushort width, ushort height) { 136 | super.MoveResize(x, y, width, height); 137 | Refresh(); 138 | } 139 | 140 | override void MouseMovePressed(Container target, Mouse mouse) { 141 | if (state != HandlingState.None) 142 | return; 143 | 144 | state = HandlingState.Move; 145 | this.target = target; 146 | target.Update(); 147 | mouse.Style = MouseStyles.Moving; 148 | } 149 | 150 | override void MouseResizePressed(Container target, Mouse mouse) { 151 | if (state != HandlingState.None) 152 | return; 153 | 154 | state = HandlingState.Resize; 155 | this.target = target; 156 | target.Update(); 157 | mouse.Style = MouseStyles.Resizing; 158 | 159 | pointerDiff = vec2(0); 160 | gridPos = target.GetGridPosition(mouse.X - target.X, mouse.Y - target.Y); 161 | 162 | if (gridPos.x == PositionPart.First) 163 | pointerDiff.x = target.X - mouse.X; 164 | else if (gridPos.x == PositionPart.Third) 165 | pointerDiff.x = target.X + target.Width - mouse.X; 166 | 167 | if (gridPos.y == PositionPart.First) 168 | pointerDiff.y = target.Y - mouse.Y; 169 | else if (gridPos.y == PositionPart.Third) 170 | pointerDiff.y = target.Y + target.Height - mouse.Y; 171 | } 172 | 173 | override void MouseMoveReleased(Container target, Mouse mouse) { 174 | if (state != HandlingState.Move) 175 | return; 176 | 177 | state = HandlingState.None; 178 | this.target = null; 179 | mouse.Style = MouseStyles.Normal; 180 | } 181 | 182 | override void MouseResizeReleased(Container target, Mouse mouse) { 183 | if (state != HandlingState.Resize) 184 | return; 185 | 186 | MouseMotion(target, mouse); //Update it a last time! 187 | 188 | state = HandlingState.None; 189 | this.target = null; 190 | mouse.Style = MouseStyles.Normal; 191 | } 192 | 193 | override void MouseMotion(Container target, Mouse mouse) { 194 | if (!target) 195 | return; 196 | 197 | if (state == HandlingState.Move) 198 | move(target, mouse); 199 | else if (state == HandlingState.Resize) 200 | resize(target, mouse); 201 | } 202 | 203 | Container PullContainer() { 204 | return left; 205 | } 206 | 207 | bool ShouldDie() { 208 | killOff(); 209 | return !right; 210 | } 211 | 212 | void Swap() { 213 | Container tmp = left; 214 | left = right; 215 | right = tmp; 216 | 217 | Refresh(); 218 | } 219 | 220 | void Refresh() { 221 | killOff(); 222 | 223 | //XXX: To fix that sometimes left/right.Parent is sometimes wrong 224 | if (left) 225 | left.Parent = this; 226 | if (right) 227 | right.Parent = this; 228 | 229 | if (left && right) { 230 | if (isHorizontal) { 231 | const ushort leftWidth = cast(ushort)(width * leftSizeRatio); 232 | const ushort rightWidth = cast(ushort)(width - leftWidth); 233 | 234 | left.MoveResize(X, Y, leftWidth, height); 235 | right.MoveResize(cast(short)(X + leftWidth), Y, rightWidth, height); 236 | } else { 237 | const ushort leftHeight = cast(ushort)(height * leftSizeRatio); 238 | const ushort rightHeight = cast(ushort)(height - leftHeight); 239 | left.MoveResize(X, Y, width, leftHeight); 240 | right.MoveResize(X, cast(short)(Y + leftHeight), width, rightHeight); 241 | } 242 | } else if (left) 243 | left.MoveResize(X, Y, Width, Height); 244 | } 245 | 246 | @property override Container[] Containers() { 247 | if (left && right) 248 | return [left, right]; 249 | else if (left) 250 | return [left]; 251 | else 252 | return []; 253 | } 254 | 255 | @property double LeftSizeRatio() { 256 | return leftSizeRatio; 257 | } 258 | 259 | @property double LeftSizeRatio(double leftSizeRatio) { 260 | const double maxSize = (isHorizontal) ? width : height; 261 | const double minSizeRatio = 32 / (maxSize * 1.0); 262 | 263 | if (leftSizeRatio < minSizeRatio) 264 | leftSizeRatio = minSizeRatio; 265 | else if (leftSizeRatio > 1 - minSizeRatio) 266 | leftSizeRatio = 1 - minSizeRatio; 267 | 268 | if (cast(ushort)(this.leftSizeRatio * maxSize) != cast(ushort)(leftSizeRatio * maxSize)) { 269 | this.leftSizeRatio = leftSizeRatio; 270 | Refresh(); 271 | } 272 | 273 | return leftSizeRatio; 274 | } 275 | 276 | @property ref Container Left() { 277 | return left; 278 | } 279 | 280 | @property ref Container Right() { 281 | return right; 282 | } 283 | 284 | override string toString() { 285 | import std.format : format; 286 | 287 | return format("%s %s: %s", cast(void*)this, typeid(this).name, isHorizontal ? "Horizontal" : "Vertical"); 288 | } 289 | 290 | private: 291 | enum HandlingState { 292 | None, 293 | Move, 294 | Resize 295 | } 296 | 297 | bool isHorizontal; 298 | Container left; 299 | Container right; 300 | double leftSizeRatio; 301 | 302 | HandlingState state; 303 | Container target; 304 | vec2 pointerDiff; 305 | 306 | GridPosition gridPos; 307 | 308 | void killOff() { 309 | while (right) { 310 | auto layout = cast(TilingLayout)right; 311 | if (!layout || !layout.ShouldDie()) 312 | break; 313 | 314 | right = layout.PullContainer(); 315 | if (right) { 316 | right.Parent = this; 317 | } 318 | layout.destroy; 319 | } 320 | 321 | while (left) { 322 | auto layout = cast(TilingLayout)left; 323 | if (!layout || !layout.ShouldDie()) 324 | break; 325 | 326 | left = layout.PullContainer(); 327 | if (left) { 328 | left.Parent = this; 329 | } 330 | layout.destroy; 331 | } 332 | 333 | if (!left) { 334 | left = right; 335 | right = null; 336 | } 337 | } 338 | 339 | void move(Container target, Mouse mouse) { 340 | Container atMouse = engine.FindWindow(mouse.X, mouse.Y); 341 | if (atMouse && atMouse != this.target) { 342 | if (auto layout = cast(TilingLayout)atMouse.Parent) { 343 | Container* loc1; 344 | if (this.target == left) 345 | loc1 = &left; 346 | else if (this.target == right) 347 | loc1 = &right; 348 | else 349 | assert(0); 350 | 351 | Container* loc2; 352 | if (atMouse == layout.Left) 353 | loc2 = &layout.Left(); 354 | else if (atMouse == layout.Right) 355 | loc2 = &layout.Right(); 356 | else 357 | assert(0); 358 | 359 | Container tmp = *loc1; 360 | *loc1 = *loc2; 361 | *loc2 = tmp; 362 | 363 | loc1.Parent = this; 364 | loc2.Parent = layout; 365 | 366 | Refresh(); 367 | layout.Refresh(); 368 | MouseMoveReleased(target, mouse); 369 | layout.MouseMovePressed(target, mouse); 370 | } 371 | } 372 | } 373 | 374 | void resize(Container target, Mouse mouse) { 375 | TilingLayout parent = cast(TilingLayout)Parent; 376 | TilingLayout grandParent = parent ? cast(TilingLayout)parent.Parent : null; 377 | 378 | if (isHorizontal) { 379 | if (gridPos.x == PositionPart.First) { 380 | if (target == left) { 381 | if (grandParent && grandParent.Containers[1] == parent) 382 | grandParent.LeftSizeRatio = ((mouse.X + pointerDiff.x - grandParent.X) / (grandParent.Width * 1.0)); 383 | } else 384 | LeftSizeRatio = ((mouse.X + pointerDiff.x - X) / (Width * 1.0)); 385 | } else if (gridPos.x == PositionPart.Third) { 386 | if (target == left) 387 | LeftSizeRatio = ((mouse.X + pointerDiff.x - X) / (Width * 1.0)); 388 | else { 389 | if (grandParent && grandParent.Containers[0] == parent) 390 | grandParent.LeftSizeRatio = ((mouse.X + pointerDiff.x - grandParent.X) / (grandParent.Width * 1.0)); 391 | } 392 | } 393 | 394 | if (parent) 395 | if ((gridPos.y == PositionPart.First && parent.Containers[1] == this) 396 | || (gridPos.y == PositionPart.Third && parent.Containers[0] == this)) 397 | parent.LeftSizeRatio = (mouse.Y + pointerDiff.y - parent.Y) / (Parent.Height * 1.0); 398 | } else { 399 | if (gridPos.y == PositionPart.First) { 400 | if (target == left) { 401 | if (grandParent && grandParent.Containers[1] == parent) 402 | grandParent.LeftSizeRatio = ((mouse.Y + pointerDiff.y - grandParent.Y) / (grandParent.Height * 1.0)); 403 | } else 404 | LeftSizeRatio = ((mouse.Y + pointerDiff.y - Y) / (Height * 1.0)); 405 | } else if (gridPos.y == PositionPart.Third) { 406 | if (target == left) 407 | LeftSizeRatio = ((mouse.Y + pointerDiff.y - Y) / (Height * 1.0)); 408 | else { 409 | if (grandParent && grandParent.Containers[0] == parent) 410 | grandParent.LeftSizeRatio = ((mouse.Y + pointerDiff.y - grandParent.Y) / (grandParent.Height * 1.0)); 411 | } 412 | } 413 | 414 | if (parent) 415 | if ((gridPos.x == PositionPart.First && parent.Containers[1] == this) 416 | || (gridPos.x == PositionPart.Third && parent.Containers[0] == this)) 417 | parent.LeftSizeRatio = (mouse.X + pointerDiff.x - parent.X) / (Parent.Width * 1.0); 418 | } 419 | 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /source/dwin/log.d: -------------------------------------------------------------------------------- 1 | module dwin.log; 2 | 3 | import std.stdio : stdout, stderr; 4 | import std.conv : toTextRange; 5 | import std.stdio : File; 6 | import std.string : format; 7 | import std.traits : fullyQualifiedName; 8 | 9 | enum LogLevel { 10 | VERBOSE, 11 | DEBUG, 12 | INFO, 13 | WARNING, 14 | ERROR, 15 | FATAL 16 | } 17 | 18 | class Log { 19 | public: 20 | static Log MainLogger() nothrow { 21 | import std.process : environment; 22 | 23 | if (mainLogger is null) { 24 | mainLogger = new Log(); 25 | try { 26 | if (environment.get("TERM")) 27 | mainLogger.AttachHandler(&TerminalHandler); 28 | else 29 | mainLogger.AttachHandler(&StdTerminalHandler); 30 | } 31 | catch (Exception e) { 32 | mainLogger.AttachHandler(&StdTerminalHandler); 33 | } 34 | 35 | mainLogger.AttachHandler(&FileLog); 36 | } 37 | return mainLogger; 38 | } 39 | 40 | alias LogHandlerFunc = void function(LogLevel level, string module_, lazy string message) nothrow; 41 | 42 | void AttachHandler(LogHandlerFunc handler) nothrow { 43 | handlers ~= handler; 44 | } 45 | 46 | void opCall(S...)(LogLevel level, string module_, lazy string format_, lazy S args) nothrow { 47 | foreach (LogHandlerFunc handler; handlers) 48 | handler(level, module_, formatMessage(format_, args)); 49 | } 50 | 51 | void Verbose(string this_ = __FUNCTION__, S...)(lazy string format, lazy S args) nothrow { 52 | Log(LogLevel.VERBOSE, /*getName!*/ this_, format, args); 53 | } 54 | 55 | void Debug(string this_ = __FUNCTION__, S...)(lazy string format, lazy S args) nothrow { 56 | Log(LogLevel.DEBUG, /*getName!*/ this_, format, args); 57 | } 58 | 59 | void Info(string this_ = __FUNCTION__, S...)(lazy string format, lazy S args) nothrow { 60 | Log(LogLevel.INFO, /*getName!*/ this_, format, args); 61 | } 62 | 63 | void Warning(string this_ = __FUNCTION__, S...)(lazy string format, lazy S args) nothrow { 64 | Log(LogLevel.WARNING, /*getName!*/ this_, format, args); 65 | } 66 | 67 | void Error(string this_ = __FUNCTION__, S...)(lazy string format, lazy S args) nothrow { 68 | Log(LogLevel.ERROR, /*getName!*/ this_, format, args); 69 | } 70 | 71 | void Fatal(string this_ = __FUNCTION__, S...)(lazy string format, lazy S args) nothrow { 72 | import std.c.stdlib : exit; 73 | 74 | Log(LogLevel.FATAL, /*getName!*/ this_, format, args); 75 | exit(-1); 76 | } 77 | 78 | @property ref File LogFile() { 79 | return Log.logFile; 80 | } 81 | 82 | private: 83 | static Log mainLogger = null; 84 | static File logFile; 85 | LogHandlerFunc[] handlers; 86 | 87 | template getName(alias this_) { 88 | import std.string : startsWith, endsWith; 89 | 90 | enum _ = fullyQualifiedName!this_; 91 | static if (_.endsWith("__ctor")) 92 | enum getName = _[0 .. $ - "__ctor".length] ~ "this" ~ fill!(80 - _.length + 2); 93 | else static if (_.endsWith("__dtor")) 94 | enum getName = _[0 .. $ - "__dtor".length] ~ "~this" ~ fill!(80 - _.length + 2); 95 | else 96 | enum getName = fullyQualifiedName!this_ ~ fill!(80 - _.length); 97 | 98 | } 99 | 100 | static string fill(int n)() { 101 | import std.range : repeat, take; 102 | import std.conv : to; 103 | 104 | static if (n < 0) 105 | return ""; 106 | return to!string(take(repeat(' '), n)); 107 | } 108 | 109 | string formatMessage(S...)(lazy string format_, lazy S args) { 110 | string message = format_; 111 | static if (args.length > 0) 112 | message = format(format_, args); 113 | return message; 114 | } 115 | 116 | version (Posix) { 117 | enum { 118 | COLOR_OFF = "\x1b[0m", /// reset color 119 | 120 | // Regular Colors 121 | FG_BLACK = "\x1b[0;30m", /// 122 | FG_RED = "\x1b[0;31m", /// 123 | FG_GREEN = "\x1b[0;32m", /// 124 | FG_YELLOW = "\x1b[0;33m", /// 125 | FG_BLUE = "\x1b[0;34m", /// 126 | FG_PURPLE = "\x1b[0;35m", /// 127 | FG_CYAN = "\x1b[0;36m", /// 128 | FG_WHITE = "\x1b[0;37m", /// 129 | 130 | // Bold 131 | FG_B_BLACK = "\x1b[1;30m", /// 132 | FG_B_RED = "\x1b[1;31m", /// 133 | FG_B_GREEN = "\x1b[1;32m", /// 134 | FG_B_YELLOW = "\x1b[1;33m", /// 135 | FG_B_BLUE = "\x1b[1;34m", /// 136 | FG_B_PURPLE = "\x1b[1;35m", /// 137 | FG_B_CYAN = "\x1b[1;36m", /// 138 | FG_B_WHITE = "\x1b[1;37m", /// 139 | 140 | // Underline 141 | FG_U_BLACK = "\x1b[4;30m", /// 142 | FG_U_RED = "\x1b[4;31m", /// 143 | FG_U_GREEN = "\x1b[4;32m", /// 144 | FG_U_YELLOW = "\x1b[4;33m", /// 145 | FG_U_BLUE = "\x1b[4;34m", /// 146 | FG_U_PURPLE = "\x1b[4;35m", /// 147 | FG_U_CYAN = "\x1b[4;36m", /// 148 | FG_U_WHITE = "\x1b[4;37m", /// 149 | 150 | // Background 151 | BG_BLACK = "\x1b[40m", /// 152 | BG_RED = "\x1b[41m", /// 153 | BG_GREEN = "\x1b[42m", /// 154 | BG_YELLOW = "\x1b[43m", /// 155 | BG_BLUE = "\x1b[44m", /// 156 | BG_PURPLE = "\x1b[45m", /// 157 | BG_CYAN = "\x1b[46m", /// 158 | BG_WHITE = "\x1b[47m", /// 159 | 160 | // High Intensity 161 | FG_I_BLACK = "\x1b[0;90m", /// 162 | FG_I_RED = "\x1b[0;91m", /// 163 | FG_I_GREEN = "\x1b[0;92m", /// 164 | FG_I_YELLOW = "\x1b[0;93m", /// 165 | FG_I_BLUE = "\x1b[0;94m", /// 166 | FG_I_PURPLE = "\x1b[0;95m", /// 167 | FG_I_CYAN = "\x1b[0;96m", /// 168 | FG_I_WHITE = "\x1b[0;97m", /// 169 | 170 | // Bold High Intensity 171 | FG_BI_BLACK = "\x1b[1;90m", /// 172 | FG_BI_RED = "\x1b[1;91m", /// 173 | FG_BI_GREEN = "\x1b[1;92m", /// 174 | FG_BI_YELLOW = "\x1b[1;93m", /// 175 | FG_BI_BLUE = "\x1b[1;94m", /// 176 | FG_BI_PURPLE = "\x1b[1;95m", /// 177 | FG_BI_CYAN = "\x1b[1;96m", /// 178 | FG_BI_WHITE = "\x1b[1;97m", /// 179 | 180 | // High Intensity backgrounds 181 | BG_I_BLACK = "\x1b[0;100m", /// 182 | BG_I_RED = "\x1b[0;101m", /// 183 | BG_I_GREEN = "\x1b[0;102m", /// 184 | BG_I_YELLOW = "\x1b[0;103m", /// 185 | BG_I_BLUE = "\x1b[0;104m", /// 186 | BG_I_PURPLE = "\x1b[0;105m", /// 187 | BG_I_CYAN = "\x1b[0;106m", /// 188 | BG_I_WHITE = "\x1b[0;107m", /// 189 | } 190 | 191 | static void TerminalHandler(LogLevel level, string module_, lazy string message) nothrow { 192 | string icon; 193 | string color; 194 | 195 | final switch (level) { 196 | case LogLevel.VERBOSE: 197 | icon = "&"; 198 | color = FG_GREEN; 199 | break; 200 | case LogLevel.DEBUG: 201 | icon = "+"; 202 | color = FG_YELLOW; 203 | break; 204 | case LogLevel.INFO: 205 | icon = "*"; 206 | color = FG_CYAN; 207 | break; 208 | case LogLevel.WARNING: 209 | icon = "#"; 210 | color = FG_PURPLE; 211 | break; 212 | case LogLevel.ERROR: 213 | icon = "-"; 214 | color = FG_RED; 215 | break; 216 | case LogLevel.FATAL: 217 | icon = "!"; 218 | color = FG_BLACK ~ BG_RED; 219 | break; 220 | } 221 | try { 222 | string levelText = format("[%1$s%3$s%2$s] [%1$s%4$s%2$s] %1$s%5$s%2$s", color, COLOR_OFF, icon, module_, message); 223 | 224 | if (level >= LogLevel.WARNING) { 225 | stderr.writeln(levelText); 226 | stderr.flush; 227 | } else { 228 | stdout.writeln(levelText); 229 | stdout.flush; 230 | } 231 | } 232 | catch (Exception e) { 233 | import std.c.stdlib : exit; 234 | import std.stdio : stderr; 235 | 236 | try { 237 | stderr.writeln(e.toString); 238 | } 239 | catch (Exception) { 240 | } 241 | 242 | exit(-2); 243 | } 244 | } 245 | } else { 246 | static void TerminalHandler(LogLevel level, string module_, lazy string message) nothrow { 247 | return StdTerminalHandler(level, module_, message); 248 | } 249 | } 250 | 251 | static void StdTerminalHandler(LogLevel level, string module_, lazy string message) nothrow { 252 | string icon; 253 | 254 | final switch (level) { 255 | case LogLevel.VERBOSE: 256 | icon = "&"; 257 | break; 258 | case LogLevel.DEBUG: 259 | icon = "+"; 260 | break; 261 | case LogLevel.INFO: 262 | icon = "*"; 263 | break; 264 | case LogLevel.WARNING: 265 | icon = "#"; 266 | break; 267 | case LogLevel.ERROR: 268 | icon = "-"; 269 | break; 270 | case LogLevel.FATAL: 271 | icon = "!"; 272 | break; 273 | } 274 | try { 275 | string levelText = format("[%s] [%s]\t %s", icon, module_, message); 276 | 277 | if (level >= LogLevel.WARNING) { 278 | stderr.writeln(levelText); 279 | stderr.flush; 280 | } else { 281 | stdout.writeln(levelText); 282 | stdout.flush; 283 | } 284 | } 285 | catch (Exception e) { 286 | import std.c.stdlib : exit; 287 | import std.stdio : stderr; 288 | 289 | try { 290 | stderr.writeln(e.toString); 291 | } 292 | catch (Exception) { 293 | } 294 | 295 | exit(-2); 296 | } 297 | } 298 | 299 | static void FileLog(LogLevel level, string module_, lazy string message) nothrow { 300 | import std.string; 301 | import std.datetime; 302 | 303 | if (!Log.logFile.isOpen) 304 | return; 305 | string icon; 306 | 307 | final switch (level) { 308 | case LogLevel.VERBOSE: 309 | icon = "&"; 310 | break; 311 | case LogLevel.DEBUG: 312 | icon = "+"; 313 | break; 314 | case LogLevel.INFO: 315 | icon = "*"; 316 | break; 317 | case LogLevel.WARNING: 318 | icon = "#"; 319 | break; 320 | case LogLevel.ERROR: 321 | icon = "-"; 322 | break; 323 | case LogLevel.FATAL: 324 | icon = "!"; 325 | break; 326 | } 327 | try { 328 | SysTime t = Clock.currTime; 329 | 330 | auto dateTime = DateTime(Date(t.year, t.month, t.day), TimeOfDay(t.hour, t.minute, t.second)); 331 | 332 | string time = dateTime.toSimpleString; 333 | 334 | string levelText = format("[%s] [%s] [%s]\t %s", icon, module_, time, message); 335 | 336 | logFile.writeln(levelText); 337 | logFile.flush(); 338 | } 339 | catch (Exception e) { 340 | import std.c.stdlib : exit; 341 | import std.stdio : stderr; 342 | 343 | try { 344 | stderr.writeln(e.toString); 345 | } 346 | catch (Exception) { 347 | } 348 | 349 | exit(-2); 350 | } 351 | } 352 | 353 | } 354 | -------------------------------------------------------------------------------- /source/dwin/script/api/bindmanagerapi.d: -------------------------------------------------------------------------------- 1 | module dwin.script.api.bindmanagerapi; 2 | 3 | import dwin.log; 4 | import dwin.script.utils; 5 | import dwin.backend.bindmanager; 6 | 7 | struct BindManagerAPI { 8 | void Init(BindManager bindManager) { 9 | this.bindManager = bindManager; 10 | } 11 | 12 | var Map(var, var[] args) { 13 | bindManager.Map(cast(string)args[0], delegate(bool v) { 14 | try { 15 | args[1](v); 16 | } 17 | catch (Exception e) { // "No such property" throws a object.Exception 18 | Log.MainLogger.Error("%s", e.msg); 19 | } 20 | }); 21 | 22 | return var.emptyObject; 23 | } 24 | 25 | var Unmap(var, var[] args) { 26 | bindManager.Unmap(cast(string)args[0]); 27 | 28 | return var.emptyObject; 29 | } 30 | 31 | var IsBinded(var, var[] args) { 32 | return var(bindManager.IsBinded(cast(string)args[0])); 33 | } 34 | 35 | BindManager bindManager; 36 | mixin ObjectWrapper; 37 | } 38 | -------------------------------------------------------------------------------- /source/dwin/script/api/dataapi.d: -------------------------------------------------------------------------------- 1 | module dwin.script.api.dataapi; 2 | 3 | import dwin.script.utils; 4 | 5 | import std.json; 6 | import std.array; 7 | 8 | struct DataAPI { 9 | var fromJson(var, var[] args) { 10 | return var.fromJson(cast(string)args[0]); 11 | } 12 | 13 | var toJson(var, var[] args) { 14 | return var(args[0].toJson); 15 | } 16 | 17 | var Split(var, var[] args) { 18 | if (args.length == 1) 19 | return var(split(cast(string)args[0])); 20 | else 21 | return var(split(cast(string)args[0], cast(string)args[1])); 22 | } 23 | 24 | var Join(var, var[] args) { 25 | if (args.length == 1) 26 | return var(join(cast(string[])args[0])); 27 | else 28 | return var(join(cast(string[])args[0], cast(string)args[1])); 29 | } 30 | 31 | mixin ObjectWrapper; 32 | } 33 | -------------------------------------------------------------------------------- /source/dwin/script/api/engineapi.d: -------------------------------------------------------------------------------- 1 | module dwin.script.api.engineapi; 2 | 3 | import dwin.backend.engine; 4 | import dwin.script.utils; 5 | 6 | import dwin.backend.container; 7 | import dwin.backend.layout; 8 | import dwin.backend.screen; 9 | import dwin.backend.window; 10 | import dwin.backend.workspace; 11 | 12 | struct EngineAPI { 13 | 14 | void Init(Engine engine) { 15 | this.engine = engine; 16 | } 17 | 18 | var GetScreens(var, var[]) { 19 | var screens = var.emptyArray; 20 | foreach (screen; engine.Screens) 21 | screens ~= createVar(screen); 22 | return screens; 23 | } 24 | 25 | var RegisterTick(var, var[] args) { 26 | engine.RegisterTick(() => cast(void)args[0]()); 27 | return var.emptyObject; 28 | } 29 | 30 | mixin ObjectWrapper; 31 | Engine engine; 32 | 33 | private: 34 | var createVar(Screen screen) { 35 | var scr = var.emptyObject; 36 | scr.Name = screen.Name; 37 | scr.CurrentWorkspace = screen.CurrentWorkspace; 38 | scr.OnTop = createVar(screen.OnTop); 39 | scr.Workspaces = var.emptyArray; 40 | foreach (workspace; screen.Workspaces) 41 | scr.Workspaces ~= createVar(workspace); 42 | return scr; 43 | } 44 | 45 | var createVar(Workspace workspace) { 46 | var work = var.emptyObject; 47 | work.Name = workspace.Name; 48 | work.OnTop = createVar(workspace.OnTop); 49 | work.Root = createVar(workspace.Root); 50 | work.ActiveWindow = createVar(workspace.ActiveWindow); 51 | return work; 52 | } 53 | 54 | var createVar(Container con) { 55 | if (auto win = cast(Window)con) 56 | return createVar(win); 57 | else if (auto layout = cast(Layout)con) 58 | return createVar(layout); 59 | return var(null); 60 | } 61 | 62 | var createVar(Layout layout) { 63 | var l = var.emptyObject; 64 | l.IsWindow = false; 65 | l["ToString"] = layout.toString(); 66 | l.IsVisible = layout.IsVisible; 67 | l.Containers = var.emptyArray; 68 | foreach (container; layout.Containers) 69 | l.Containers ~= createVar(container); 70 | return l; 71 | } 72 | 73 | var createVar(Window window) { 74 | if (!window) 75 | return var.emptyObject; 76 | var win = var.emptyObject; 77 | win.IsWindow = true; 78 | win.Title = window.Title(); 79 | win["ToString"] = window.toString(); 80 | win.IsVisible = window.IsVisible; 81 | return win; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /source/dwin/script/api/infoapi.d: -------------------------------------------------------------------------------- 1 | module dwin.script.api.infoapi; 2 | 3 | import dwin.script.utils; 4 | 5 | struct InfoAPI { 6 | var AddCtor(var, var[] args) { 7 | Ctors[cast(string)args[0]] = args[1]; 8 | return var.emptyObject; 9 | } 10 | 11 | var AddDtor(var, var[] args) { 12 | Dtors[cast(string)args[0]] = args[1]; 13 | return var.emptyObject; 14 | } 15 | 16 | mixin ObjectWrapper; 17 | 18 | var[string] Ctors; 19 | var[string] Dtors; 20 | } 21 | -------------------------------------------------------------------------------- /source/dwin/script/api/ioapi.d: -------------------------------------------------------------------------------- 1 | module dwin.script.api.ioapi; 2 | 3 | import dwin.script.utils; 4 | 5 | import std.stdio; 6 | import std.file; 7 | 8 | struct IOAPI { 9 | void Init(string scriptFolder) { 10 | this.scriptFolder = scriptFolder; 11 | } 12 | 13 | var ReadFile(var, var[] args) { 14 | //TODO: Restrict where to read the files from! 15 | return var(readText(scriptFolder ~ cast(string)args[0])); 16 | } 17 | 18 | mixin ObjectWrapper; 19 | string scriptFolder; 20 | } 21 | -------------------------------------------------------------------------------- /source/dwin/script/api/logapi.d: -------------------------------------------------------------------------------- 1 | module dwin.script.api.logapi; 2 | 3 | import dwin.log; 4 | import dwin.script.utils; 5 | 6 | struct LogAPI { 7 | void Init() { 8 | log = Log.MainLogger(); 9 | } 10 | 11 | var Verbose(var, var[] args) { 12 | log.Verbose!("SCRIPT")("%(%s, %)", args); 13 | return var.emptyObject; 14 | } 15 | 16 | var Debug(var, var[] args) { 17 | log.Debug!("SCRIPT")("%(%s, %)", args); 18 | return var.emptyObject; 19 | } 20 | 21 | var Info(var, var[] args) { 22 | log.Info!("SCRIPT")("%(%s, %)", args); 23 | return var.emptyObject; 24 | } 25 | 26 | var Warning(var, var[] args) { 27 | log.Warning!("SCRIPT")("%(%s, %)", args); 28 | return var.emptyObject; 29 | } 30 | 31 | var Error(var, var[] args) { 32 | log.Error!("SCRIPT")("%(%s, %)", args); 33 | return var.emptyObject; 34 | } 35 | 36 | mixin ObjectWrapper; 37 | Log log; 38 | } 39 | -------------------------------------------------------------------------------- /source/dwin/script/api/package.d: -------------------------------------------------------------------------------- 1 | module dwin.script.api; 2 | 3 | public: 4 | import dwin.script.api.bindmanagerapi; 5 | import dwin.script.api.dataapi; 6 | import dwin.script.api.engineapi; 7 | import dwin.script.api.infoapi; 8 | import dwin.script.api.ioapi; 9 | import dwin.script.api.logapi; 10 | import dwin.script.api.systemapi; 11 | -------------------------------------------------------------------------------- /source/dwin/script/api/systemapi.d: -------------------------------------------------------------------------------- 1 | module dwin.script.api.systemapi; 2 | 3 | import dwin.script.utils; 4 | import std.process; 5 | 6 | struct SystemAPI { 7 | var GetDate(var, var[]) { 8 | import std.datetime : Clock, DateTime; 9 | 10 | auto time = cast(DateTime)Clock.currTime; 11 | return var(time.toString); 12 | } 13 | 14 | var SpawnProcess(var, var[] args) { 15 | auto arg0 = args[0]; 16 | string[] spawnArgs; 17 | if (arg0.payloadType == var.Type.String) 18 | spawnArgs = parseArgs(cast(string)arg0); 19 | else 20 | foreach (arg; arg0) 21 | spawnArgs ~= cast(string)arg; 22 | 23 | spawnProcess(spawnArgs); 24 | 25 | return var.emptyObject; 26 | } 27 | 28 | var PipeProcess(var, var[] args) { 29 | auto id = cast(string)args[0]; 30 | auto pArgs = args[1]; 31 | string[] spawnArgs; 32 | if (pArgs.payloadType == var.Type.String) 33 | spawnArgs = parseArgs(cast(string)pArgs); 34 | else 35 | foreach (arg; pArgs) 36 | spawnArgs ~= cast(string)arg; 37 | 38 | processes[id] = pipeProcess(spawnArgs); 39 | return var.emptyObject; 40 | } 41 | 42 | var WritePipeProcess(var, var[] args) { 43 | if (ProcessPipes* pipes = (cast(string)args[0]) in processes) { 44 | pipes.stdin.writeln(cast(string)args[1]); 45 | pipes.stdin.flush(); 46 | } 47 | 48 | return var.emptyObject; 49 | } 50 | 51 | var KillPipeProcess(var, var[] args) { 52 | if (ProcessPipes* pipes = (cast(string)args[0]) in processes) { 53 | kill(pipes.pid); 54 | processes.remove(cast(string)args[0]); 55 | } 56 | 57 | return var.emptyObject; 58 | } 59 | 60 | string[] parseArgs(string arg) { 61 | string[] args; 62 | string tmp; 63 | bool strip = false; 64 | char inQuote = '\0'; 65 | foreach (char ch; arg) { 66 | if (strip) { 67 | tmp ~= ch; 68 | strip = false; 69 | continue; 70 | } 71 | 72 | if (ch == '\\' && !strip) 73 | strip = true; 74 | else { 75 | if (ch == '\"' && !strip && (inQuote == '\0' || inQuote == '\"')) 76 | inQuote = (inQuote == '\0') ? ch : '\0'; 77 | else if (ch == '\'' && !strip && (inQuote == '\0' || inQuote == '\'')) 78 | inQuote = (inQuote == '\0') ? ch : '\0'; 79 | else if (ch == ' ' && !strip && inQuote == '\0') { 80 | args ~= tmp; 81 | tmp = ""; 82 | } else 83 | tmp ~= ch; 84 | 85 | strip = false; 86 | } 87 | } 88 | if (tmp.length) 89 | args ~= tmp; 90 | return args; 91 | } 92 | 93 | ProcessPipes[string] processes; 94 | mixin ObjectWrapper; 95 | } 96 | -------------------------------------------------------------------------------- /source/dwin/script/script.d: -------------------------------------------------------------------------------- 1 | module dwin.script.script; 2 | 3 | import std.stdio; 4 | import std.file; 5 | import std.string; 6 | import std.algorithm.iteration; 7 | import dwin.dwin; 8 | import arsd.script; 9 | public import dwin.script.utils; 10 | 11 | import dwin.script.api; 12 | import dwin.log; 13 | 14 | class Script { 15 | public: 16 | this(DWin dwin, string scriptFolder) { 17 | this.scriptFolder = scriptFolder; 18 | 19 | bindManagerAPI.Init(dwin.Engine.BindManager); 20 | engineAPI.Init(dwin.Engine); 21 | ioAPI.Init(scriptFolder); 22 | logAPI.Init(); 23 | 24 | env = var.emptyObject; 25 | env.BindManager = bindManagerAPI.Get(); 26 | env.Data = dataAPI.Get(); 27 | env.Engine = engineAPI.Get(); 28 | env.Info = infoAPI.Get(); 29 | env.IO = ioAPI.Get(); 30 | env.Log = logAPI.Get(); 31 | env.System = systemAPI.Get(); 32 | 33 | foreach (file; dirEntries(scriptFolder, SpanMode.breadth).filter!(f => f.name.endsWith(".ds"))) { 34 | Log.MainLogger.Info("Loading script: %s", file); 35 | runFile(File(file, "r")); 36 | } 37 | } 38 | 39 | ~this() { 40 | } 41 | 42 | void RunCtors() { 43 | foreach (idx, ctor; infoAPI.Ctors) 44 | ctor(); 45 | } 46 | 47 | void RunDtors() { 48 | foreach (idx, dtor; infoAPI.Dtors) 49 | dtor(); 50 | } 51 | 52 | void opDispatch(string func, string callerFile = __FILE__, int callerLine = __LINE__, Args...)(Args args) { 53 | import std.traits : isSomeString, isBasicType; 54 | import std.conv : to; 55 | 56 | string line = func ~ "("; 57 | foreach (idx, arg; args) { 58 | static if (idx) 59 | line ~= ", "; 60 | static if (isSomeString!(typeof(arg))) 61 | line ~= "\"" ~ arg ~ "\""; 62 | else static if (isBasicType!(typeof(arg))) 63 | line ~= to!string(arg); 64 | else { 65 | //XXX: Hack to show why the function could not be called! 66 | pragma(msg, callerFile, "(", callerLine, 67 | ",1): Error: Script Function: " ~ func ~ ", Unsupported argument type: " ~ typeof(arg).stringof); 68 | static assert(0, "Unsupported argument type: " ~ typeof(arg).stringof); 69 | } 70 | } 71 | line ~= ");"; 72 | run(line); 73 | } 74 | 75 | @property string ScriptFolder() { 76 | return scriptFolder; 77 | } 78 | 79 | private: 80 | string scriptFolder; 81 | var env; 82 | 83 | BindManagerAPI bindManagerAPI; 84 | DataAPI dataAPI; 85 | EngineAPI engineAPI; 86 | InfoAPI infoAPI; 87 | IOAPI ioAPI; 88 | LogAPI logAPI; 89 | SystemAPI systemAPI; 90 | 91 | void run(string str) { 92 | try { 93 | interpret(str, env); 94 | } 95 | catch (Exception e) { // "No such property" throws a object.Exception 96 | Log.MainLogger.Error("%s", e.msg); 97 | } 98 | } 99 | 100 | void runFile(File file) { 101 | try { 102 | interpretFile(file, env); 103 | } 104 | catch (Exception e) { // "No such property" throws a object.Exception 105 | Log.MainLogger.Error("%s", e.msg); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /source/dwin/script/utils.d: -------------------------------------------------------------------------------- 1 | module dwin.script.utils; 2 | 3 | public import arsd.jsvar; 4 | 5 | struct PublicVar { 6 | } 7 | 8 | mixin template ObjectWrapper() { 9 | var Get() { 10 | import std.traits : TemplateArgsOf, hasUDA; 11 | 12 | var obj = var.emptyObject; 13 | 14 | var a; 15 | var[] b; 16 | 17 | alias func = var delegate(var, var[]); 18 | 19 | foreach (memberName; __traits(allMembers, typeof(this))) { 20 | static if (is(typeof(__traits(getMember, this, memberName)) type)) { 21 | static if (is(typeof(__traits(getMember, this, memberName)))) { 22 | static if (hasUDA!(__traits(getMember, this, memberName), PublicVar)) { 23 | obj[memberName] = &__traits(getMember, this, memberName); 24 | } else static if (__traits(compiles, (__traits(getMember, this, memberName))(a, b))) { 25 | static if (!is(typeof(&__traits(getMember, this, memberName)) == var*)) { 26 | obj[memberName]._function = &__traits(getMember, this, memberName); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | 33 | return obj; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /source/dwin/util/data.d: -------------------------------------------------------------------------------- 1 | module dwin.util.data; 2 | 3 | static size_t EnumCount(T)() { 4 | import std.traits : EnumMembers; 5 | 6 | size_t len = 0; 7 | foreach (x; EnumMembers!T) 8 | len++; 9 | return len; 10 | } 11 | 12 | struct vec2 { 13 | int x; 14 | int y; 15 | } 16 | 17 | struct Geometry { 18 | int x; 19 | int y; 20 | 21 | int width; 22 | int height; 23 | } 24 | --------------------------------------------------------------------------------