├── .elpaignore ├── .gitignore ├── LICENSE ├── README.md ├── exwm-background.el ├── exwm-core.el ├── exwm-floating.el ├── exwm-input.el ├── exwm-layout.el ├── exwm-manage.el ├── exwm-randr.el ├── exwm-systemtray.el ├── exwm-workspace.el ├── exwm-xim.el ├── exwm-xsettings.el └── exwm.el /.elpaignore: -------------------------------------------------------------------------------- 1 | LICENSE 2 | README.md 3 | .elpaignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | *-pkg.el 3 | *-autoloads.el 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Emacs X Window Manager 2 | 3 | EXWM (Emacs X Window Manager) is a full-featured tiling X window manager 4 | for Emacs built on top of [XELB](https://github.com/emacs-exwm/xelb). 5 | 6 | It features: 7 | + Fully keyboard-driven operations 8 | + Hybrid layout modes (tiling & stacking) 9 | + Dynamic workspace support 10 | + ICCCM/EWMH compliance 11 | 12 | Optional features: 13 | + RandR (multi-monitor) support 14 | + System tray 15 | + Input method 16 | + Background setting support 17 | + XSETTINGS server 18 | 19 | Please check out the 20 | [screenshots](https://github.com/emacs-exwm/exwm/wiki/Screenshots) 21 | to get an overview of what EXWM is capable of, and the 22 | [user guide](https://github.com/emacs-exwm/exwm/wiki) 23 | for installation instructions and a detailed explanation of its usage. 24 | -------------------------------------------------------------------------------- /exwm-background.el: -------------------------------------------------------------------------------- 1 | ;;; exwm-background.el --- X Background Module for EXWM -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2022-2025 Free Software Foundation, Inc. 4 | 5 | ;; Author: Steven Allen 6 | 7 | ;; This file is part of GNU Emacs. 8 | 9 | ;; GNU Emacs is free software: you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation, either version 3 of the License, or 12 | ;; (at your option) any later version. 13 | 14 | ;; GNU Emacs is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with GNU Emacs. If not, see . 21 | 22 | ;;; Commentary: 23 | 24 | ;; This module adds X background color setting support to EXWM. 25 | 26 | ;; To use this module, enable it as follows: 27 | ;; 28 | ;; (exwm-background-mode 1) 29 | ;; 30 | ;; By default, this will apply the theme's background color. However, that 31 | ;; color can be customized via the `exwm-background-color' setting. 32 | 33 | ;;; Code: 34 | 35 | (require 'exwm-core) 36 | (eval-when-compile (require 'subr-x)) ;; Needed on 28 for when-let* 37 | 38 | (defgroup exwm-background nil 39 | "Background support." 40 | :group 'exwm) 41 | 42 | ;;;###autoload 43 | (define-minor-mode exwm-background-mode 44 | "Toggle EXWM background support." 45 | :global t 46 | :group 'exwm-background 47 | (exwm--global-minor-mode-body background)) 48 | 49 | (defcustom exwm-background-color nil 50 | "Background color for Xorg." 51 | :type '(choice 52 | (color :tag "Background Color") 53 | (const :tag "Default" nil)) 54 | :initialize #'custom-initialize-default 55 | :set (lambda (symbol value) 56 | (set-default-toplevel-value symbol value) 57 | (when exwm-background-mode (exwm-background--update)))) 58 | 59 | (defconst exwm-background--properties '("_XROOTPMAP_ID" "_XSETROOT_ID" "ESETROOT_PMAP_ID") 60 | "The background properties to set. 61 | We can't need to set these so that compositing window managers 62 | can correctly display the background color.") 63 | 64 | (defvar exwm-background--connection nil 65 | "The X connection used for setting the background. 66 | We use a separate connection as other background-setting tools 67 | may kill this connection when they replace it.") 68 | 69 | (defvar exwm-background--pixmap nil 70 | "Cached background pixmap.") 71 | 72 | (defvar exwm-background--atoms nil 73 | "Cached background atoms.") 74 | 75 | (defun exwm-background--update (&rest _) 76 | "Update the EXWM background." 77 | 78 | ;; Always reconnect as any tool that sets the background may have disconnected us (to force X to 79 | ;; free resources). 80 | (exwm-background--connect) 81 | 82 | (let ((gc (xcb:generate-id exwm-background--connection)) 83 | (color (exwm--color->pixel (or exwm-background-color 84 | (face-background 'default))))) 85 | ;; Fill the pixmap. 86 | (xcb:+request exwm-background--connection 87 | (make-instance 'xcb:CreateGC 88 | :cid gc :drawable exwm-background--pixmap 89 | :value-mask (logior xcb:GC:Foreground 90 | xcb:GC:GraphicsExposures) 91 | :foreground color 92 | :graphics-exposures 0)) 93 | 94 | (xcb:+request exwm-background--connection 95 | (make-instance 'xcb:PolyFillRectangle 96 | :gc gc :drawable exwm-background--pixmap 97 | :rectangles 98 | (list 99 | (make-instance 100 | 'xcb:RECTANGLE 101 | :x 0 :y 0 :width 1 :height 1)))) 102 | (xcb:+request exwm-background--connection (make-instance 'xcb:FreeGC :gc gc))) 103 | 104 | ;; Reapply it to force an update (also clobber anyone else who may have set it). 105 | (xcb:+request exwm-background--connection 106 | (make-instance 'xcb:ChangeWindowAttributes 107 | :window exwm--root 108 | :value-mask xcb:CW:BackPixmap 109 | :background-pixmap exwm-background--pixmap)) 110 | 111 | (let (old) 112 | ;; Collect old pixmaps so we can kill other background clients (all the background setting tools 113 | ;; seem to do this). 114 | (dolist (atom exwm-background--atoms) 115 | (when-let* ((reply (xcb:+request-unchecked+reply exwm-background--connection 116 | (make-instance 'xcb:GetProperty 117 | :delete 0 118 | :window exwm--root 119 | :property atom 120 | :type xcb:Atom:PIXMAP 121 | :long-offset 0 122 | :long-length 1))) 123 | (value (vconcat (slot-value reply 'value))) 124 | ((length= value 4)) 125 | (pixmap (funcall (if xcb:lsb #'xcb:-unpack-u4-lsb #'xcb:-unpack-u4) 126 | value 0)) 127 | ((not (or (= pixmap exwm-background--pixmap) 128 | (member pixmap old))))) 129 | (push pixmap old))) 130 | 131 | ;; Change the background. 132 | (dolist (atom exwm-background--atoms) 133 | (xcb:+request exwm-background--connection 134 | (make-instance 'xcb:ChangeProperty 135 | :window exwm--root 136 | :property atom 137 | :type xcb:Atom:PIXMAP 138 | :format 32 139 | :mode xcb:PropMode:Replace 140 | :data-len 1 141 | :data 142 | (funcall (if xcb:lsb 143 | #'xcb:-pack-u4-lsb 144 | #'xcb:-pack-u4) 145 | exwm-background--pixmap)))) 146 | 147 | ;; Kill the old background clients. 148 | (dolist (pixmap old) 149 | (xcb:+request exwm-background--connection 150 | (make-instance 'xcb:KillClient :resource pixmap)))) 151 | 152 | (xcb:flush exwm-background--connection)) 153 | 154 | (defun exwm-background--connected-p () 155 | "Return t if a live background connection process exists and is connected." 156 | (and exwm-background--connection 157 | (process-live-p (slot-value exwm-background--connection 'process)))) 158 | 159 | (defun exwm-background--connect () 160 | "Establish background Pixmap connection." 161 | (unless (exwm-background--connected-p) 162 | (setq exwm-background--connection (xcb:connect)) 163 | ;;prevent query message on exit 164 | (set-process-query-on-exit-flag (slot-value exwm-background--connection 'process) nil) 165 | 166 | ;; Intern the background property atoms. 167 | (setq exwm-background--atoms 168 | (mapcar 169 | (lambda (prop) (exwm--intern-atom prop exwm-background--connection)) 170 | exwm-background--properties)) 171 | 172 | ;; Create the pixmap. 173 | (setq exwm-background--pixmap (xcb:generate-id exwm-background--connection)) 174 | (xcb:+request exwm-background--connection 175 | (make-instance 'xcb:CreatePixmap 176 | :depth 177 | (slot-value 178 | (xcb:+request-unchecked+reply exwm-background--connection 179 | (make-instance 'xcb:GetGeometry :drawable exwm--root)) 180 | 'depth) 181 | :pid exwm-background--pixmap 182 | :drawable exwm--root 183 | :width 1 :height 1)))) 184 | 185 | (defun exwm-background--init () 186 | "Initialize background module." 187 | (exwm--log) 188 | (add-hook 'enable-theme-functions 'exwm-background--update) 189 | (add-hook 'disable-theme-functions 'exwm-background--update) 190 | (exwm-background--update)) 191 | 192 | (defun exwm-background--exit () 193 | "Uninitialize the background module." 194 | (exwm--log) 195 | (remove-hook 'enable-theme-functions 'exwm-background--update) 196 | (remove-hook 'disable-theme-functions 'exwm-background--update) 197 | (when (and exwm-background--connection 198 | (slot-value exwm-background--connection 'connected)) 199 | (xcb:disconnect exwm-background--connection)) 200 | (setq exwm-background--pixmap nil 201 | exwm-background--connection nil 202 | exwm-background--atoms nil)) 203 | 204 | (provide 'exwm-background) 205 | ;;; exwm-background.el ends here 206 | -------------------------------------------------------------------------------- /exwm-core.el: -------------------------------------------------------------------------------- 1 | ;;; exwm-core.el --- Core definitions -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2015-2025 Free Software Foundation, Inc. 4 | 5 | ;; Author: Chris Feng 6 | 7 | ;; This file is part of GNU Emacs. 8 | 9 | ;; GNU Emacs is free software: you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation, either version 3 of the License, or 12 | ;; (at your option) any later version. 13 | 14 | ;; GNU Emacs is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with GNU Emacs. If not, see . 21 | 22 | ;;; Commentary: 23 | 24 | ;; This module includes core definitions of variables, macros, functions, etc 25 | ;; shared by various other modules. 26 | 27 | ;;; Code: 28 | 29 | (require 'compat) 30 | (require 'kmacro) 31 | 32 | (require 'xcb) 33 | (require 'xcb-icccm) 34 | (require 'xcb-ewmh) 35 | (require 'xcb-debug) 36 | 37 | (defgroup exwm-debug nil 38 | "Debugging." 39 | :group 'exwm) 40 | 41 | (defcustom exwm-debug-log-time-function #'exwm-debug-log-uptime 42 | "Function used for generating timestamps in debug log. 43 | 44 | Here are some predefined candidates: 45 | `exwm-debug-log-uptime': Display the uptime of this Emacs instance. 46 | `exwm-debug-log-time': Display time of day. 47 | nil: Disable timestamp." 48 | :type `(choice (const :tag "Emacs uptime" ,#'exwm-debug-log-uptime) 49 | (const :tag "Time of day" ,#'exwm-debug-log-time) 50 | (const :tag "Off" nil) 51 | (function :tag "Other")) 52 | :set (lambda (symbol value) 53 | (set-default symbol value) 54 | ;; Also change the format for XELB to make logs consistent 55 | ;; (as they share the same buffer). 56 | (setq xcb-debug:log-time-function value))) 57 | 58 | (defalias 'exwm-debug-log-uptime 'xcb-debug:log-uptime 59 | "Add uptime to `exwm-debug' logs.") 60 | 61 | (defalias 'exwm-debug-log-time 'xcb-debug:log-time 62 | "Add time of day to `exwm-debug' logs.") 63 | 64 | (defvar exwm--connection nil "X connection.") 65 | 66 | (defvar exwm--terminal nil 67 | "Terminal corresponding to `exwm--connection'.") 68 | 69 | (defvar exwm--wmsn-window nil 70 | "An X window owning the WM_S0 selection.") 71 | 72 | (defvar exwm--wmsn-acquire-timeout 3 73 | "Number of seconds to wait for other window managers to release the selection.") 74 | 75 | (defvar exwm--guide-window nil 76 | "An X window separating workspaces and X windows.") 77 | 78 | (defvar exwm--id-buffer-alist nil "Alist of ( . ).") 79 | 80 | (defvar exwm--root nil "Root window.") 81 | 82 | (defvar exwm-input--global-prefix-keys) 83 | (defvar exwm-input--simulation-keys) 84 | (defvar exwm-input-line-mode-passthrough) 85 | (defvar exwm-input-prefix-keys) 86 | (defvar exwm-workspace--list) 87 | (declare-function exwm-input--fake-key "exwm-input.el" (event)) 88 | (declare-function exwm-input--on-KeyPress-line-mode "exwm-input.el" 89 | (key-press raw-data)) 90 | (declare-function exwm-floating-hide "exwm-floating.el") 91 | (declare-function exwm-floating-toggle-floating "exwm-floating.el") 92 | (declare-function exwm-input-release-keyboard "exwm-input.el") 93 | (declare-function exwm-input-send-next-key "exwm-input.el" (times)) 94 | (declare-function exwm-layout-set-fullscreen "exwm-layout.el" (&optional id)) 95 | (declare-function exwm-layout-toggle-mode-line "exwm-layout.el") 96 | (declare-function exwm-manage--kill-buffer-query-function "exwm-manage.el") 97 | (declare-function exwm-workspace-move-window "exwm-workspace.el" 98 | (frame-or-index &optional id)) 99 | (declare-function exwm-workspace-switch "exwm-workspace.el" 100 | (frame-or-index &optional force)) 101 | 102 | (defvaralias 'exwm-debug 'exwm-debug-mode) 103 | (define-minor-mode exwm-debug-mode 104 | "Debug-logging enabled if non-nil." 105 | :global t 106 | :group 'exwm-debug) 107 | 108 | (defmacro exwm--debug (&rest forms) 109 | "Evaluate FORMS if `exwm-debug-mode' is active." 110 | (when exwm-debug `(progn ,@forms))) 111 | 112 | (defmacro exwm--log (&optional format-string &rest objects) 113 | "Emit a message prepending the name of the function being executed. 114 | 115 | FORMAT-STRING is a string specifying the message to output, as in 116 | `format'. The OBJECTS arguments specify the substitutions." 117 | (unless format-string (setq format-string "")) 118 | `(when exwm-debug 119 | (xcb-debug:message ,(concat "%s%s:\t" format-string "\n") 120 | (if exwm-debug-log-time-function 121 | (funcall exwm-debug-log-time-function) 122 | "") 123 | (xcb-debug:compile-time-function-name) 124 | ,@objects) 125 | nil)) 126 | 127 | (defsubst exwm--id->buffer (id) 128 | "X window ID => Emacs buffer." 129 | (declare (indent defun)) 130 | (cdr (assoc id exwm--id-buffer-alist))) 131 | 132 | (defsubst exwm--buffer->id (buffer) 133 | "Emacs buffer BUFFER => X window ID." 134 | (declare (indent defun)) 135 | (car (rassoc buffer exwm--id-buffer-alist))) 136 | 137 | (defun exwm--lock (&rest _args) 138 | "Lock (disable all events)." 139 | (exwm--log) 140 | (xcb:+request exwm--connection 141 | (make-instance 'xcb:ChangeWindowAttributes 142 | :window exwm--root 143 | :value-mask xcb:CW:EventMask 144 | :event-mask xcb:EventMask:NoEvent)) 145 | (xcb:flush exwm--connection)) 146 | 147 | (defun exwm--unlock (&rest _args) 148 | "Unlock (enable all events)." 149 | (exwm--log) 150 | (xcb:+request exwm--connection 151 | (make-instance 'xcb:ChangeWindowAttributes 152 | :window exwm--root 153 | :value-mask xcb:CW:EventMask 154 | :event-mask (eval-when-compile 155 | (logior xcb:EventMask:SubstructureRedirect 156 | xcb:EventMask:StructureNotify)))) 157 | (xcb:flush exwm--connection)) 158 | 159 | (defun exwm--set-geometry (xwin x y width height) 160 | "Set the geometry of X window XWIN to WIDTHxHEIGHT+X+Y. 161 | 162 | Nil can be passed as placeholder." 163 | (exwm--log "Setting #x%x to %sx%s+%s+%s" xwin width height x y) 164 | (xcb:+request exwm--connection 165 | (make-instance 'xcb:ConfigureWindow 166 | :window xwin 167 | :value-mask (logior (if x xcb:ConfigWindow:X 0) 168 | (if y xcb:ConfigWindow:Y 0) 169 | (if width xcb:ConfigWindow:Width 0) 170 | (if height xcb:ConfigWindow:Height 0)) 171 | :x x :y y :width width :height height))) 172 | 173 | (defun exwm--intern-atom (atom &optional conn) 174 | "Intern X11 ATOM. 175 | If CONN is non-nil, use it instead of the value of the variable 176 | `exwm--connection'." 177 | (slot-value (xcb:+request-unchecked+reply (or conn exwm--connection) 178 | (make-instance 'xcb:InternAtom 179 | :only-if-exists 0 180 | :name-len (length atom) 181 | :name atom)) 182 | 'atom)) 183 | 184 | (defmacro exwm--defer (secs function &rest args) 185 | "Defer the execution of FUNCTION. 186 | 187 | The action is to call FUNCTION with arguments ARGS. If Emacs is not idle, 188 | defer the action until Emacs is idle. Otherwise, defer the action until at 189 | least SECS seconds later." 190 | `(run-with-idle-timer (+ (float-time (or (current-idle-time) 191 | (seconds-to-time (- ,secs)))) 192 | ,secs) 193 | nil 194 | ,function 195 | ,@args)) 196 | 197 | (defsubst exwm--terminal-p (&optional frame) 198 | "Return t when FRAME's terminal is EXWM's terminal. 199 | If FRAME is null, use selected frame." 200 | (declare (indent defun)) 201 | (eq exwm--terminal (frame-terminal frame))) 202 | 203 | (defun exwm--get-client-event-mask () 204 | "Return event mask set on all managed windows." 205 | (logior xcb:EventMask:StructureNotify 206 | xcb:EventMask:PropertyChange 207 | (if mouse-autoselect-window 208 | xcb:EventMask:EnterWindow 0))) 209 | 210 | (defun exwm--color->pixel (color) 211 | "Convert COLOR to PIXEL (index in TrueColor colormap)." 212 | (when (and color 213 | (eq (x-display-visual-class) 'true-color)) 214 | (let ((rgb (color-values color))) 215 | (logior (ash (ash (pop rgb) -8) 16) 216 | (ash (ash (pop rgb) -8) 8) 217 | (ash (pop rgb) -8))))) 218 | 219 | (defun exwm--get-visual-depth-colormap (conn id) 220 | "Get visual, depth and colormap from X window ID. 221 | Return a three element list with the respective results. 222 | 223 | If CONN is non-nil, use it instead of the value of the variable 224 | `exwm--connection'." 225 | (let (ret-visual ret-depth ret-colormap) 226 | (with-slots (visual colormap) 227 | (xcb:+request-unchecked+reply conn 228 | (make-instance 'xcb:GetWindowAttributes :window id)) 229 | (setq ret-visual visual) 230 | (setq ret-colormap colormap)) 231 | (with-slots (depth) 232 | (xcb:+request-unchecked+reply conn 233 | (make-instance 'xcb:GetGeometry :drawable id)) 234 | (setq ret-depth depth)) 235 | (list ret-visual ret-depth ret-colormap))) 236 | 237 | (defun exwm--mode-name () 238 | "Mode name string used in `exwm-mode' buffers." 239 | (let ((name "EXWM")) 240 | (if (cl-some (lambda (i) (frame-parameter i 'exwm-urgency)) 241 | exwm-workspace--list) 242 | (propertize name 'face 'font-lock-warning-face) 243 | name))) 244 | 245 | ;; Internal variables 246 | (defvar-local exwm--id nil) ;window ID 247 | (defvar-local exwm--configurations nil) ;initial configurations. 248 | (defvar-local exwm--frame nil) ;workspace frame 249 | (defvar-local exwm--floating-frame nil) ;floating frame 250 | (defvar-local exwm--mode-line-format nil) ;save mode-line-format 251 | (defvar-local exwm--floating-frame-position nil) ;set when hidden. 252 | (defvar-local exwm--fixed-size nil) ;fixed size 253 | (defvar-local exwm--selected-input-mode 'line-mode 254 | "Input mode as selected by the user. 255 | One of `line-mode' or `char-mode'.") 256 | (defvar-local exwm--input-mode 'line-mode 257 | "Actual input mode, i.e. whether mouse and keyboard are grabbed.") 258 | ;; Properties 259 | (defvar-local exwm--desktop nil "_NET_WM_DESKTOP.") 260 | (defvar-local exwm-window-type nil "_NET_WM_WINDOW_TYPE.") 261 | (defvar-local exwm--geometry nil) 262 | (defvar-local exwm-class-name nil "Class name in WM_CLASS.") 263 | (defvar-local exwm-instance-name nil "Instance name in WM_CLASS.") 264 | (defvar-local exwm-title nil "Window title (either _NET_WM_NAME or WM_NAME).") 265 | (defvar-local exwm--title-is-utf8 nil) 266 | (defvar-local exwm-transient-for nil "WM_TRANSIENT_FOR.") 267 | (defvar-local exwm--protocols nil) 268 | (defvar-local exwm-state xcb:icccm:WM_STATE:NormalState "WM_STATE.") 269 | (defvar-local exwm--ewmh-state nil "_NET_WM_STATE.") 270 | ;; _NET_WM_NORMAL_HINTS 271 | (defvar-local exwm--normal-hints-x nil) 272 | (defvar-local exwm--normal-hints-y nil) 273 | (defvar-local exwm--normal-hints-width nil) 274 | (defvar-local exwm--normal-hints-height nil) 275 | (defvar-local exwm--normal-hints-min-width nil) 276 | (defvar-local exwm--normal-hints-min-height nil) 277 | (defvar-local exwm--normal-hints-max-width nil) 278 | (defvar-local exwm--normal-hints-max-height nil) 279 | ;; (defvar-local exwm--normal-hints-win-gravity nil) 280 | ;; WM_HINTS 281 | (defvar-local exwm--hints-input nil) 282 | (defvar-local exwm--hints-urgency nil) 283 | ;; _MOTIF_WM_HINTS 284 | (defvar-local exwm--mwm-hints-decorations t) 285 | 286 | (defvar-keymap exwm-mode-map 287 | :doc "Keymap for `exwm-mode'." 288 | "C-c C-d C-l" #'xcb-debug:clear 289 | "C-c C-d C-m" #'xcb-debug:mark 290 | "C-c C-d C-t" #'exwm-debug-mode 291 | "C-c C-f" #'exwm-layout-set-fullscreen 292 | "C-c C-h" #'exwm-floating-hide 293 | "C-c C-k" #'exwm-input-release-keyboard 294 | "C-c C-m" #'exwm-workspace-move-window 295 | "C-c C-q" #'exwm-input-send-next-key 296 | "C-c C-t C-f" #'exwm-floating-toggle-floating 297 | "C-c C-t C-m" #'exwm-layout-toggle-mode-line) 298 | 299 | (defun exwm--kmacro-self-insert-command () 300 | "The EXWM kmacro equivalent of `self-insert-command'." 301 | (interactive) 302 | (cond 303 | ((or exwm-input-line-mode-passthrough 304 | (active-minibuffer-window) 305 | (memq last-input-event exwm-input--global-prefix-keys) 306 | (memq last-input-event exwm-input-prefix-keys) 307 | (lookup-key exwm-mode-map (vector last-input-event)) 308 | (gethash last-input-event exwm-input--simulation-keys)) 309 | (set-transient-map (make-composed-keymap (list exwm-mode-map global-map))) 310 | (push last-input-event unread-command-events)) 311 | (t 312 | (exwm-input--fake-key last-input-event)))) 313 | (put 'exwm--kmacro-self-insert-command 'completion-predicate #'ignore) 314 | 315 | (defvar-keymap exwm--kmacro-map 316 | :doc "Keymap used when executing keyboard macros." 317 | "" #'exwm--kmacro-self-insert-command) 318 | 319 | ;; This menu mainly acts as an reminder for users. Thus it should be as 320 | ;; detailed as possible, even some entries do not make much sense here. 321 | ;; Also, inactive entries should be disabled rather than hidden. 322 | (easy-menu-define exwm-mode-menu exwm-mode-map 323 | "Menu for `exwm-mode'." 324 | `("EXWM" 325 | ("General" 326 | ["Toggle floating" exwm-floating-toggle-floating] 327 | ["Toggle fullscreen mode" exwm-layout-toggle-fullscreen] 328 | ["Hide window" exwm-floating-hide exwm--floating-frame] 329 | ["Close window" (kill-buffer (current-buffer))]) 330 | ("Resizing" 331 | ["Toggle mode-line" exwm-layout-toggle-mode-line] 332 | ["Enlarge window vertically" exwm-layout-enlarge-window] 333 | ["Enlarge window horizontally" exwm-layout-enlarge-window-horizontally] 334 | ["Shrink window vertically" exwm-layout-shrink-window] 335 | ["Shrink window horizontally" exwm-layout-shrink-window-horizontally]) 336 | ("Keyboard" 337 | ["Toggle keyboard mode" exwm-input-toggle-keyboard] 338 | ["Send key" exwm-input-send-next-key (eq exwm--input-mode 'line-mode)] 339 | ;; This is merely a reference. 340 | ("Send simulation key" :filter 341 | ,(lambda (&rest _args) 342 | (let (result) 343 | (maphash 344 | (lambda (key value) 345 | (when (sequencep key) 346 | (setq result (append result 347 | `([,(format "Send '%s'" 348 | (key-description value)) 349 | ,(lambda () 350 | (interactive) 351 | (mapc #'exwm-input--fake-key value)) 352 | :keys ,(key-description key)]))))) 353 | exwm-input--simulation-keys) 354 | result))) 355 | 356 | ["Define global binding" exwm-input-set-key]) 357 | 358 | ("Workspace" 359 | ["Add workspace" exwm-workspace-add] 360 | ["Delete current workspace" exwm-workspace-delete] 361 | ["Move workspace to" exwm-workspace-move] 362 | ["Swap workspaces" exwm-workspace-swap] 363 | ["Move X window to" exwm-workspace-move-window] 364 | ["Move X window from" exwm-workspace-switch-to-buffer] 365 | ["Toggle minibuffer" exwm-workspace-toggle-minibuffer] 366 | ["Switch workspace" exwm-workspace-switch] 367 | ;; Place this entry at bottom to avoid selecting others by accident. 368 | ("Switch to" :active (cdr exwm-workspace--list) :filter 369 | ,(lambda (&rest _args) 370 | (mapcar (lambda (i) 371 | `[,(format "Workspace %d" i) 372 | ,(lambda () 373 | (interactive) 374 | (exwm-workspace-switch i)) 375 | (/= ,i exwm-workspace-current-index)]) 376 | (number-sequence 0 (1- (length exwm-workspace--list))))))))) 377 | 378 | (define-derived-mode exwm-mode nil "EXWM" 379 | "Major mode for managing X windows. 380 | 381 | \\{exwm-mode-map}" 382 | :interactive nil :abbrev-table nil :syntax-table nil 383 | ;; Change major-mode is not allowed 384 | (add-hook 'change-major-mode-hook #'kill-buffer nil t) 385 | ;; Kill buffer -> close window 386 | (add-hook 'kill-buffer-query-functions 387 | #'exwm-manage--kill-buffer-query-function nil t) 388 | ;; Redirect events when executing keyboard macros. 389 | (push `(executing-kbd-macro . ,exwm--kmacro-map) 390 | minor-mode-overriding-map-alist) 391 | (setq-local mode-name '(:eval (exwm--mode-name)) 392 | buffer-read-only t 393 | cursor-type nil 394 | left-margin-width nil 395 | right-margin-width nil 396 | left-fringe-width 0 397 | right-fringe-width 0 398 | vertical-scroll-bar nil 399 | eldoc-documentation-functions nil 400 | mode-line-position nil 401 | mode-line-modified nil 402 | mode-line-mule-info nil 403 | mode-line-remote nil)) 404 | 405 | (defmacro exwm--global-minor-mode-body (name &optional init exit) 406 | "Global minor mode body for mode with NAME. 407 | The INIT and EXIT functions are added to `exwm-init-hook' and 408 | `exwm-exit-hook' respectively. If an X connection exists, the mode is 409 | immediately enabled or disabled." 410 | (declare (indent 1) (debug t)) 411 | (let* ((mode (intern (format "exwm-%s-mode" name))) 412 | (init (or init (intern (format "exwm-%s--init" name)))) 413 | (exit (or exit (intern (format "exwm-%s--exit" name))))) 414 | `(progn 415 | (exwm--log) 416 | (cond 417 | (,mode 418 | (add-hook 'exwm-init-hook #',init) 419 | (add-hook 'exwm-exit-hook #',exit) 420 | (when exwm--connection (,init))) 421 | (t 422 | (remove-hook 'exwm-init-hook #',init) 423 | (remove-hook 'exwm-exit-hook #',exit) 424 | (when exwm--connection (,exit))))))) 425 | 426 | (provide 'exwm-core) 427 | ;;; exwm-core.el ends here 428 | -------------------------------------------------------------------------------- /exwm-layout.el: -------------------------------------------------------------------------------- 1 | ;;; exwm-layout.el --- Layout Module for EXWM -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2015-2025 Free Software Foundation, Inc. 4 | 5 | ;; Author: Chris Feng 6 | 7 | ;; This file is part of GNU Emacs. 8 | 9 | ;; GNU Emacs is free software: you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation, either version 3 of the License, or 12 | ;; (at your option) any later version. 13 | 14 | ;; GNU Emacs is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with GNU Emacs. If not, see . 21 | 22 | ;;; Commentary: 23 | 24 | ;; This module is responsible for keeping X client window properly displayed. 25 | 26 | ;;; Code: 27 | 28 | (require 'exwm-core) 29 | 30 | (defgroup exwm-layout nil 31 | "Layout." 32 | :group 'exwm) 33 | 34 | (defcustom exwm-layout-auto-iconify t 35 | "Non-nil to automatically iconify unused X windows when possible." 36 | :type 'boolean) 37 | 38 | (defcustom exwm-layout-show-all-buffers nil 39 | "Non-nil to allow switching to buffers on other workspaces." 40 | :type 'boolean) 41 | 42 | (defconst exwm-layout--floating-hidden-position -101 43 | "Where to place hidden floating X windows.") 44 | 45 | (defvar exwm-layout--other-buffer-exclude-buffers nil 46 | "List of buffers that should not be selected by `other-buffer'.") 47 | 48 | (defvar exwm-layout--other-buffer-exclude-exwm-mode-buffers nil 49 | "When non-nil, prevent EXWM buffers from being selected by `other-buffer'.") 50 | 51 | (defvar exwm-layout--timer nil "Timer used to track echo area changes.") 52 | 53 | (defvar exwm-workspace--current) 54 | (defvar exwm-workspace--frame-y-offset) 55 | (declare-function exwm-input--release-keyboard "exwm-input.el") 56 | (declare-function exwm-input--grab-keyboard "exwm-input.el") 57 | (declare-function exwm-input-grab-keyboard "exwm-input.el") 58 | (declare-function exwm-workspace--active-p "exwm-workspace.el" (frame)) 59 | (declare-function exwm-workspace--get-geometry "exwm-workspace.el" (frame)) 60 | (declare-function exwm-workspace--minibuffer-own-frame-p "exwm-workspace.el") 61 | (declare-function exwm-workspace--workspace-p "exwm-workspace.el" 62 | (workspace)) 63 | (declare-function exwm-workspace-move-window "exwm-workspace.el" 64 | (frame-or-index &optional id)) 65 | 66 | (defun exwm-layout--set-state (id state) 67 | "Set WM_STATE of X window ID to STATE." 68 | (exwm--log "id=#x%x" id) 69 | (xcb:+request exwm--connection 70 | (make-instance 'xcb:icccm:set-WM_STATE 71 | :window id :state state :icon xcb:Window:None)) 72 | (with-current-buffer (exwm--id->buffer id) 73 | (setq exwm-state state))) 74 | 75 | (defun exwm-layout--iconic-state-p (&optional id) 76 | "Check whether X window ID is in iconic state." 77 | (= xcb:icccm:WM_STATE:IconicState 78 | (if id 79 | (buffer-local-value 'exwm-state (exwm--id->buffer id)) 80 | exwm-state))) 81 | 82 | (defun exwm-layout--set-ewmh-state (id) 83 | "Set _NET_WM_STATE of X window ID to the value of variable `exwm--ewmh-state'." 84 | (with-current-buffer (exwm--id->buffer id) 85 | (xcb:+request exwm--connection 86 | (make-instance 'xcb:ewmh:set-_NET_WM_STATE 87 | :window exwm--id 88 | :data exwm--ewmh-state)))) 89 | 90 | (defun exwm-layout--fullscreen-p () 91 | "Check whether current `exwm-mode' buffer is in fullscreen state." 92 | (when (derived-mode-p 'exwm-mode) 93 | (memq xcb:Atom:_NET_WM_STATE_FULLSCREEN exwm--ewmh-state))) 94 | 95 | (defun exwm-layout--auto-iconify () 96 | "Helper function to iconify unused X windows. 97 | See variable `exwm-layout-auto-iconify'." 98 | (when (and exwm-layout-auto-iconify 99 | (not exwm-transient-for)) 100 | (let ((xwin exwm--id) 101 | (state exwm-state)) 102 | (dolist (pair exwm--id-buffer-alist) 103 | (with-current-buffer (cdr pair) 104 | (when (and exwm--floating-frame 105 | (eq exwm-transient-for xwin) 106 | (not (eq exwm-state state))) 107 | (if (eq state xcb:icccm:WM_STATE:NormalState) 108 | (exwm-layout--refresh-floating exwm--floating-frame) 109 | (exwm-layout--hide exwm--id)))))))) 110 | 111 | (defun exwm-layout--show (id &optional window) 112 | "Show window ID exactly fit in the Emacs window WINDOW." 113 | (exwm--log "Show #x%x in %s" id window) 114 | (let* ((edges (window-inside-absolute-pixel-edges window)) 115 | (x (pop edges)) 116 | (y (pop edges)) 117 | (width (- (pop edges) x)) 118 | (height (- (pop edges) y)) 119 | frame-x frame-y frame-width frame-height) 120 | (when (< emacs-major-version 31) 121 | (setq y (+ y (window-tab-line-height window)))) 122 | (with-current-buffer (exwm--id->buffer id) 123 | (when exwm--floating-frame 124 | (setq frame-width (frame-pixel-width exwm--floating-frame) 125 | frame-height (+ (frame-pixel-height exwm--floating-frame) 126 | ;; Use `frame-outer-height' in the future. 127 | exwm-workspace--frame-y-offset)) 128 | (when exwm--floating-frame-position 129 | (setq frame-x (elt exwm--floating-frame-position 0) 130 | frame-y (elt exwm--floating-frame-position 1) 131 | x (+ x frame-x (- exwm-layout--floating-hidden-position)) 132 | y (+ y frame-y (- exwm-layout--floating-hidden-position))) 133 | (setq exwm--floating-frame-position nil)) 134 | (exwm--set-geometry (frame-parameter exwm--floating-frame 135 | 'exwm-container) 136 | frame-x frame-y frame-width frame-height)) 137 | (when (exwm-layout--fullscreen-p) 138 | (with-slots ((x* x) 139 | (y* y) 140 | (width* width) 141 | (height* height)) 142 | (exwm-workspace--get-geometry exwm--frame) 143 | (setq x x* 144 | y y* 145 | width width* 146 | height height*))) 147 | (exwm--set-geometry id x y width height) 148 | (xcb:+request exwm--connection (make-instance 'xcb:MapWindow :window id)) 149 | (exwm-layout--set-state id xcb:icccm:WM_STATE:NormalState) 150 | (setq exwm--ewmh-state 151 | (delq xcb:Atom:_NET_WM_STATE_HIDDEN exwm--ewmh-state)) 152 | (exwm-layout--set-ewmh-state id) 153 | (exwm-layout--auto-iconify))) 154 | (xcb:flush exwm--connection)) 155 | 156 | (defun exwm-layout--hide (id) 157 | "Hide window ID." 158 | (with-current-buffer (exwm--id->buffer id) 159 | (unless (or (exwm-layout--iconic-state-p) 160 | (and exwm--floating-frame 161 | exwm--desktop 162 | (= 4294967295. exwm--desktop))) 163 | (exwm--log "Hide #x%x" id) 164 | (when exwm--floating-frame 165 | (let* ((container (frame-parameter exwm--floating-frame 166 | 'exwm-container)) 167 | (geometry (xcb:+request-unchecked+reply exwm--connection 168 | (make-instance 'xcb:GetGeometry 169 | :drawable container)))) 170 | (setq exwm--floating-frame-position 171 | (vector (slot-value geometry 'x) (slot-value geometry 'y))) 172 | (exwm--set-geometry container exwm-layout--floating-hidden-position 173 | exwm-layout--floating-hidden-position 174 | 1 175 | 1))) 176 | (xcb:+request exwm--connection 177 | (make-instance 'xcb:ChangeWindowAttributes 178 | :window id :value-mask xcb:CW:EventMask 179 | :event-mask xcb:EventMask:NoEvent)) 180 | (xcb:+request exwm--connection 181 | (make-instance 'xcb:UnmapWindow :window id)) 182 | (xcb:+request exwm--connection 183 | (make-instance 'xcb:ChangeWindowAttributes 184 | :window id :value-mask xcb:CW:EventMask 185 | :event-mask (exwm--get-client-event-mask))) 186 | (exwm-layout--set-state id xcb:icccm:WM_STATE:IconicState) 187 | (cl-pushnew xcb:Atom:_NET_WM_STATE_HIDDEN exwm--ewmh-state) 188 | (exwm-layout--set-ewmh-state id) 189 | (exwm-layout--auto-iconify) 190 | (xcb:flush exwm--connection)))) 191 | 192 | (cl-defun exwm-layout-set-fullscreen (&optional id) 193 | "Make window ID fullscreen." 194 | (interactive) 195 | (exwm--log "id=#x%x" (or id 0)) 196 | (unless (and (or id (derived-mode-p 'exwm-mode)) 197 | (not (exwm-layout--fullscreen-p))) 198 | (cl-return-from exwm-layout-set-fullscreen)) 199 | (with-current-buffer (if id (exwm--id->buffer id) (window-buffer)) 200 | ;; Expand the X window to fill the whole screen. 201 | (with-slots (x y width height) (exwm-workspace--get-geometry exwm--frame) 202 | (exwm--set-geometry exwm--id x y width height)) 203 | ;; Raise the X window. 204 | (xcb:+request exwm--connection 205 | (make-instance 'xcb:ConfigureWindow 206 | :window exwm--id 207 | :value-mask (logior xcb:ConfigWindow:BorderWidth 208 | xcb:ConfigWindow:StackMode) 209 | :border-width 0 210 | :stack-mode xcb:StackMode:Above)) 211 | (cl-pushnew xcb:Atom:_NET_WM_STATE_FULLSCREEN exwm--ewmh-state) 212 | (exwm-layout--set-ewmh-state exwm--id) 213 | (xcb:flush exwm--connection) 214 | (set-window-dedicated-p (get-buffer-window) t) 215 | (exwm-input--release-keyboard exwm--id))) 216 | 217 | (cl-defun exwm-layout-unset-fullscreen (&optional id) 218 | "Restore X window ID from fullscreen state." 219 | (interactive) 220 | (exwm--log "id=#x%x" (or id 0)) 221 | (unless (and (or id (derived-mode-p 'exwm-mode)) 222 | (exwm-layout--fullscreen-p)) 223 | (cl-return-from exwm-layout-unset-fullscreen)) 224 | (with-current-buffer (if id (exwm--id->buffer id) (window-buffer)) 225 | ;; `exwm-layout--show' relies on `exwm--ewmh-state' to decide whether to 226 | ;; fullscreen the window. 227 | (setq exwm--ewmh-state 228 | (delq xcb:Atom:_NET_WM_STATE_FULLSCREEN exwm--ewmh-state)) 229 | (exwm-layout--set-ewmh-state exwm--id) 230 | (if exwm--floating-frame 231 | (exwm-layout--show exwm--id (frame-root-window exwm--floating-frame)) 232 | (xcb:+request exwm--connection 233 | (make-instance 'xcb:ConfigureWindow 234 | :window exwm--id 235 | :value-mask (logior xcb:ConfigWindow:Sibling 236 | xcb:ConfigWindow:StackMode) 237 | :sibling exwm--guide-window 238 | :stack-mode xcb:StackMode:Above)) 239 | (let ((window (get-buffer-window nil t))) 240 | (when window 241 | (exwm-layout--show exwm--id window)))) 242 | (xcb:flush exwm--connection) 243 | (set-window-dedicated-p (get-buffer-window) nil) 244 | (when (eq 'line-mode exwm--selected-input-mode) 245 | (exwm-input--grab-keyboard exwm--id)))) 246 | 247 | (defun exwm-layout-toggle-fullscreen (&optional id) 248 | "Toggle fullscreen mode of X window ID. 249 | If ID is non-nil, default to ID of `window-buffer'." 250 | (interactive) 251 | (setq id (or id (exwm--buffer->id (current-buffer)) 252 | (user-error "Current buffer has no X window ID"))) 253 | (exwm--log "id=#x%x" id) 254 | (with-current-buffer (exwm--id->buffer id) 255 | (if (exwm-layout--fullscreen-p) 256 | (exwm-layout-unset-fullscreen id) 257 | (exwm-layout-set-fullscreen id)))) 258 | 259 | (defun exwm-layout--other-buffer-predicate (buffer) 260 | "Return non-nil when the BUFFER may be displayed in selected frame. 261 | 262 | Prevents EXWM-mode buffers already being displayed on some other window from 263 | being selected. 264 | 265 | Should be set as `buffer-predicate' frame parameter for all 266 | frames. Used by `other-buffer'. 267 | 268 | When variable `exwm-layout--other-buffer-exclude-exwm-mode-buffers' 269 | is t EXWM buffers are never selected by `other-buffer'. 270 | 271 | When variable `exwm-layout--other-buffer-exclude-buffers' is a 272 | list of buffers, EXWM buffers belonging to that list are never 273 | selected by `other-buffer'." 274 | (or (not (with-current-buffer buffer (derived-mode-p 'exwm-mode))) 275 | (and (not exwm-layout--other-buffer-exclude-exwm-mode-buffers) 276 | (not (memq buffer exwm-layout--other-buffer-exclude-buffers)) 277 | ;; Do not select if already shown in some window. 278 | (not (get-buffer-window buffer t))))) 279 | 280 | (defun exwm-layout--set-client-list-stacking () 281 | "Set _NET_CLIENT_LIST_STACKING." 282 | (exwm--log) 283 | (let (id clients-floating clients clients-iconic clients-other) 284 | (dolist (pair exwm--id-buffer-alist) 285 | (setq id (car pair)) 286 | (with-current-buffer (cdr pair) 287 | (if (eq exwm--frame exwm-workspace--current) 288 | (if exwm--floating-frame 289 | ;; A floating X window on the current workspace. 290 | (setq clients-floating (cons id clients-floating)) 291 | (if (get-buffer-window (cdr pair) exwm-workspace--current) 292 | ;; A normal tilling X window on the current workspace. 293 | (setq clients (cons id clients)) 294 | ;; An iconic tilling X window on the current workspace. 295 | (setq clients-iconic (cons id clients-iconic)))) 296 | ;; X window on other workspaces. 297 | (setq clients-other (cons id clients-other))))) 298 | (xcb:+request exwm--connection 299 | (make-instance 'xcb:ewmh:set-_NET_CLIENT_LIST_STACKING 300 | :window exwm--root 301 | :data (vconcat (append clients-other clients-iconic 302 | clients clients-floating)))))) 303 | 304 | (defun exwm-layout--refresh (&optional frame) 305 | "Refresh layout of FRAME. 306 | If FRAME is nil, refresh layout of selected frame." 307 | ;; `window-size-change-functions' sets this argument while 308 | ;; `window-configuration-change-hook' makes the frame selected. 309 | (unless frame 310 | (setq frame (selected-frame))) 311 | (exwm--log "frame=%s" frame) 312 | (if (not (exwm-workspace--workspace-p frame)) 313 | (if (frame-parameter frame 'exwm-outer-id) 314 | (exwm-layout--refresh-floating frame) 315 | (exwm-layout--refresh-other frame)) 316 | (exwm-layout--refresh-workspace frame))) 317 | 318 | (defun exwm-layout--refresh-floating (frame) 319 | "Refresh floating frame FRAME." 320 | (exwm--log "Refresh floating %s" frame) 321 | (let ((window (frame-first-window frame))) 322 | (with-current-buffer (window-buffer window) 323 | (when (and (derived-mode-p 'exwm-mode) 324 | ;; It may be a buffer waiting to be killed. 325 | (exwm--id->buffer exwm--id)) 326 | (exwm--log "Refresh floating window #x%x" exwm--id) 327 | (if (exwm-workspace--active-p exwm--frame) 328 | (exwm-layout--show exwm--id window) 329 | (exwm-layout--hide exwm--id)))))) 330 | 331 | (defun exwm-layout--refresh-other (frame) 332 | "Refresh client or nox frame FRAME." 333 | ;; Other frames (e.g. terminal/graphical frame of emacsclient) 334 | ;; We shall bury all `exwm-mode' buffers in this case 335 | (exwm--log "Refresh other %s" frame) 336 | (let ((windows (window-list frame 'nomini)) ;exclude minibuffer 337 | (exwm-layout--other-buffer-exclude-exwm-mode-buffers t)) 338 | (dolist (window windows) 339 | (with-current-buffer (window-buffer window) 340 | (when (derived-mode-p 'exwm-mode) 341 | (if (window-prev-buffers window) 342 | (switch-to-prev-buffer window) 343 | (switch-to-next-buffer window))))))) 344 | 345 | (defun exwm-layout--refresh-workspace (frame) 346 | "Refresh workspace frame FRAME." 347 | (exwm--log "Refresh workspace %s" frame) 348 | ;; Workspaces other than the active one can also be refreshed (RandR) 349 | (let (covered-buffers ;EXWM-buffers covered by a new X window. 350 | vacated-windows) ;Windows previously displaying EXWM-buffers. 351 | (dolist (pair exwm--id-buffer-alist) 352 | (with-current-buffer (cdr pair) 353 | (when (and (not exwm--floating-frame) ;exclude floating X windows 354 | (or exwm-layout-show-all-buffers 355 | ;; Exclude X windows on other workspaces 356 | (eq frame exwm--frame))) 357 | (let (;; List of windows in current frame displaying the `exwm-mode' 358 | ;; buffers. 359 | (windows (get-buffer-window-list (current-buffer) 'nomini 360 | frame))) 361 | (if (not windows) 362 | (when (eq frame exwm--frame) 363 | ;; Hide it if it was being shown in this workspace. 364 | (exwm-layout--hide exwm--id)) 365 | (let ((window (car windows))) 366 | (if (eq frame exwm--frame) 367 | ;; Show it if `frame' is active, hide otherwise. 368 | (if (exwm-workspace--active-p frame) 369 | (exwm-layout--show exwm--id window) 370 | (exwm-layout--hide exwm--id)) 371 | ;; It was last shown in other workspace; move it here. 372 | (exwm-workspace-move-window frame exwm--id)) 373 | ;; Vacate any other windows (in any workspace) showing this 374 | ;; `exwm-mode' buffer. 375 | (setq vacated-windows 376 | (append vacated-windows (remove 377 | window 378 | (get-buffer-window-list 379 | (current-buffer) 'nomini t)))) 380 | ;; Note any `exwm-mode' buffer is being covered by another 381 | ;; `exwm-mode' buffer. We want to avoid that `exwm-mode' 382 | ;; buffer to be reappear in any of the vacated windows. 383 | (let ((prev-buffer (car-safe 384 | (car-safe (window-prev-buffers window))))) 385 | (and 386 | prev-buffer 387 | (buffer-live-p prev-buffer) 388 | (with-current-buffer prev-buffer 389 | (derived-mode-p 'exwm-mode)) 390 | (push prev-buffer covered-buffers))))))))) 391 | ;; Set some sensible buffer to vacated windows. 392 | (let ((exwm-layout--other-buffer-exclude-buffers covered-buffers)) 393 | (dolist (window vacated-windows) 394 | (if (window-prev-buffers window) 395 | (switch-to-prev-buffer window) 396 | (switch-to-next-buffer window)))) 397 | ;; Make sure windows floating / on other workspaces are excluded 398 | (let ((exwm-layout--other-buffer-exclude-exwm-mode-buffers t)) 399 | (dolist (window (window-list frame 'nomini)) 400 | (with-current-buffer (window-buffer window) 401 | (when (and (derived-mode-p 'exwm-mode) 402 | (or exwm--floating-frame (not (eq frame exwm--frame)))) 403 | (if (window-prev-buffers window) 404 | (switch-to-prev-buffer window) 405 | (switch-to-next-buffer window)))))) 406 | (exwm-layout--set-client-list-stacking) 407 | (xcb:flush exwm--connection))) 408 | 409 | (defun exwm-layout--on-minibuffer-setup () 410 | "Refresh layout when minibuffer grows." 411 | (exwm--log) 412 | ;; Only when active minibuffer's frame is an EXWM frame. 413 | (let* ((mini-window (active-minibuffer-window)) 414 | (frame (window-frame mini-window))) 415 | (when (exwm-workspace--workspace-p frame) 416 | (exwm--defer 0 (lambda () 417 | (when (< 1 (window-height mini-window)) 418 | (exwm-layout--refresh frame))))))) 419 | 420 | (defun exwm-layout--on-echo-area-change (&optional dirty) 421 | "Run when message arrives or in `echo-area-clear-hook' to refresh layout. 422 | If DIRTY is non-nil, refresh layout immediately." 423 | (let ((frame (window-frame (active-minibuffer-window))) 424 | (msg (current-message))) 425 | ;; Check whether the frame where current window's minibuffer resides (not 426 | ;; current window's frame for floating windows!) must be adjusted. 427 | (when (and msg 428 | (exwm-workspace--workspace-p frame) 429 | (or (cl-position ?\n msg) 430 | (> (length msg) (frame-width frame)))) 431 | (exwm--log) 432 | (if dirty 433 | (exwm-layout--refresh exwm-workspace--current) 434 | (exwm--defer 0 #'exwm-layout--refresh exwm-workspace--current))))) 435 | 436 | (defun exwm-layout-enlarge-window (delta &optional horizontal) 437 | "Make the selected window DELTA pixels taller. 438 | 439 | If no argument is given, make the selected window one pixel taller. If the 440 | optional argument HORIZONTAL is non-nil, make selected window DELTA pixels 441 | wider. If DELTA is negative, shrink selected window by -DELTA pixels. 442 | 443 | Normal hints are checked and regarded if the selected window is displaying an 444 | `exwm-mode' buffer. However, this may violate the normal hints set on other X 445 | windows." 446 | (interactive "p") 447 | (exwm--log) 448 | (cond 449 | ((zerop delta)) ;no operation 450 | ((window-minibuffer-p)) ;avoid resize minibuffer-window 451 | ((not (and (derived-mode-p 'exwm-mode) exwm--floating-frame)) 452 | ;; Resize on tiling layout 453 | (unless (= 0 (window-resizable nil delta horizontal nil t)) ;not resizable 454 | (let ((window-resize-pixelwise t)) 455 | (window-resize nil delta horizontal nil t)))) 456 | ;; Resize on floating layout 457 | (exwm--fixed-size) ;fixed size 458 | (horizontal 459 | (let* ((width (frame-pixel-width)) 460 | (edges (window-inside-pixel-edges)) 461 | (inner-width (- (elt edges 2) (elt edges 0))) 462 | (margin (- width inner-width))) 463 | (if (> delta 0) 464 | (if (not exwm--normal-hints-max-width) 465 | (cl-incf width delta) 466 | (if (>= inner-width exwm--normal-hints-max-width) 467 | (setq width nil) 468 | (setq width (min (+ exwm--normal-hints-max-width margin) 469 | (+ width delta))))) 470 | (if (not exwm--normal-hints-min-width) 471 | (cl-incf width delta) 472 | (if (<= inner-width exwm--normal-hints-min-width) 473 | (setq width nil) 474 | (setq width (max (+ exwm--normal-hints-min-width margin) 475 | (+ width delta)))))) 476 | (when (and width (> width 0)) 477 | (setf (slot-value exwm--geometry 'width) width) 478 | (xcb:+request exwm--connection 479 | (make-instance 'xcb:ConfigureWindow 480 | :window (frame-parameter exwm--floating-frame 481 | 'exwm-outer-id) 482 | :value-mask xcb:ConfigWindow:Width 483 | :width width)) 484 | (xcb:+request exwm--connection 485 | (make-instance 'xcb:ConfigureWindow 486 | :window (frame-parameter exwm--floating-frame 487 | 'exwm-container) 488 | :value-mask xcb:ConfigWindow:Width 489 | :width width)) 490 | (xcb:flush exwm--connection)))) 491 | (t 492 | (let* ((height (+ (frame-pixel-height) exwm-workspace--frame-y-offset)) 493 | (edges (window-inside-pixel-edges)) 494 | (inner-height (- (elt edges 3) (elt edges 1))) 495 | (margin (- height inner-height))) 496 | (if (> delta 0) 497 | (if (not exwm--normal-hints-max-height) 498 | (cl-incf height delta) 499 | (if (>= inner-height exwm--normal-hints-max-height) 500 | (setq height nil) 501 | (setq height (min (+ exwm--normal-hints-max-height margin) 502 | (+ height delta))))) 503 | (if (not exwm--normal-hints-min-height) 504 | (cl-incf height delta) 505 | (if (<= inner-height exwm--normal-hints-min-height) 506 | (setq height nil) 507 | (setq height (max (+ exwm--normal-hints-min-height margin) 508 | (+ height delta)))))) 509 | (when (and height (> height 0)) 510 | (setf (slot-value exwm--geometry 'height) height) 511 | (xcb:+request exwm--connection 512 | (make-instance 'xcb:ConfigureWindow 513 | :window (frame-parameter exwm--floating-frame 514 | 'exwm-outer-id) 515 | :value-mask xcb:ConfigWindow:Height 516 | :height height)) 517 | (xcb:+request exwm--connection 518 | (make-instance 'xcb:ConfigureWindow 519 | :window (frame-parameter exwm--floating-frame 520 | 'exwm-container) 521 | :value-mask xcb:ConfigWindow:Height 522 | :height height)) 523 | (xcb:flush exwm--connection)))))) 524 | 525 | (defun exwm-layout-enlarge-window-horizontally (delta) 526 | "Make the selected window DELTA pixels wider. 527 | 528 | See also `exwm-layout-enlarge-window'." 529 | (interactive "p") 530 | (exwm--log "%s" delta) 531 | (exwm-layout-enlarge-window delta t)) 532 | 533 | (defun exwm-layout-shrink-window (delta) 534 | "Make the selected window DELTA pixels lower. 535 | 536 | See also `exwm-layout-enlarge-window'." 537 | (interactive "p") 538 | (exwm--log "%s" delta) 539 | (exwm-layout-enlarge-window (- delta))) 540 | 541 | (defun exwm-layout-shrink-window-horizontally (delta) 542 | "Make the selected window DELTA pixels narrower. 543 | 544 | See also `exwm-layout-enlarge-window'." 545 | (interactive "p") 546 | (exwm--log "%s" delta) 547 | (exwm-layout-enlarge-window (- delta) t)) 548 | 549 | (defun exwm-layout-hide-mode-line () 550 | "Hide mode-line." 551 | (interactive) 552 | (exwm--log) 553 | (when (and (derived-mode-p 'exwm-mode) mode-line-format) 554 | (let (mode-line-height) 555 | (when exwm--floating-frame 556 | (setq mode-line-height (window-mode-line-height 557 | (frame-root-window exwm--floating-frame)))) 558 | (setq exwm--mode-line-format mode-line-format 559 | mode-line-format nil) 560 | (if (not exwm--floating-frame) 561 | (exwm-layout--show exwm--id) 562 | (set-frame-height exwm--floating-frame 563 | (- (frame-pixel-height exwm--floating-frame) 564 | mode-line-height) 565 | nil t))))) 566 | 567 | (defun exwm-layout-show-mode-line () 568 | "Show mode-line." 569 | (interactive) 570 | (exwm--log) 571 | (when (and (derived-mode-p 'exwm-mode) (not mode-line-format)) 572 | (setq mode-line-format exwm--mode-line-format 573 | exwm--mode-line-format nil) 574 | (if (not exwm--floating-frame) 575 | (exwm-layout--show exwm--id) 576 | (set-frame-height exwm--floating-frame 577 | (+ (frame-pixel-height exwm--floating-frame) 578 | (window-mode-line-height (frame-root-window 579 | exwm--floating-frame))) 580 | nil t) 581 | (call-interactively #'exwm-input-grab-keyboard)) 582 | (force-mode-line-update))) 583 | 584 | (defun exwm-layout-toggle-mode-line () 585 | "Toggle the display of mode-line." 586 | (interactive) 587 | (exwm--log) 588 | (when (derived-mode-p 'exwm-mode) 589 | (if mode-line-format 590 | (exwm-layout-hide-mode-line) 591 | (exwm-layout-show-mode-line)))) 592 | 593 | (defun exwm-layout--init () 594 | "Initialize layout module." 595 | ;; Auto refresh layout 596 | (exwm--log) 597 | (add-hook 'window-configuration-change-hook #'exwm-layout--refresh) 598 | (add-hook 'window-size-change-functions #'exwm-layout--refresh) 599 | (unless (exwm-workspace--minibuffer-own-frame-p) 600 | ;; Refresh when minibuffer grows 601 | (add-hook 'minibuffer-setup-hook #'exwm-layout--on-minibuffer-setup t) 602 | (setq exwm-layout--timer 603 | (run-with-idle-timer 0 t #'exwm-layout--on-echo-area-change t)) 604 | (add-hook 'echo-area-clear-hook #'exwm-layout--on-echo-area-change))) 605 | 606 | (defun exwm-layout--exit () 607 | "Exit the layout module." 608 | (exwm--log) 609 | (remove-hook 'window-configuration-change-hook #'exwm-layout--refresh) 610 | (remove-hook 'window-size-change-functions #'exwm-layout--refresh) 611 | (remove-hook 'minibuffer-setup-hook #'exwm-layout--on-minibuffer-setup) 612 | (when exwm-layout--timer 613 | (cancel-timer exwm-layout--timer) 614 | (setq exwm-layout--timer nil)) 615 | (remove-hook 'echo-area-clear-hook #'exwm-layout--on-echo-area-change)) 616 | 617 | (provide 'exwm-layout) 618 | ;;; exwm-layout.el ends here 619 | -------------------------------------------------------------------------------- /exwm-randr.el: -------------------------------------------------------------------------------- 1 | ;;; exwm-randr.el --- RandR Module for EXWM -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2015-2025 Free Software Foundation, Inc. 4 | 5 | ;; Author: Chris Feng 6 | 7 | ;; This file is part of GNU Emacs. 8 | 9 | ;; GNU Emacs is free software: you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation, either version 3 of the License, or 12 | ;; (at your option) any later version. 13 | 14 | ;; GNU Emacs is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with GNU Emacs. If not, see . 21 | 22 | ;;; Commentary: 23 | 24 | ;; This module adds RandR support for EXWM. Currently it requires external 25 | ;; tools such as xrandr(1) to properly configure RandR first. This 26 | ;; dependency may be removed in the future, but more work is needed before 27 | ;; that. 28 | 29 | ;; To use this module, load, enable it and configure 30 | ;; `exwm-randr-workspace-monitor-plist' and `exwm-randr-screen-change-hook' 31 | ;; as follows: 32 | ;; 33 | ;; (setq exwm-randr-workspace-monitor-plist '(0 "VGA1")) 34 | ;; (add-hook 'exwm-randr-screen-change-hook 35 | ;; (lambda () 36 | ;; (start-process-shell-command 37 | ;; "xrandr" nil "xrandr --output VGA1 --left-of LVDS1 --auto"))) 38 | ;; (exwm-randr-mode 1) 39 | ;; 40 | ;; With above lines, workspace 0 should be assigned to the output named "VGA1", 41 | ;; staying at the left of other workspaces on the output "LVDS1". Please refer 42 | ;; to xrandr(1) for the configuration of RandR. 43 | 44 | ;; References: 45 | ;; + RandR (http://www.x.org/archive/X11R7.7/doc/randrproto/randrproto.txt) 46 | 47 | ;;; Code: 48 | 49 | (require 'xcb-randr) 50 | 51 | (require 'exwm-core) 52 | (require 'exwm-workspace) 53 | 54 | (declare-function x-get-atom-name "C source code" (VALUE &optional FRAME)) 55 | 56 | (defgroup exwm-randr nil 57 | "RandR." 58 | :group 'exwm) 59 | 60 | (defvar exwm-randr--connection nil "The X connection.") 61 | 62 | (defcustom exwm-randr-refresh-hook nil 63 | "Normal hook run when the RandR module just refreshed." 64 | :type 'hook) 65 | 66 | (defcustom exwm-randr-screen-change-hook nil 67 | "Normal hook run when screen changes." 68 | :type 'hook) 69 | 70 | (defcustom exwm-randr-workspace-monitor-plist nil 71 | "Plist mapping workspaces to monitors. 72 | 73 | In RandR 1.5 a monitor is a rectangle region decoupled from the physical 74 | size of screens, and can be identified with `xrandr --listmonitors' (name of 75 | the primary monitor is prefixed with an `*'). When no monitor is created it 76 | automatically fallback to RandR 1.2 output which represents the physical 77 | screen size. RandR 1.5 monitors can be created with `xrandr --setmonitor'. 78 | For example, to split an output (`LVDS-1') of size 1280x800 into two 79 | side-by-side monitors one could invoke (the digits after `/' are size in mm) 80 | 81 | xrandr --setmonitor *LVDS-1-L 640/135x800/163+0+0 LVDS-1 82 | xrandr --setmonitor LVDS-1-R 640/135x800/163+640+0 none 83 | 84 | If a monitor is not active, the workspaces mapped to it are displayed on the 85 | primary monitor until it becomes active (if ever). Unspecified workspaces 86 | are all mapped to the primary monitor. For example, with the following 87 | setting workspace other than 1 and 3 would always be displayed on the 88 | primary monitor where workspace 1 and 3 would be displayed on their 89 | corresponding monitors whenever the monitors are active. 90 | 91 | Changes to this variable only take immediate affect when set before 92 | `exwm-randr-mode' is enabled, via `setopt', or when customized (see the 93 | Info node `Customization'). Otherwise, the `exwm-randr-refresh' must be 94 | called explicitly to assign the correct workspaces to the correct monitors. 95 | 96 | \\='(1 \"HDMI-1\" 3 \"DP-1\")" 97 | :type '(plist :key-type integer :value-type string) 98 | :initialize 'custom-initialize-changed 99 | :set (lambda (symbol value) 100 | (set-default-toplevel-value symbol value) 101 | (when exwm-randr--connection 102 | (exwm-randr-refresh)))) 103 | 104 | (defvar exwm-randr--connection nil "The X connection.") 105 | 106 | (defvar exwm-randr--last-timestamp 0 "Used for debouncing events.") 107 | 108 | (defvar exwm-randr--prev-screen-change-timestamp 0 109 | "The most recent ScreenChangeNotify config change timestamp.") 110 | 111 | (defvar exwm-randr--compatibility-mode nil 112 | "Non-nil when the server does not support RandR 1.5 protocol.") 113 | 114 | ;;;###autoload 115 | (define-minor-mode exwm-randr-mode 116 | "Toggle EXWM randr support." 117 | :global t 118 | :group 'exwm-randr 119 | (exwm--global-minor-mode-body randr)) 120 | 121 | (defsubst exwm-randr--assert-connected () 122 | "Assert that `exwm-randr-mode' is enabled and activated." 123 | (cond 124 | ((not exwm-randr-mode) (user-error "EXWM RandR mode not enabled")) 125 | ((not exwm-randr--connection) (user-error "EXWM RandR not connected, is EXWM running?")))) 126 | 127 | (defun exwm-randr--get-monitors () 128 | "Get RandR 1.5 monitors." 129 | (exwm--log) 130 | (let (monitor-name geometry monitor-geometry-alist primary-monitor) 131 | (with-slots (timestamp monitors) 132 | (xcb:+request-unchecked+reply exwm-randr--connection 133 | (make-instance 'xcb:randr:GetMonitors 134 | :window exwm--root 135 | :get-active 1)) 136 | (when (> timestamp exwm-randr--last-timestamp) 137 | (setq exwm-randr--last-timestamp timestamp)) 138 | (dolist (monitor monitors) 139 | (with-slots (name primary x y width height) monitor 140 | (setq monitor-name (x-get-atom-name name) 141 | geometry (make-instance 'xcb:RECTANGLE 142 | :x x 143 | :y y 144 | :width width 145 | :height height) 146 | monitor-geometry-alist (cons (cons monitor-name geometry) 147 | monitor-geometry-alist)) 148 | (exwm--log "%s: %sx%s+%s+%s" monitor-name x y width height) 149 | ;; Save primary monitor when available (fallback to the first one). 150 | (when (or (/= 0 primary) 151 | (not primary-monitor)) 152 | (setq primary-monitor monitor-name))))) 153 | (exwm--log "Primary monitor: %s" primary-monitor) 154 | (list primary-monitor monitor-geometry-alist 155 | (exwm-randr--get-monitor-alias primary-monitor 156 | monitor-geometry-alist)))) 157 | 158 | (defun exwm-randr--get-outputs () 159 | "Get RandR 1.2 outputs. 160 | 161 | Only used when RandR 1.5 is not supported by the server." 162 | (exwm--log) 163 | (let (output-name geometry output-geometry-alist primary-output) 164 | (with-slots (config-timestamp outputs) 165 | (xcb:+request-unchecked+reply exwm-randr--connection 166 | (make-instance 'xcb:randr:GetScreenResourcesCurrent 167 | :window exwm--root)) 168 | (when (> config-timestamp exwm-randr--last-timestamp) 169 | (setq exwm-randr--last-timestamp config-timestamp)) 170 | (dolist (output outputs) 171 | (with-slots (crtc connection name) 172 | (xcb:+request-unchecked+reply exwm-randr--connection 173 | (make-instance 'xcb:randr:GetOutputInfo 174 | :output output 175 | :config-timestamp config-timestamp)) 176 | (when (and (= connection xcb:randr:Connection:Connected) 177 | (/= crtc 0)) 178 | (with-slots (x y width height) 179 | (xcb:+request-unchecked+reply exwm-randr--connection 180 | (make-instance 'xcb:randr:GetCrtcInfo 181 | :crtc crtc 182 | :config-timestamp config-timestamp)) 183 | (setq output-name (decode-coding-string 184 | (apply #'unibyte-string name) 'utf-8) 185 | geometry (make-instance 'xcb:RECTANGLE 186 | :x x 187 | :y y 188 | :width width 189 | :height height) 190 | output-geometry-alist (cons (cons output-name geometry) 191 | output-geometry-alist)) 192 | (exwm--log "%s: %sx%s+%s+%s" output-name x y width height) 193 | ;; The primary output is the first one. 194 | (unless primary-output 195 | (setq primary-output output-name))))))) 196 | (exwm--log "Primary output: %s" primary-output) 197 | (list primary-output output-geometry-alist 198 | (exwm-randr--get-monitor-alias primary-output 199 | output-geometry-alist)))) 200 | 201 | (defun exwm-randr--get-monitor-alias (primary-monitor monitor-geometry-alist) 202 | "Generate monitor aliases using PRIMARY-MONITOR MONITOR-GEOMETRY-ALIST. 203 | 204 | In a mirroring setup some monitors overlap and should be treated as one." 205 | (let (monitor-position-alist monitor-alias-alist monitor-name geometry) 206 | (setq monitor-position-alist (with-slots (x y) 207 | (cdr (assoc primary-monitor 208 | monitor-geometry-alist)) 209 | (list (cons primary-monitor (vector x y))))) 210 | (setq monitor-alias-alist (list (cons primary-monitor primary-monitor))) 211 | (dolist (pair monitor-geometry-alist) 212 | (setq monitor-name (car pair) 213 | geometry (cdr pair)) 214 | (unless (assoc monitor-name monitor-alias-alist) 215 | (let* ((position (vector (slot-value geometry 'x) 216 | (slot-value geometry 'y))) 217 | (alias (car (rassoc position monitor-position-alist)))) 218 | (if alias 219 | (setq monitor-alias-alist (cons (cons monitor-name alias) 220 | monitor-alias-alist)) 221 | (setq monitor-position-alist (cons (cons monitor-name position) 222 | monitor-position-alist) 223 | monitor-alias-alist (cons (cons monitor-name monitor-name) 224 | monitor-alias-alist)))))) 225 | monitor-alias-alist)) 226 | 227 | (defun exwm-randr-refresh () 228 | "Refresh workspaces according to the updated RandR info." 229 | (interactive) 230 | (exwm--log) 231 | (exwm-randr--assert-connected) 232 | (let* ((result (if exwm-randr--compatibility-mode 233 | (exwm-randr--get-outputs) 234 | (exwm-randr--get-monitors))) 235 | (primary-monitor (elt result 0)) 236 | (monitor-geometry-alist (elt result 1)) 237 | (monitor-alias-alist (elt result 2)) 238 | container-monitor-alist container-frame-alist) 239 | (when (and primary-monitor monitor-geometry-alist) 240 | (when exwm-workspace--fullscreen-frame-count 241 | ;; Not all workspaces are fullscreen; reset this counter. 242 | (setq exwm-workspace--fullscreen-frame-count 0)) 243 | (dotimes (i (exwm-workspace--count)) 244 | (let* ((monitor (plist-get exwm-randr-workspace-monitor-plist i)) 245 | (geometry (cdr (assoc monitor monitor-geometry-alist))) 246 | (frame (elt exwm-workspace--list i)) 247 | (container (frame-parameter frame 'exwm-container))) 248 | (if geometry 249 | ;; Unify monitor names in case it's a mirroring setup. 250 | (setq monitor (cdr (assoc monitor monitor-alias-alist))) 251 | ;; Missing monitors fallback to the primary one. 252 | (setq monitor primary-monitor 253 | geometry (cdr (assoc primary-monitor 254 | monitor-geometry-alist)))) 255 | (setq container-monitor-alist (nconc 256 | `((,container . ,(intern monitor))) 257 | container-monitor-alist) 258 | container-frame-alist (nconc `((,container . ,frame)) 259 | container-frame-alist)) 260 | (set-frame-parameter frame 'exwm-randr-monitor monitor) 261 | (set-frame-parameter frame 'exwm-geometry geometry))) 262 | ;; Update workareas. 263 | (exwm-workspace--update-workareas) 264 | ;; Resize workspace. 265 | (dolist (f exwm-workspace--list) 266 | (exwm-workspace--set-fullscreen f)) 267 | (xcb:flush exwm-randr--connection) 268 | ;; Raise the minibuffer if it's active. 269 | (when (and (active-minibuffer-window) 270 | (exwm-workspace--minibuffer-own-frame-p)) 271 | (exwm-workspace--show-minibuffer)) 272 | ;; Set _NET_DESKTOP_GEOMETRY. 273 | (exwm-workspace--set-desktop-geometry) 274 | ;; Update active/inactive workspaces. 275 | (dolist (w exwm-workspace--list) 276 | (exwm-workspace--set-active w nil)) 277 | ;; Mark the workspace on the top of each monitor as active. 278 | (dolist (xwin 279 | (reverse 280 | (slot-value (xcb:+request-unchecked+reply exwm-randr--connection 281 | (make-instance 'xcb:QueryTree 282 | :window exwm--root)) 283 | 'children))) 284 | (let ((monitor (cdr (assq xwin container-monitor-alist)))) 285 | (when monitor 286 | (setq container-monitor-alist 287 | (rassq-delete-all monitor container-monitor-alist)) 288 | (exwm-workspace--set-active (cdr (assq xwin container-frame-alist)) 289 | t)))) 290 | (xcb:flush exwm-randr--connection) 291 | (run-hooks 'exwm-randr-refresh-hook)))) 292 | 293 | (defun exwm-randr--on-ScreenChangeNotify (data _synthetic) 294 | "Handle `ScreenChangeNotify' event with DATA. 295 | 296 | Run `exwm-randr-screen-change-hook' (usually user scripts to configure RandR)." 297 | (exwm--log) 298 | (let ((evt (make-instance 'xcb:randr:ScreenChangeNotify))) 299 | (xcb:unmarshal evt data) 300 | (let ((ts (slot-value evt 'config-timestamp))) 301 | (unless (equal ts exwm-randr--prev-screen-change-timestamp) 302 | (setq exwm-randr--prev-screen-change-timestamp ts) 303 | (run-hooks 'exwm-randr-screen-change-hook))))) 304 | 305 | (defun exwm-randr--on-Notify (data _synthetic) 306 | "Handle `CrtcChangeNotify' and `OutputChangeNotify' events with DATA. 307 | 308 | Refresh when any CRTC/output changes." 309 | (exwm--log) 310 | (let ((evt (make-instance 'xcb:randr:Notify)) 311 | notify) 312 | (xcb:unmarshal evt data) 313 | (with-slots (subCode u) evt 314 | (cond ((= subCode xcb:randr:Notify:CrtcChange) 315 | (setq notify (slot-value u 'cc))) 316 | ((= subCode xcb:randr:Notify:OutputChange) 317 | (setq notify (slot-value u 'oc)))) 318 | (when notify 319 | (with-slots (timestamp) notify 320 | (when (> timestamp exwm-randr--last-timestamp) 321 | (exwm-randr-refresh) 322 | (setq exwm-randr--last-timestamp timestamp))))))) 323 | 324 | (defun exwm-randr--on-ConfigureNotify (data _synthetic) 325 | "Handle `ConfigureNotify' event with DATA. 326 | 327 | Refresh when any RandR 1.5 monitor changes." 328 | (exwm--log) 329 | (let ((evt (make-instance 'xcb:ConfigureNotify))) 330 | (xcb:unmarshal evt data) 331 | (with-slots (window) evt 332 | (when (eq window exwm--root) 333 | (exwm-randr-refresh))))) 334 | 335 | (cl-defun exwm-randr--init () 336 | "Initialize RandR extension and EXWM RandR module." 337 | (exwm--log) 338 | (when exwm-randr--connection 339 | (cl-return-from exwm-randr--init)) 340 | (setq exwm-randr--connection (xcb:connect)) 341 | (set-process-query-on-exit-flag (slot-value exwm-randr--connection 'process) nil) 342 | (when (= 0 (slot-value (xcb:get-extension-data exwm-randr--connection 'xcb:randr) 343 | 'present)) 344 | (xcb:disconnect exwm-randr--connection) 345 | (setq exwm-randr--connection nil) 346 | (error "[EXWM] RandR extension is not supported by the server")) 347 | (with-slots (major-version minor-version) 348 | (xcb:+request-unchecked+reply exwm-randr--connection 349 | (make-instance 'xcb:randr:QueryVersion 350 | :major-version 1 :minor-version 5)) 351 | (cond ((and (= major-version 1) (= minor-version 5)) 352 | (setq exwm-randr--compatibility-mode nil)) 353 | ((and (= major-version 1) (>= minor-version 2)) 354 | (setq exwm-randr--compatibility-mode t)) 355 | (t 356 | (xcb:disconnect exwm-randr--connection) 357 | (setq exwm-randr--connection nil) 358 | (error "[EXWM] The server only support RandR version up to %d.%d" 359 | major-version minor-version))) 360 | ;; External monitor(s) may already be connected. 361 | (run-hooks 'exwm-randr-screen-change-hook) 362 | (exwm-randr-refresh) 363 | ;; Listen for `ScreenChangeNotify' to notify external tools to 364 | ;; configure RandR and `CrtcChangeNotify/OutputChangeNotify' to 365 | ;; refresh the workspace layout. 366 | (xcb:+event exwm-randr--connection 'xcb:randr:ScreenChangeNotify 367 | #'exwm-randr--on-ScreenChangeNotify) 368 | (xcb:+event exwm-randr--connection 'xcb:randr:Notify 369 | #'exwm-randr--on-Notify) 370 | (xcb:+event exwm-randr--connection 'xcb:ConfigureNotify 371 | #'exwm-randr--on-ConfigureNotify) 372 | (xcb:+request exwm-randr--connection 373 | (make-instance 'xcb:randr:SelectInput 374 | :window exwm--root 375 | :enable (logior 376 | xcb:randr:NotifyMask:ScreenChange 377 | xcb:randr:NotifyMask:CrtcChange 378 | xcb:randr:NotifyMask:OutputChange))) 379 | (xcb:flush exwm-randr--connection) 380 | (add-hook 'exwm-workspace-list-change-hook #'exwm-randr-refresh)) 381 | ;; Prevent frame parameters introduced by this module from being 382 | ;; saved/restored. 383 | (dolist (i '(exwm-randr-monitor)) 384 | (unless (assq i frameset-filter-alist) 385 | (push (cons i :never) frameset-filter-alist)))) 386 | 387 | (defun exwm-randr--exit () 388 | "Exit the RandR module." 389 | (exwm--log) 390 | (remove-hook 'exwm-workspace-list-change-hook #'exwm-randr-refresh) 391 | (when exwm-randr--connection 392 | (xcb:disconnect exwm-randr--connection) 393 | (setq exwm-randr--connection nil))) 394 | 395 | (provide 'exwm-randr) 396 | ;;; exwm-randr.el ends here 397 | -------------------------------------------------------------------------------- /exwm-systemtray.el: -------------------------------------------------------------------------------- 1 | ;;; exwm-systemtray.el --- System Tray Module for -*- lexical-binding: t -*- 2 | ;;; EXWM 3 | 4 | ;; Copyright (C) 2016-2025 Free Software Foundation, Inc. 5 | 6 | ;; Author: Chris Feng 7 | 8 | ;; This file is part of GNU Emacs. 9 | 10 | ;; GNU Emacs is free software: you can redistribute it and/or modify 11 | ;; it under the terms of the GNU General Public License as published by 12 | ;; the Free Software Foundation, either version 3 of the License, or 13 | ;; (at your option) any later version. 14 | 15 | ;; GNU Emacs is distributed in the hope that it will be useful, 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ;; GNU General Public License for more details. 19 | 20 | ;; You should have received a copy of the GNU General Public License 21 | ;; along with GNU Emacs. If not, see . 22 | 23 | ;;; Commentary: 24 | 25 | ;; This module adds system tray support for EXWM. 26 | 27 | ;; To use this module, enable it as follows: 28 | ;; 29 | ;; (exwm-systemtray-mode 1) 30 | 31 | ;;; Code: 32 | 33 | (require 'xcb-ewmh) 34 | (require 'xcb-icccm) 35 | (require 'xcb-xembed) 36 | (require 'xcb-systemtray) 37 | 38 | (require 'exwm-core) 39 | (require 'exwm-workspace) 40 | 41 | (declare-function exwm-workspace--workarea "exwm-workspace.el" (frame)) 42 | 43 | (defclass exwm-systemtray--icon () 44 | ((width :initarg :width) 45 | (height :initarg :height) 46 | (visible :initarg :visible)) 47 | :documentation "Attributes of a system tray icon.") 48 | 49 | (defgroup exwm-systemtray nil 50 | "System tray." 51 | :group 'exwm) 52 | 53 | ;;;###autoload 54 | (define-minor-mode exwm-systemtray-mode 55 | "Toggle EXWM systemtray support." 56 | :global t 57 | :group 'exwm-systemtray 58 | (exwm--global-minor-mode-body systemtray)) 59 | 60 | (defcustom exwm-systemtray-height nil 61 | "System tray height. 62 | 63 | You shall use the default value if using auto-hide minibuffer." 64 | :type 'integer) 65 | 66 | (defcustom exwm-systemtray-icon-gap 2 67 | "Gap between icons." 68 | :type 'integer) 69 | 70 | (defcustom exwm-systemtray-position 'bottom 71 | "Position of the systemtray at the top or bottom." 72 | :type '(choice (const top) (const bottom))) 73 | 74 | (defvar exwm-systemtray--connection nil "The X connection.") 75 | 76 | (defvar exwm-systemtray--embedder-window nil "The embedder window.") 77 | (defvar exwm-systemtray--embedder-window-depth nil 78 | "The embedder window's depth.") 79 | 80 | (defcustom exwm-systemtray-background-color 'default 81 | "Background color of systemtray. 82 | This should be a color, the symbol `transparent' for transparent 83 | background, or a face symbol like `default' or `tab-bar'. 84 | 85 | Transparent background is not yet supported when Emacs uses 32-bit depth 86 | visual, as reported by `x-display-planes'. The X resource \"Emacs.visualClass: 87 | TrueColor-24\" can be used to force Emacs to use 24-bit depth." 88 | :type '(choice (const :tag "Transparent" transparent) 89 | (const :tag "Frame background" default) 90 | (const :tag "Tab-bar background" tab-bar) 91 | (color :tag "Color")) 92 | :initialize #'custom-initialize-default 93 | :set (lambda (symbol value) 94 | (when (eq value 'workspace-background) 95 | (display-warning 'exwm-systemtray "Use `default' instead\ 96 | of `workspace-background' for `exwm-systemtray-background-color'.") 97 | (setq value 'default)) 98 | (when (and (eq value 'transparent) 99 | (not (exwm-systemtray--transparency-supported-p))) 100 | (display-warning 'exwm-systemtray 101 | "Transparent background is not supported yet when \ 102 | using 32-bit depth. Using `default' instead.") 103 | (setq value 'default)) 104 | (set-default symbol value) 105 | (when (and exwm-systemtray-mode 106 | exwm-systemtray--connection 107 | exwm-systemtray--embedder-window) 108 | ;; Change the background color for embedder. 109 | (exwm-systemtray--set-background-color) 110 | ;; Unmap & map to take effect immediately. 111 | (xcb:+request exwm-systemtray--connection 112 | (make-instance 'xcb:UnmapWindow 113 | :window exwm-systemtray--embedder-window)) 114 | (xcb:+request exwm-systemtray--connection 115 | (make-instance 'xcb:MapWindow 116 | :window exwm-systemtray--embedder-window)) 117 | (xcb:flush exwm-systemtray--connection)))) 118 | 119 | ;; GTK icons require at least 16 pixels to show normally. 120 | (defconst exwm-systemtray--icon-min-size 16 "Minimum icon size.") 121 | 122 | (defvar exwm-systemtray--list nil "The icon list.") 123 | 124 | (defvar exwm-systemtray--selection-owner-window nil 125 | "The selection owner window.") 126 | 127 | (defvar xcb:Atom:_NET_SYSTEM_TRAY_S0) 128 | 129 | (defun exwm-systemtray--embed (icon) 130 | "Embed an ICON." 131 | (exwm--log "Try to embed #x%x" icon) 132 | (let ((info (xcb:+request-unchecked+reply exwm-systemtray--connection 133 | (make-instance 'xcb:xembed:get-_XEMBED_INFO 134 | :window icon))) 135 | width* height* visible) 136 | (when info 137 | (exwm--log "Embed #x%x" icon) 138 | (with-slots (width height) 139 | (xcb:+request-unchecked+reply exwm-systemtray--connection 140 | (make-instance 'xcb:GetGeometry :drawable icon)) 141 | (setq height* exwm-systemtray-height 142 | width* (round (* width (/ (float height*) height)))) 143 | (when (< width* exwm-systemtray--icon-min-size) 144 | (setq width* exwm-systemtray--icon-min-size 145 | height* (round (* height (/ (float width*) width))))) 146 | (exwm--log "Resize from %dx%d to %dx%d" 147 | width height width* height*)) 148 | ;; Add this icon to save-set. 149 | (xcb:+request exwm-systemtray--connection 150 | (make-instance 'xcb:ChangeSaveSet 151 | :mode xcb:SetMode:Insert 152 | :window icon)) 153 | ;; Reparent to the embedder. 154 | (xcb:+request exwm-systemtray--connection 155 | (make-instance 'xcb:ReparentWindow 156 | :window icon 157 | :parent exwm-systemtray--embedder-window 158 | :x 0 159 | ;; Vertically centered. 160 | :y (/ (- exwm-systemtray-height height*) 2))) 161 | ;; Resize the icon. 162 | (xcb:+request exwm-systemtray--connection 163 | (make-instance 'xcb:ConfigureWindow 164 | :window icon 165 | :value-mask (logior xcb:ConfigWindow:Width 166 | xcb:ConfigWindow:Height 167 | xcb:ConfigWindow:BorderWidth) 168 | :width width* 169 | :height height* 170 | :border-width 0)) 171 | ;; Set event mask. 172 | (xcb:+request exwm-systemtray--connection 173 | (make-instance 'xcb:ChangeWindowAttributes 174 | :window icon 175 | :value-mask xcb:CW:EventMask 176 | :event-mask (logior xcb:EventMask:ResizeRedirect 177 | xcb:EventMask:KeyPress 178 | xcb:EventMask:PropertyChange))) 179 | ;; Grab all keys and forward them to Emacs frame. 180 | (unless (exwm-workspace--minibuffer-own-frame-p) 181 | (xcb:+request exwm-systemtray--connection 182 | (make-instance 'xcb:GrabKey 183 | :owner-events 0 184 | :grab-window icon 185 | :modifiers xcb:ModMask:Any 186 | :key xcb:Grab:Any 187 | :pointer-mode xcb:GrabMode:Async 188 | :keyboard-mode xcb:GrabMode:Async))) 189 | (setq visible (slot-value info 'flags)) 190 | (if visible 191 | (setq visible 192 | (/= 0 (logand (slot-value info 'flags) xcb:xembed:MAPPED))) 193 | ;; Default to visible. 194 | (setq visible t)) 195 | (when visible 196 | (exwm--log "Map the window") 197 | (xcb:+request exwm-systemtray--connection 198 | (make-instance 'xcb:MapWindow :window icon))) 199 | (xcb:+request exwm-systemtray--connection 200 | (make-instance 'xcb:xembed:SendEvent 201 | :destination icon 202 | :event 203 | (xcb:marshal 204 | (make-instance 'xcb:xembed:EMBEDDED-NOTIFY 205 | :window icon 206 | :time xcb:Time:CurrentTime 207 | :embedder 208 | exwm-systemtray--embedder-window 209 | :version 0) 210 | exwm-systemtray--connection))) 211 | (push `(,icon . ,(make-instance 'exwm-systemtray--icon 212 | :width width* 213 | :height height* 214 | :visible visible)) 215 | exwm-systemtray--list) 216 | (exwm-systemtray--refresh)))) 217 | 218 | (defun exwm-systemtray--unembed (icon) 219 | "Unembed an ICON." 220 | (exwm--log "Unembed #x%x" icon) 221 | (xcb:+request exwm-systemtray--connection 222 | (make-instance 'xcb:UnmapWindow :window icon)) 223 | (xcb:+request exwm-systemtray--connection 224 | (make-instance 'xcb:ReparentWindow 225 | :window icon 226 | :parent exwm--root 227 | :x 0 :y 0)) 228 | (setq exwm-systemtray--list 229 | (assq-delete-all icon exwm-systemtray--list)) 230 | (exwm-systemtray--refresh)) 231 | 232 | (defun exwm-systemtray--refresh () 233 | "Refresh the system tray." 234 | (exwm--log) 235 | ;; Make sure to redraw the embedder. 236 | (xcb:+request exwm-systemtray--connection 237 | (make-instance 'xcb:UnmapWindow 238 | :window exwm-systemtray--embedder-window)) 239 | (let ((x exwm-systemtray-icon-gap) 240 | map) 241 | (dolist (pair exwm-systemtray--list) 242 | (when (slot-value (cdr pair) 'visible) 243 | (xcb:+request exwm-systemtray--connection 244 | (make-instance 'xcb:ConfigureWindow 245 | :window (car pair) 246 | :value-mask xcb:ConfigWindow:X 247 | :x x)) 248 | (setq x (+ x (slot-value (cdr pair) 'width) 249 | exwm-systemtray-icon-gap)) 250 | (setq map t))) 251 | (let ((workarea (exwm-workspace--workarea exwm-workspace-current-index))) 252 | (xcb:+request exwm-systemtray--connection 253 | (make-instance 'xcb:ConfigureWindow 254 | :window exwm-systemtray--embedder-window 255 | :value-mask (logior xcb:ConfigWindow:X 256 | xcb:ConfigWindow:Width) 257 | :x (- (slot-value workarea 'width) x) 258 | :width x))) 259 | (when map 260 | (xcb:+request exwm-systemtray--connection 261 | (make-instance 'xcb:MapWindow 262 | :window exwm-systemtray--embedder-window)))) 263 | (xcb:flush exwm-systemtray--connection)) 264 | 265 | (defun exwm-systemtray--refresh-background-color (&optional remap) 266 | "Refresh background color after theme change or workspace switch. 267 | If REMAP is not nil, map and unmap the embedder window so that the background is 268 | redrawn." 269 | (when (facep exwm-systemtray-background-color) 270 | (exwm-systemtray--set-background-color) 271 | (when remap 272 | (xcb:+request exwm-systemtray--connection 273 | (make-instance 'xcb:UnmapWindow 274 | :window exwm-systemtray--embedder-window)) 275 | (xcb:+request exwm-systemtray--connection 276 | (make-instance 'xcb:MapWindow 277 | :window exwm-systemtray--embedder-window)) 278 | (xcb:flush exwm-systemtray--connection)))) 279 | 280 | (defun exwm-systemtray--set-background-color () 281 | "Change the background color of the embedder. 282 | The color is set according to `exwm-systemtray-background-color'. 283 | 284 | Note that this function does not change the current contents of the embedder 285 | window; unmap & map are necessary for the background color to take effect." 286 | (when (and exwm-systemtray--connection 287 | exwm-systemtray--embedder-window) 288 | (let* ((color (pcase exwm-systemtray-background-color 289 | ((or `transparent `nil) ; nil means transparent as well 290 | (if (exwm-systemtray--transparency-supported-p) 291 | nil 292 | (message "%s" "[EXWM] system tray does not support \ 293 | `transparent' background; using `default' instead") 294 | (face-background 'default exwm-workspace--current))) 295 | ((pred facep) 296 | (face-background exwm-systemtray-background-color 297 | exwm-workspace--current)) 298 | (_ exwm-systemtray-background-color))) 299 | (background-pixel (exwm--color->pixel color))) 300 | (xcb:+request exwm-systemtray--connection 301 | (make-instance 'xcb:ChangeWindowAttributes 302 | :window exwm-systemtray--embedder-window 303 | ;; Either-or. A `background-pixel' of nil 304 | ;; means simulate transparency. We use 305 | ;; `xcb:CW:BackPixmap' together with 306 | ;; `xcb:BackPixmap:ParentRelative' do that, 307 | ;; but this only works when the parent 308 | ;; window's visual (Emacs') has the same 309 | ;; visual depth. 310 | :value-mask (if background-pixel 311 | xcb:CW:BackPixel 312 | xcb:CW:BackPixmap) 313 | ;; Due to the :value-mask above, 314 | ;; :background-pixmap only takes effect when 315 | ;; `transparent' is requested and supported 316 | ;; (visual depth of Emacs and of system tray 317 | ;; are equal). Setting 318 | ;; `xcb:BackPixmap:ParentRelative' when 319 | ;; that's not the case would produce an 320 | ;; `xcb:Match' error. 321 | :background-pixmap xcb:BackPixmap:ParentRelative 322 | :background-pixel background-pixel))))) 323 | 324 | (defun exwm-systemtray--transparency-supported-p () 325 | "Check whether transparent background is supported. 326 | EXWM system tray supports transparency when the visual depth of the system tray 327 | window matches that of Emacs. The visual depth of the system tray window is the 328 | default visual depth of the display. 329 | 330 | Sections \"Visual and background pixmap handling\" and 331 | \"_NET_SYSTEM_TRAY_VISUAL\" of the System Tray Protocol Specification 332 | \(https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-latest.html#visuals) 333 | indicate how to support actual transparency." 334 | (let ((planes (x-display-planes))) 335 | (if exwm-systemtray--embedder-window-depth 336 | (= planes exwm-systemtray--embedder-window-depth) 337 | (<= planes 24)))) 338 | 339 | (defun exwm-systemtray--on-DestroyNotify (data _synthetic) 340 | "Unembed icons on DestroyNotify. 341 | Argument DATA contains the raw event data." 342 | (exwm--log) 343 | (let ((obj (make-instance 'xcb:DestroyNotify))) 344 | (xcb:unmarshal obj data) 345 | (with-slots (window) obj 346 | (when (assoc window exwm-systemtray--list) 347 | (exwm-systemtray--unembed window))))) 348 | 349 | (defun exwm-systemtray--on-ReparentNotify (data _synthetic) 350 | "Unembed icons on ReparentNotify. 351 | Argument DATA contains the raw event data." 352 | (exwm--log) 353 | (let ((obj (make-instance 'xcb:ReparentNotify))) 354 | (xcb:unmarshal obj data) 355 | (with-slots (window parent) obj 356 | (when (and (/= parent exwm-systemtray--embedder-window) 357 | (assoc window exwm-systemtray--list)) 358 | (exwm-systemtray--unembed window))))) 359 | 360 | (defun exwm-systemtray--on-ResizeRequest (data _synthetic) 361 | "Resize the tray icon on ResizeRequest. 362 | Argument DATA contains the raw event data." 363 | (exwm--log) 364 | (let ((obj (make-instance 'xcb:ResizeRequest)) 365 | attr) 366 | (xcb:unmarshal obj data) 367 | (with-slots (window width height) obj 368 | (when (setq attr (cdr (assoc window exwm-systemtray--list))) 369 | (with-slots ((width* width) 370 | (height* height)) 371 | attr 372 | (setq height* exwm-systemtray-height 373 | width* (round (* width (/ (float height*) height)))) 374 | (when (< width* exwm-systemtray--icon-min-size) 375 | (setq width* exwm-systemtray--icon-min-size 376 | height* (round (* height (/ (float width*) width))))) 377 | (xcb:+request exwm-systemtray--connection 378 | (make-instance 'xcb:ConfigureWindow 379 | :window window 380 | :value-mask (logior xcb:ConfigWindow:Y 381 | xcb:ConfigWindow:Width 382 | xcb:ConfigWindow:Height) 383 | ;; Vertically centered. 384 | :y (/ (- exwm-systemtray-height height*) 2) 385 | :width width* 386 | :height height*))) 387 | (exwm-systemtray--refresh))))) 388 | 389 | (defun exwm-systemtray--on-PropertyNotify (data _synthetic) 390 | "Map/Unmap the tray icon on PropertyNotify. 391 | Argument DATA contains the raw event data." 392 | (exwm--log) 393 | (let ((obj (make-instance 'xcb:PropertyNotify)) 394 | attr info visible) 395 | (xcb:unmarshal obj data) 396 | (with-slots (window atom state) obj 397 | (when (and (eq state xcb:Property:NewValue) 398 | (eq atom xcb:Atom:_XEMBED_INFO) 399 | (setq attr (cdr (assoc window exwm-systemtray--list)))) 400 | (setq info (xcb:+request-unchecked+reply exwm-systemtray--connection 401 | (make-instance 'xcb:xembed:get-_XEMBED_INFO 402 | :window window))) 403 | (when info 404 | (setq visible (/= 0 (logand (slot-value info 'flags) 405 | xcb:xembed:MAPPED))) 406 | (exwm--log "#x%x visible? %s" window visible) 407 | (if visible 408 | (xcb:+request exwm-systemtray--connection 409 | (make-instance 'xcb:MapWindow :window window)) 410 | (xcb:+request exwm-systemtray--connection 411 | (make-instance 'xcb:UnmapWindow :window window))) 412 | (setf (slot-value attr 'visible) visible) 413 | (exwm-systemtray--refresh)))))) 414 | 415 | (defun exwm-systemtray--on-ClientMessage (data _synthetic) 416 | "Handle client messages. 417 | Argument DATA contains the raw event data." 418 | (let ((obj (make-instance 'xcb:ClientMessage)) 419 | opcode data32) 420 | (xcb:unmarshal obj data) 421 | (with-slots (window type data) obj 422 | (when (eq type xcb:Atom:_NET_SYSTEM_TRAY_OPCODE) 423 | (setq data32 (slot-value data 'data32) 424 | opcode (elt data32 1)) 425 | (exwm--log "opcode: %s" opcode) 426 | (cond ((= opcode xcb:systemtray:opcode:REQUEST-DOCK) 427 | (unless (assoc (elt data32 2) exwm-systemtray--list) 428 | (exwm-systemtray--embed (elt data32 2)))) 429 | ;; Not implemented (rarely used nowadays). 430 | ((or (= opcode xcb:systemtray:opcode:BEGIN-MESSAGE) 431 | (= opcode xcb:systemtray:opcode:CANCEL-MESSAGE))) 432 | (t 433 | (exwm--log "Unknown opcode message: %s" obj))))))) 434 | 435 | (defun exwm-systemtray--on-KeyPress (data _synthetic) 436 | "Forward all KeyPress events to Emacs frame. 437 | Argument DATA contains the raw event data." 438 | (exwm--log) 439 | ;; This function is only executed when there's no autohide minibuffer, 440 | ;; a workspace frame has the input focus and the pointer is over a 441 | ;; tray icon. 442 | (let ((dest (frame-parameter (selected-frame) 'exwm-outer-id)) 443 | (obj (make-instance 'xcb:KeyPress))) 444 | (xcb:unmarshal obj data) 445 | (setf (slot-value obj 'event) dest) 446 | (xcb:+request exwm-systemtray--connection 447 | (make-instance 'xcb:SendEvent 448 | :propagate 0 449 | :destination dest 450 | :event-mask xcb:EventMask:NoEvent 451 | :event (xcb:marshal obj exwm-systemtray--connection)))) 452 | (xcb:flush exwm-systemtray--connection)) 453 | 454 | (defun exwm-systemtray--y-position () 455 | "Y position of system tray." 456 | (if (eq exwm-systemtray-position 'bottom) 457 | (- (slot-value (exwm-workspace--workarea 458 | exwm-workspace-current-index) 459 | 'height) 460 | exwm-workspace--frame-y-offset 461 | exwm-systemtray-height) 462 | 0)) 463 | 464 | (defun exwm-systemtray--on-workspace-switch () 465 | "Reparent/Refresh the system tray in `exwm-workspace-switch-hook'." 466 | (exwm--log) 467 | (unless (exwm-workspace--minibuffer-own-frame-p) 468 | (exwm-workspace--update-offsets) 469 | (xcb:+request exwm-systemtray--connection 470 | (make-instance 'xcb:ReparentWindow 471 | :window exwm-systemtray--embedder-window 472 | :parent (string-to-number 473 | (frame-parameter exwm-workspace--current 474 | 'window-id)) 475 | :x 0 476 | :y (exwm-systemtray--y-position)))) 477 | (exwm-systemtray--refresh-background-color) 478 | (exwm-systemtray--refresh)) 479 | 480 | (defun exwm-systemtray--on-theme-change (_theme) 481 | "Refresh system tray upon theme change." 482 | (exwm-systemtray--refresh-background-color 'remap)) 483 | 484 | (defun exwm-systemtray--refresh-all () 485 | "Reposition/Refresh the system tray." 486 | (exwm--log) 487 | (unless (exwm-workspace--minibuffer-own-frame-p) 488 | (exwm-workspace--update-offsets) 489 | (xcb:+request exwm-systemtray--connection 490 | (make-instance 'xcb:ConfigureWindow 491 | :window exwm-systemtray--embedder-window 492 | :value-mask xcb:ConfigWindow:Y 493 | :y (exwm-systemtray--y-position)))) 494 | (exwm-systemtray--refresh)) 495 | 496 | (cl-defun exwm-systemtray--init () 497 | "Initialize system tray module." 498 | (exwm--log) 499 | ;; idempotent initialization 500 | (when exwm-systemtray--connection 501 | (cl-return-from exwm-systemtray--init)) 502 | (cl-assert (not exwm-systemtray--list)) 503 | (cl-assert (not exwm-systemtray--selection-owner-window)) 504 | (cl-assert (not exwm-systemtray--embedder-window)) 505 | (unless exwm-systemtray-height 506 | (setq exwm-systemtray-height (max exwm-systemtray--icon-min-size 507 | (with-selected-window (minibuffer-window) 508 | (line-pixel-height))))) 509 | ;; Create a new connection. 510 | (setq exwm-systemtray--connection (xcb:connect)) 511 | (set-process-query-on-exit-flag (slot-value exwm-systemtray--connection 512 | 'process) 513 | nil) 514 | ;; Initialize XELB modules. 515 | (xcb:xembed:init exwm-systemtray--connection t) 516 | (xcb:systemtray:init exwm-systemtray--connection t) 517 | ;; Acquire the manager selection _NET_SYSTEM_TRAY_S0. 518 | (with-slots (owner) 519 | (xcb:+request-unchecked+reply exwm-systemtray--connection 520 | (make-instance 'xcb:GetSelectionOwner 521 | :selection xcb:Atom:_NET_SYSTEM_TRAY_S0)) 522 | (when (/= owner xcb:Window:None) 523 | (xcb:disconnect exwm-systemtray--connection) 524 | (setq exwm-systemtray--connection nil) 525 | (warn "[EXWM] Other system tray detected") 526 | (cl-return-from exwm-systemtray--init))) 527 | (let ((id (xcb:generate-id exwm-systemtray--connection))) 528 | (setq exwm-systemtray--selection-owner-window id) 529 | (xcb:+request exwm-systemtray--connection 530 | (make-instance 'xcb:CreateWindow 531 | :depth 0 532 | :wid id 533 | :parent exwm--root 534 | :x 0 535 | :y 0 536 | :width 1 537 | :height 1 538 | :border-width 0 539 | :class xcb:WindowClass:InputOnly 540 | :visual 0 541 | :value-mask xcb:CW:OverrideRedirect 542 | :override-redirect 1)) 543 | ;; Get the selection ownership. 544 | (xcb:+request exwm-systemtray--connection 545 | (make-instance 'xcb:SetSelectionOwner 546 | :owner id 547 | :selection xcb:Atom:_NET_SYSTEM_TRAY_S0 548 | :time xcb:Time:CurrentTime)) 549 | ;; Send a client message to announce the selection. 550 | (xcb:+request exwm-systemtray--connection 551 | (make-instance 'xcb:SendEvent 552 | :propagate 0 553 | :destination exwm--root 554 | :event-mask xcb:EventMask:StructureNotify 555 | :event (xcb:marshal 556 | (make-instance 'xcb:icccm:-ManagerSelection 557 | :window exwm--root 558 | :time xcb:Time:CurrentTime 559 | :selection 560 | xcb:Atom:_NET_SYSTEM_TRAY_S0 561 | :owner id) 562 | exwm-systemtray--connection))) 563 | ;; Set _NET_WM_NAME. 564 | (xcb:+request exwm-systemtray--connection 565 | (make-instance 'xcb:ewmh:set-_NET_WM_NAME 566 | :window id 567 | :data "EXWM: exwm-systemtray--selection-owner-window")) 568 | ;; Set the _NET_SYSTEM_TRAY_ORIENTATION property. 569 | (xcb:+request exwm-systemtray--connection 570 | (make-instance 'xcb:xembed:set-_NET_SYSTEM_TRAY_ORIENTATION 571 | :window id 572 | :data xcb:systemtray:ORIENTATION:HORZ))) 573 | ;; Create the embedder. 574 | (let ((id (xcb:generate-id exwm-systemtray--connection)) 575 | frame parent embedder-depth embedder-visual embedder-colormap y) 576 | (setq exwm-systemtray--embedder-window id) 577 | (if (exwm-workspace--minibuffer-own-frame-p) 578 | (setq frame exwm-workspace--minibuffer 579 | y (if (>= (line-pixel-height) exwm-systemtray-height) 580 | ;; Bottom aligned. 581 | (- (line-pixel-height) exwm-systemtray-height) 582 | ;; Vertically centered. 583 | (/ (- (line-pixel-height) exwm-systemtray-height) 2))) 584 | (exwm-workspace--update-offsets) 585 | (setq frame exwm-workspace--current 586 | ;; Bottom aligned. 587 | y (exwm-systemtray--y-position))) 588 | (setq parent (string-to-number (frame-parameter frame 'window-id))) 589 | ;; Use default depth, visual and colormap (from root window), instead of 590 | ;; Emacs frame's. See Section "Visual and background pixmap handling" in 591 | ;; "System Tray Protocol Specification 0.3". 592 | (let* ((vdc (exwm--get-visual-depth-colormap exwm-systemtray--connection 593 | exwm--root))) 594 | (setq embedder-visual (car vdc)) 595 | (setq embedder-depth (cadr vdc)) 596 | (setq embedder-colormap (caddr vdc))) 597 | ;; Note down the embedder window's depth. It will be used to check whether 598 | ;; we can use xcb:BackPixmap:ParentRelative to emulate transparency. 599 | (setq exwm-systemtray--embedder-window-depth embedder-depth) 600 | (xcb:+request exwm-systemtray--connection 601 | (make-instance 'xcb:CreateWindow 602 | :depth embedder-depth 603 | :wid id 604 | :parent parent 605 | :x 0 606 | :y y 607 | :width 1 608 | :height exwm-systemtray-height 609 | :border-width 0 610 | :class xcb:WindowClass:InputOutput 611 | :visual embedder-visual 612 | :colormap embedder-colormap 613 | :value-mask (logior xcb:CW:BorderPixel 614 | xcb:CW:Colormap 615 | xcb:CW:EventMask) 616 | :border-pixel 0 617 | :event-mask xcb:EventMask:SubstructureNotify)) 618 | (exwm-systemtray--set-background-color) 619 | ;; Set _NET_WM_NAME. 620 | (xcb:+request exwm-systemtray--connection 621 | (make-instance 'xcb:ewmh:set-_NET_WM_NAME 622 | :window id 623 | :data "EXWM: exwm-systemtray--embedder-window")) 624 | ;; Set _NET_WM_WINDOW_TYPE. 625 | (xcb:+request exwm-systemtray--connection 626 | (make-instance 'xcb:ewmh:set-_NET_WM_WINDOW_TYPE 627 | :window id 628 | :data (vector xcb:Atom:_NET_WM_WINDOW_TYPE_DOCK))) 629 | ;; Set _NET_SYSTEM_TRAY_VISUAL. 630 | (xcb:+request exwm-systemtray--connection 631 | (make-instance 'xcb:xembed:set-_NET_SYSTEM_TRAY_VISUAL 632 | :window exwm-systemtray--selection-owner-window 633 | :data embedder-visual))) 634 | (xcb:flush exwm-systemtray--connection) 635 | ;; Attach event listeners. 636 | (xcb:+event exwm-systemtray--connection 'xcb:DestroyNotify 637 | #'exwm-systemtray--on-DestroyNotify) 638 | (xcb:+event exwm-systemtray--connection 'xcb:ReparentNotify 639 | #'exwm-systemtray--on-ReparentNotify) 640 | (xcb:+event exwm-systemtray--connection 'xcb:ResizeRequest 641 | #'exwm-systemtray--on-ResizeRequest) 642 | (xcb:+event exwm-systemtray--connection 'xcb:PropertyNotify 643 | #'exwm-systemtray--on-PropertyNotify) 644 | (xcb:+event exwm-systemtray--connection 'xcb:ClientMessage 645 | #'exwm-systemtray--on-ClientMessage) 646 | (unless (exwm-workspace--minibuffer-own-frame-p) 647 | (xcb:+event exwm-systemtray--connection 'xcb:KeyPress 648 | #'exwm-systemtray--on-KeyPress)) 649 | ;; Add hook to move/reparent the embedder. 650 | (add-hook 'exwm-workspace-switch-hook #'exwm-systemtray--on-workspace-switch) 651 | (add-hook 'exwm-workspace--update-workareas-hook 652 | #'exwm-systemtray--refresh-all) 653 | ;; Add hook to update background colors. 654 | (add-hook 'enable-theme-functions #'exwm-systemtray--on-theme-change) 655 | (add-hook 'disable-theme-functions #'exwm-systemtray--on-theme-change) 656 | (add-hook 'menu-bar-mode-hook #'exwm-systemtray--refresh-all) 657 | (add-hook 'tool-bar-mode-hook #'exwm-systemtray--refresh-all) 658 | (when (boundp 'exwm-randr-refresh-hook) 659 | (add-hook 'exwm-randr-refresh-hook #'exwm-systemtray--refresh-all)) 660 | ;; The struts can be updated already. 661 | (when exwm-workspace--workareas 662 | (exwm-systemtray--refresh-all))) 663 | 664 | (defun exwm-systemtray--exit () 665 | "Exit the systemtray module." 666 | (exwm--log) 667 | (when exwm-systemtray--connection 668 | (when (slot-value exwm-systemtray--connection 'connected) 669 | ;; Hide & reparent out the embedder before disconnection to prevent 670 | ;; embedded icons from being reparented to an Emacs frame (which is the 671 | ;; parent of the embedder). 672 | (xcb:+request exwm-systemtray--connection 673 | (make-instance 'xcb:UnmapWindow 674 | :window exwm-systemtray--embedder-window)) 675 | (xcb:+request exwm-systemtray--connection 676 | (make-instance 'xcb:ReparentWindow 677 | :window exwm-systemtray--embedder-window 678 | :parent exwm--root 679 | :x 0 680 | :y 0)) 681 | (xcb:disconnect exwm-systemtray--connection)) 682 | (setq exwm-systemtray--connection nil 683 | exwm-systemtray--list nil 684 | exwm-systemtray--selection-owner-window nil 685 | exwm-systemtray--embedder-window nil 686 | exwm-systemtray--embedder-window-depth nil) 687 | (remove-hook 'exwm-workspace-switch-hook 688 | #'exwm-systemtray--on-workspace-switch) 689 | (remove-hook 'exwm-workspace--update-workareas-hook 690 | #'exwm-systemtray--refresh-all) 691 | (remove-hook 'enable-theme-functions #'exwm-systemtray--on-theme-change) 692 | (remove-hook 'disable-theme-functions #'exwm-systemtray--on-theme-change) 693 | (remove-hook 'menu-bar-mode-hook #'exwm-systemtray--refresh-all) 694 | (remove-hook 'tool-bar-mode-hook #'exwm-systemtray--refresh-all) 695 | (when (boundp 'exwm-randr-refresh-hook) 696 | (remove-hook 'exwm-randr-refresh-hook #'exwm-systemtray--refresh-all)))) 697 | 698 | (provide 'exwm-systemtray) 699 | ;;; exwm-systemtray.el ends here 700 | -------------------------------------------------------------------------------- /exwm-xim.el: -------------------------------------------------------------------------------- 1 | ;;; exwm-xim.el --- XIM Module for EXWM -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2019-2025 Free Software Foundation, Inc. 4 | 5 | ;; Author: Chris Feng 6 | 7 | ;; This file is part of GNU Emacs. 8 | 9 | ;; GNU Emacs is free software: you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation, either version 3 of the License, or 12 | ;; (at your option) any later version. 13 | 14 | ;; GNU Emacs is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with GNU Emacs. If not, see . 21 | 22 | ;;; Commentary: 23 | 24 | ;; This module adds XIM support for EXWM and allows sending characters 25 | ;; generated by any Emacs's builtin input method (info node `Input Methods') 26 | ;; to X windows. 27 | 28 | ;; This module is essentially an X input method server utilizing Emacs as 29 | ;; its backend. It talks with X windows through the XIM protocol. The XIM 30 | ;; protocol is quite flexible by itself, stating that an implementation can 31 | ;; create network connections of various types as well as make use of an 32 | ;; existing X connection for communication, and that an IM server may 33 | ;; support multiple transport versions, various input styles and several 34 | ;; event flow modals, etc. Here we only make choices that are most popular 35 | ;; among other IM servers and more importantly, practical for Emacs to act 36 | ;; as an IM server: 37 | ;; 38 | ;; + Packets are transported on top of an X connection like most IMEs. 39 | ;; + Only transport version 0.0 (i.e. only-CM & Property-with-CM) is 40 | ;; supported (same as "IM Server Developers Kit", adopted by most IMEs). 41 | ;; + Only support static event flow, on-demand-synchronous method. 42 | ;; + Only "root-window" input style is supported. 43 | 44 | ;; To use this module, enable it as follows: 45 | ;; 46 | ;; (exwm-xim-mode 1) 47 | ;; 48 | ;; A keybinding for `toggle-input-method' is probably required to turn on & 49 | ;; off an input method (default to `default-input-method'). It's bound to 50 | ;; 'C-\' by default and can be made reachable when working with X windows: 51 | ;; 52 | ;; (push ?\C-\\ exwm-input-prefix-keys) 53 | ;; 54 | ;; It's also required (and error-prone) to setup environment variables to 55 | ;; make applications actually use this input method. Typically the 56 | ;; following lines should be inserted into '~/.xinitrc'. 57 | ;; 58 | ;; export XMODIFIERS=@im=exwm-xim 59 | ;; export GTK_IM_MODULE=xim 60 | ;; export QT_IM_MODULE=xim 61 | ;; export CLUTTER_IM_MODULE=xim 62 | 63 | ;; References: 64 | ;; + XIM (http://www.x.org/releases/X11R7.6/doc/libX11/specs/XIM/xim.html) 65 | ;; + IMdkit (http://xorg.freedesktop.org/archive/unsupported/lib/IMdkit/) 66 | ;; + UIM (https://github.com/uim/uim) 67 | 68 | ;;; Code: 69 | 70 | (require 'cl-lib) 71 | 72 | (require 'xcb-keysyms) 73 | (require 'xcb-xim) 74 | 75 | (require 'exwm-core) 76 | (require 'exwm-input) 77 | 78 | (defconst exwm-xim--locales 79 | "@locale=\ 80 | aa,af,ak,am,an,anp,ar,as,ast,ayc,az,be,bem,ber,bg,bhb,bho,bn,bo,br,brx,bs,byn,\ 81 | ca,ce,cmn,crh,cs,csb,cv,cy,da,de,doi,dv,dz,el,en,es,et,eu,fa,ff,fi,fil,fo,fr,\ 82 | fur,fy,ga,gd,gez,gl,gu,gv,ha,hak,he,hi,hne,hr,hsb,ht,hu,hy,ia,id,ig,ik,is,it,\ 83 | iu,iw,ja,ka,kk,kl,km,kn,ko,kok,ks,ku,kw,ky,lb,lg,li,li,lij,lo,lt,lv,lzh,mag,\ 84 | mai,mg,mhr,mi,mk,ml,mn,mni,mr,ms,mt,my,nan,nb,nds,ne,nhn,niu,nl,nn,nr,nso,oc,\ 85 | om,or,os,pa,pa,pap,pl,ps,pt,quz,raj,ro,ru,rw,sa,sat,sc,sd,se,shs,si,sid,sk,sl,\ 86 | so,sq,sr,ss,st,sv,sw,szl,ta,tcy,te,tg,th,the,ti,tig,tk,tl,tn,tr,ts,tt,ug,uk,\ 87 | unm,ur,uz,ve,vi,wa,wae,wal,wo,xh,yi,yo,yue,zh,zu,\ 88 | C,no" 89 | "All supported locales (stolen from glibc).") 90 | 91 | (defconst exwm-xim--default-error 92 | (make-instance 'xim:error 93 | :im-id 0 94 | :ic-id 0 95 | :flag xim:error-flag:invalid-both 96 | :error-code xim:error-code:bad-something 97 | :length 0 98 | :type 0 99 | :detail nil) 100 | "Default error returned to clients.") 101 | 102 | (defconst exwm-xim--default-im-attrs 103 | (list (make-instance 'xim:XIMATTR 104 | :id 0 105 | :type xim:ATTRIBUTE-VALUE-TYPE:xim-styles 106 | :length (length xlib:XNQueryInputStyle) 107 | :attribute xlib:XNQueryInputStyle)) 108 | "Default IM attrs returned to clients.") 109 | 110 | (defconst exwm-xim--default-ic-attrs 111 | (list (make-instance 'xim:XICATTR 112 | :id 0 113 | :type xim:ATTRIBUTE-VALUE-TYPE:long-data 114 | :length (length xlib:XNInputStyle) 115 | :attribute xlib:XNInputStyle) 116 | (make-instance 'xim:XICATTR 117 | :id 1 118 | :type xim:ATTRIBUTE-VALUE-TYPE:window 119 | :length (length xlib:XNClientWindow) 120 | :attribute xlib:XNClientWindow) 121 | ;; Required by e.g. xterm. 122 | (make-instance 'xim:XICATTR 123 | :id 2 124 | :type xim:ATTRIBUTE-VALUE-TYPE:window 125 | :length (length xlib:XNFocusWindow) 126 | :attribute xlib:XNFocusWindow)) 127 | "Default IC attrs returned to clients.") 128 | 129 | (defconst exwm-xim--default-styles 130 | (make-instance 'xim:XIMStyles 131 | :number nil 132 | :styles (list (logior xlib:XIMPreeditNothing 133 | xlib:XIMStatusNothing))) 134 | "Default styles: root-window, i.e. no preediting or status display support.") 135 | 136 | (defconst exwm-xim--default-attributes 137 | (list (make-instance 'xim:XIMATTRIBUTE 138 | :id 0 139 | :length nil 140 | :value exwm-xim--default-styles)) 141 | "Default IM/IC attributes returned to clients.") 142 | 143 | (defvar exwm-xim--conn nil 144 | "The X connection for initiating other XIM connections.") 145 | (defvar exwm-xim--event-xwin nil 146 | "X window for initiating new XIM connections.") 147 | (defvar exwm-xim--server-client-plist '(nil nil) 148 | "Plist mapping server window to [X connection, client window, byte-order].") 149 | (defvar exwm-xim--client-server-plist '(nil nil) 150 | "Plist mapping client window to server window.") 151 | (defvar exwm-xim--property-index 0 "For generating a unique property name.") 152 | (defvar exwm-xim--im-id 0 "Last IM ID.") 153 | (defvar exwm-xim--ic-id 0 "Last IC ID.") 154 | 155 | ;; X11 atoms. 156 | (defvar exwm-xim--@server nil) 157 | (defvar exwm-xim--LOCALES nil) 158 | (defvar exwm-xim--TRANSPORT nil) 159 | (defvar exwm-xim--XIM_SERVERS nil) 160 | (defvar exwm-xim--_XIM_PROTOCOL nil) 161 | (defvar exwm-xim--_XIM_XCONNECT nil) 162 | 163 | (defvar exwm-xim-buffer-p nil 164 | "Whether current buffer is used by exwm-xim.") 165 | (make-variable-buffer-local 'exwm-xim-buffer-p) 166 | 167 | (defun exwm-xim--on-SelectionRequest (data _synthetic) 168 | "Handle SelectionRequest events on IMS window. 169 | DATA contains unmarshalled SelectionRequest event data. 170 | 171 | Such events would be received when clients query for LOCALES or TRANSPORT." 172 | (exwm--log) 173 | (let ((evt (make-instance 'xcb:SelectionRequest)) 174 | value fake-event) 175 | (xcb:unmarshal evt data) 176 | (with-slots (time requestor selection target property) evt 177 | (setq value (cond ((= target exwm-xim--LOCALES) 178 | ;; Return supported locales. 179 | exwm-xim--locales) 180 | ((= target exwm-xim--TRANSPORT) 181 | ;; Use XIM over an X connection. 182 | "@transport=X/"))) 183 | (when value 184 | ;; Change the property. 185 | (xcb:+request exwm-xim--conn 186 | (make-instance 'xcb:ChangeProperty 187 | :mode xcb:PropMode:Replace 188 | :window requestor 189 | :property property 190 | :type target 191 | :format 8 192 | :data-len (length value) 193 | :data value)) 194 | ;; Send a SelectionNotify event. 195 | (setq fake-event (make-instance 'xcb:SelectionNotify 196 | :time time 197 | :requestor requestor 198 | :selection selection 199 | :target target 200 | :property property)) 201 | (xcb:+request exwm-xim--conn 202 | (make-instance 'xcb:SendEvent 203 | :propagate 0 204 | :destination requestor 205 | :event-mask xcb:EventMask:NoEvent 206 | :event (xcb:marshal fake-event exwm-xim--conn))) 207 | (xcb:flush exwm-xim--conn))))) 208 | 209 | (cl-defun exwm-xim--on-ClientMessage-0 (data _synthetic) 210 | "Handle ClientMessage event on IMS window (new connection). 211 | 212 | Such events would be received when clients request for _XIM_XCONNECT. 213 | A new X connection and server window would be created to communicate with 214 | this client." 215 | (exwm--log) 216 | (let ((evt (make-instance 'xcb:ClientMessage)) 217 | conn client-xwin server-xwin) 218 | (xcb:unmarshal evt data) 219 | (with-slots (window type data) evt 220 | (unless (= type exwm-xim--_XIM_XCONNECT) 221 | ;; Only handle _XIM_XCONNECT. 222 | (exwm--log "Ignore ClientMessage %s" type) 223 | (cl-return-from exwm-xim--on-ClientMessage-0)) 224 | (setq client-xwin (elt (slot-value data 'data32) 0) 225 | ;; Create a new X connection and a new server window. 226 | conn (xcb:connect) 227 | server-xwin (xcb:generate-id conn)) 228 | (set-process-query-on-exit-flag (slot-value conn 'process) nil) 229 | ;; Store this client. 230 | (plist-put exwm-xim--server-client-plist server-xwin 231 | `[,conn ,client-xwin nil]) 232 | (plist-put exwm-xim--client-server-plist client-xwin server-xwin) 233 | ;; Select DestroyNotify events on this client window. 234 | (xcb:+request exwm-xim--conn 235 | (make-instance 'xcb:ChangeWindowAttributes 236 | :window client-xwin 237 | :value-mask xcb:CW:EventMask 238 | :event-mask xcb:EventMask:StructureNotify)) 239 | (xcb:flush exwm-xim--conn) 240 | ;; Handle ClientMessage events from this new connection. 241 | (xcb:+event conn 'xcb:ClientMessage #'exwm-xim--on-ClientMessage) 242 | ;; Create a communication window. 243 | (xcb:+request conn 244 | (make-instance 'xcb:CreateWindow 245 | :depth 0 246 | :wid server-xwin 247 | :parent exwm--root 248 | :x 0 249 | :y 0 250 | :width 1 251 | :height 1 252 | :border-width 0 253 | :class xcb:WindowClass:InputOutput 254 | :visual 0 255 | :value-mask xcb:CW:OverrideRedirect 256 | :override-redirect 1)) 257 | (xcb:flush conn) 258 | ;; Send connection establishment ClientMessage. 259 | (setf window client-xwin 260 | (slot-value data 'data32) `(,server-xwin 0 0 0 0)) 261 | (slot-makeunbound data 'data8) 262 | (slot-makeunbound data 'data16) 263 | (xcb:+request exwm-xim--conn 264 | (make-instance 'xcb:SendEvent 265 | :propagate 0 266 | :destination client-xwin 267 | :event-mask xcb:EventMask:NoEvent 268 | :event (xcb:marshal evt exwm-xim--conn))) 269 | (xcb:flush exwm-xim--conn)))) 270 | 271 | (cl-defun exwm-xim--on-ClientMessage (data _synthetic) 272 | "Handle ClientMessage event DATA on IMS communication window (request). 273 | 274 | Such events would be received when clients request for _XIM_PROTOCOL. 275 | The actual XIM request is in client message data or a property." 276 | (exwm--log) 277 | (let ((evt (make-instance 'xcb:ClientMessage)) 278 | conn client-xwin server-xwin) 279 | (xcb:unmarshal evt data) 280 | (with-slots (format window type data) evt 281 | (unless (= type exwm-xim--_XIM_PROTOCOL) 282 | (exwm--log "Ignore ClientMessage %s" type) 283 | (cl-return-from exwm-xim--on-ClientMessage)) 284 | (setq server-xwin window 285 | conn (plist-get exwm-xim--server-client-plist server-xwin) 286 | client-xwin (elt conn 1) 287 | conn (elt conn 0)) 288 | (cond ((= format 8) 289 | ;; Data. 290 | (exwm-xim--on-request (vconcat (slot-value data 'data8)) 291 | conn client-xwin server-xwin)) 292 | ((= format 32) 293 | ;; Atom. 294 | (with-slots (data32) data 295 | (with-slots (value) 296 | (xcb:+request-unchecked+reply conn 297 | (make-instance 'xcb:GetProperty 298 | :delete 1 299 | :window server-xwin 300 | :property (elt data32 1) 301 | :type xcb:GetPropertyType:Any 302 | :long-offset 0 303 | :long-length (elt data32 0))) 304 | (when (> (length value) 0) 305 | (exwm-xim--on-request value conn client-xwin 306 | server-xwin))))))))) 307 | 308 | (defun exwm-xim--on-request (data conn client-xwin server-xwin) 309 | "Handle an XIM reuqest." 310 | (exwm--log) 311 | (let ((opcode (elt data 0)) 312 | ;; Let-bind `xim:lsb' to make pack/unpack functions work correctly. 313 | (xim:lsb (elt (plist-get exwm-xim--server-client-plist server-xwin) 2)) 314 | req replies) 315 | (cond ((= opcode xim:opcode:error) 316 | (exwm--log "ERROR: %s" data)) 317 | ((= opcode xim:opcode:connect) 318 | (exwm--log "CONNECT") 319 | (setq xim:lsb (= (elt data 4) xim:connect-byte-order:lsb-first)) 320 | ;; Store byte-order. 321 | (setf (elt (plist-get exwm-xim--server-client-plist server-xwin) 2) 322 | xim:lsb) 323 | (setq req (make-instance 'xim:connect)) 324 | (xcb:unmarshal req data) 325 | (if (and (= (slot-value req 'major-version) 1) 326 | (= (slot-value req 'minor-version) 0) 327 | ;; Do not support authentication. 328 | (= (slot-value req 'number) 0)) 329 | ;; Accept the connection. 330 | (push (make-instance 'xim:connect-reply) replies) 331 | ;; Deny it. 332 | (push exwm-xim--default-error replies))) 333 | ((memq opcode (list xim:opcode:auth-required 334 | xim:opcode:auth-reply 335 | xim:opcode:auth-next 336 | xim:opcode:auth-ng)) 337 | (exwm--log "AUTH: %d" opcode) 338 | ;; Deny any attempt to make authentication. 339 | (push exwm-xim--default-error replies)) 340 | ((= opcode xim:opcode:disconnect) 341 | (exwm--log "DISCONNECT") 342 | ;; Gracefully disconnect from the client. 343 | (exwm-xim--make-request (make-instance 'xim:disconnect-reply) 344 | conn client-xwin) 345 | ;; Destroy the communication window & connection. 346 | (xcb:+request conn 347 | (make-instance 'xcb:DestroyWindow 348 | :window server-xwin)) 349 | (xcb:disconnect conn) 350 | ;; Clean up cache. 351 | (cl-remf exwm-xim--server-client-plist server-xwin) 352 | (cl-remf exwm-xim--client-server-plist client-xwin)) 353 | ((= opcode xim:opcode:open) 354 | (exwm--log "OPEN") 355 | ;; Note: We make no check here. 356 | (setq exwm-xim--im-id (if (< exwm-xim--im-id #xffff) 357 | (1+ exwm-xim--im-id) 358 | 1)) 359 | (setq replies 360 | (list 361 | (make-instance 'xim:open-reply 362 | :im-id exwm-xim--im-id 363 | :im-attrs-length nil 364 | :im-attrs exwm-xim--default-im-attrs 365 | :ic-attrs-length nil 366 | :ic-attrs exwm-xim--default-ic-attrs) 367 | (make-instance 'xim:set-event-mask 368 | :im-id exwm-xim--im-id 369 | :ic-id 0 370 | ;; Static event flow. 371 | :forward-event-mask xcb:EventMask:KeyPress 372 | ;; on-demand-synchronous method. 373 | :synchronous-event-mask 374 | xcb:EventMask:NoEvent)))) 375 | ((= opcode xim:opcode:close) 376 | (exwm--log "CLOSE") 377 | (setq req (make-instance 'xim:close)) 378 | (xcb:unmarshal req data) 379 | (push (make-instance 'xim:close-reply 380 | :im-id (slot-value req 'im-id)) 381 | replies)) 382 | ((= opcode xim:opcode:trigger-notify) 383 | (exwm--log "TRIGGER-NOTIFY") 384 | ;; Only static event flow modal is supported. 385 | (push exwm-xim--default-error replies)) 386 | ((= opcode xim:opcode:encoding-negotiation) 387 | (exwm--log "ENCODING-NEGOTIATION") 388 | (setq req (make-instance 'xim:encoding-negotiation)) 389 | (xcb:unmarshal req data) 390 | (let ((index (cl-position "COMPOUND_TEXT" 391 | (mapcar (lambda (i) (slot-value i 'name)) 392 | (slot-value req 'names)) 393 | :test #'equal))) 394 | (unless index 395 | ;; Fallback to portable character encoding (a subset of ASCII). 396 | (setq index -1)) 397 | (push (make-instance 'xim:encoding-negotiation-reply 398 | :im-id (slot-value req 'im-id) 399 | :category 400 | xim:encoding-negotiation-reply-category:name 401 | :index index) 402 | replies))) 403 | ((= opcode xim:opcode:query-extension) 404 | (exwm--log "QUERY-EXTENSION") 405 | (setq req (make-instance 'xim:query-extension)) 406 | (xcb:unmarshal req data) 407 | (push (make-instance 'xim:query-extension-reply 408 | :im-id (slot-value req 'im-id) 409 | ;; No extension support. 410 | :length 0 411 | :extensions nil) 412 | replies)) 413 | ((= opcode xim:opcode:set-im-values) 414 | (exwm--log "SET-IM-VALUES") 415 | ;; There's only one possible input method attribute. 416 | (setq req (make-instance 'xim:set-im-values)) 417 | (xcb:unmarshal req data) 418 | (push (make-instance 'xim:set-im-values-reply 419 | :im-id (slot-value req 'im-id)) 420 | replies)) 421 | ((= opcode xim:opcode:get-im-values) 422 | (exwm--log "GET-IM-VALUES") 423 | (setq req (make-instance 'xim:get-im-values)) 424 | (let (im-attributes-id) 425 | (xcb:unmarshal req data) 426 | (setq im-attributes-id (slot-value req 'im-attributes-id)) 427 | (if (cl-notevery (lambda (i) (= i 0)) im-attributes-id) 428 | ;; Only support one IM attributes. 429 | (push (make-instance 'xim:error 430 | :im-id (slot-value req 'im-id) 431 | :ic-id 0 432 | :flag xim:error-flag:invalid-ic-id 433 | :error-code xim:error-code:bad-something 434 | :length 0 435 | :type 0 436 | :detail nil) 437 | replies) 438 | (push 439 | (make-instance 'xim:get-im-values-reply 440 | :im-id (slot-value req 'im-id) 441 | :length nil 442 | :im-attributes exwm-xim--default-attributes) 443 | replies)))) 444 | ((= opcode xim:opcode:create-ic) 445 | (exwm--log "CREATE-IC") 446 | (setq req (make-instance 'xim:create-ic)) 447 | (xcb:unmarshal req data) 448 | ;; Note: The ic-attributes slot is ignored. 449 | (setq exwm-xim--ic-id (if (< exwm-xim--ic-id #xffff) 450 | (1+ exwm-xim--ic-id) 451 | 1)) 452 | (push (make-instance 'xim:create-ic-reply 453 | :im-id (slot-value req 'im-id) 454 | :ic-id exwm-xim--ic-id) 455 | replies)) 456 | ((= opcode xim:opcode:destroy-ic) 457 | (exwm--log "DESTROY-IC") 458 | (setq req (make-instance 'xim:destroy-ic)) 459 | (xcb:unmarshal req data) 460 | (push (make-instance 'xim:destroy-ic-reply 461 | :im-id (slot-value req 'im-id) 462 | :ic-id (slot-value req 'ic-id)) 463 | replies)) 464 | ((= opcode xim:opcode:set-ic-values) 465 | (exwm--log "SET-IC-VALUES") 466 | (setq req (make-instance 'xim:set-ic-values)) 467 | (xcb:unmarshal req data) 468 | ;; We don't distinguish between input contexts. 469 | (push (make-instance 'xim:set-ic-values-reply 470 | :im-id (slot-value req 'im-id) 471 | :ic-id (slot-value req 'ic-id)) 472 | replies)) 473 | ((= opcode xim:opcode:get-ic-values) 474 | (exwm--log "GET-IC-VALUES") 475 | (setq req (make-instance 'xim:get-ic-values)) 476 | (xcb:unmarshal req data) 477 | (push (make-instance 'xim:get-ic-values-reply 478 | :im-id (slot-value req 'im-id) 479 | :ic-id (slot-value req 'ic-id) 480 | :length nil 481 | :ic-attributes exwm-xim--default-attributes) 482 | replies)) 483 | ((= opcode xim:opcode:set-ic-focus) 484 | (exwm--log "SET-IC-FOCUS") 485 | ;; All input contexts are the same. 486 | ) 487 | ((= opcode xim:opcode:unset-ic-focus) 488 | (exwm--log "UNSET-IC-FOCUS") 489 | ;; All input contexts are the same. 490 | ) 491 | ((= opcode xim:opcode:forward-event) 492 | (exwm--log "FORWARD-EVENT") 493 | (setq req (make-instance 'xim:forward-event)) 494 | (xcb:unmarshal req data) 495 | (exwm-xim--handle-forward-event-request req xim:lsb conn 496 | client-xwin)) 497 | ((= opcode xim:opcode:sync) 498 | (exwm--log "SYNC") 499 | (setq req (make-instance 'xim:sync)) 500 | (xcb:unmarshal req data) 501 | (push (make-instance 'xim:sync-reply 502 | :im-id (slot-value req 'im-id) 503 | :ic-id (slot-value req 'ic-id)) 504 | replies)) 505 | ((= opcode xim:opcode:sync-reply) 506 | (exwm--log "SYNC-REPLY")) 507 | ((= opcode xim:opcode:reset-ic) 508 | (exwm--log "RESET-IC") 509 | ;; No context-specific data saved. 510 | (setq req (make-instance 'xim:reset-ic)) 511 | (xcb:unmarshal req data) 512 | (push (make-instance 'xim:reset-ic-reply 513 | :im-id (slot-value req 'im-id) 514 | :ic-id (slot-value req 'ic-id) 515 | :length 0 516 | :string "") 517 | replies)) 518 | ((memq opcode (list xim:opcode:str-conversion-reply 519 | xim:opcode:preedit-start-reply 520 | xim:opcode:preedit-caret-reply)) 521 | (exwm--log "PREEDIT: %d" opcode) 522 | ;; No preedit support. 523 | (push exwm-xim--default-error replies)) 524 | (t 525 | (exwm--log "Bad protocol") 526 | (push exwm-xim--default-error replies))) 527 | ;; Actually send the replies. 528 | (when replies 529 | (mapc (lambda (reply) 530 | (exwm-xim--make-request reply conn client-xwin)) 531 | replies) 532 | (xcb:flush conn)))) 533 | 534 | (defun exwm-xim--handle-forward-event-request (req lsb conn client-xwin) 535 | (let ((im-func (with-current-buffer (window-buffer) 536 | input-method-function)) 537 | key-event keysym keysyms event result) 538 | ;; Note: The flag slot is ignored. 539 | ;; Do conversion in client's byte-order. 540 | (let ((xcb:lsb lsb)) 541 | (setq key-event (make-instance 'xcb:KeyPress)) 542 | (xcb:unmarshal key-event (slot-value req 'event))) 543 | (with-slots (detail state) key-event 544 | (setq keysym (xcb:keysyms:keycode->keysym exwm-xim--conn detail 545 | state)) 546 | (when (/= (car keysym) 0) 547 | (setq event (xcb:keysyms:keysym->event 548 | exwm-xim--conn 549 | (car keysym) 550 | (logand state (lognot (cdr keysym))))))) 551 | (while (or (slot-value req 'event) unread-command-events) 552 | (unless (slot-value req 'event) 553 | (setq event (pop unread-command-events)) 554 | ;; Handle events in (t . EVENT) format. 555 | (when (and (consp event) 556 | (eq (car event) t)) 557 | (setq event (cdr event)))) 558 | (if (or (not im-func) 559 | ;; `list' is the default method. 560 | (eq im-func #'list) 561 | (not event) 562 | ;; Select only printable keys. 563 | (not (integerp event)) (> #x20 event) (< #x7e event)) 564 | ;; Either there is no active input method, or invalid key 565 | ;; is detected. 566 | (with-slots ((raw-event event) 567 | im-id ic-id serial-number) 568 | req 569 | (if raw-event 570 | (setq event raw-event) 571 | (setq keysyms (xcb:keysyms:event->keysyms exwm-xim--conn event)) 572 | (with-slots (detail state) key-event 573 | (setf detail (xcb:keysyms:keysym->keycode exwm-xim--conn 574 | (caar keysyms)) 575 | state (cdar keysyms))) 576 | (setq event (let ((xcb:lsb lsb)) 577 | (xcb:marshal key-event conn)))) 578 | (when event 579 | (exwm-xim--make-request 580 | (make-instance 'xim:forward-event 581 | :im-id im-id 582 | :ic-id ic-id 583 | :flag xim:commit-flag:synchronous 584 | :serial-number serial-number 585 | :event event) 586 | conn client-xwin))) 587 | (when (eq exwm--selected-input-mode 'char-mode) 588 | ;; Grab keyboard temporarily for char-mode. 589 | (exwm-input--grab-keyboard)) 590 | (unwind-protect 591 | (with-temp-buffer 592 | ;; This variable is used to test whether exwm-xim is enabled. 593 | ;; Used by e.g. pyim-probe. 594 | (setq-local exwm-xim-buffer-p t) 595 | ;; Always show key strokes. 596 | (let ((input-method-use-echo-area t) 597 | (exwm-input-line-mode-passthrough t)) 598 | (setq result (funcall im-func event)) 599 | ;; Clear echo area for the input method. 600 | (message nil) 601 | ;; This also works for portable character encoding. 602 | (setq result 603 | (encode-coding-string (concat result) 604 | 'compound-text-with-extensions)) 605 | (exwm-xim--make-request 606 | (make-instance 'xim:commit-x-lookup-chars 607 | :im-id (slot-value req 'im-id) 608 | :ic-id (slot-value req 'ic-id) 609 | :flag (logior xim:commit-flag:synchronous 610 | xim:commit-flag:x-lookup-chars) 611 | :length (length result) 612 | :string result) 613 | conn client-xwin))) 614 | (when (eq exwm--selected-input-mode 'char-mode) 615 | (exwm-input--release-keyboard)))) 616 | (xcb:flush conn) 617 | (setf event nil 618 | (slot-value req 'event) nil)))) 619 | 620 | (defun exwm-xim--make-request (req conn client-xwin) 621 | "Make an XIM request REQ via connection CONN. 622 | 623 | CLIENT-XWIN would receive a ClientMessage event either telling the client 624 | the request data or where to fetch the data." 625 | (exwm--log) 626 | (let ((data (xcb:marshal req)) 627 | property format client-message-data client-message) 628 | (if (<= (length data) 20) 629 | ;; Send short requests directly with client messages. 630 | (setq format 8 631 | ;; Pad to 20 bytes. 632 | data (append data (make-list (- 20 (length data)) 0)) 633 | client-message-data (make-instance 'xcb:ClientMessageData 634 | :data8 data)) 635 | ;; Send long requests with properties. 636 | (setq property (exwm--intern-atom (format "_EXWM_XIM_%x" 637 | exwm-xim--property-index))) 638 | (cl-incf exwm-xim--property-index) 639 | (xcb:+request conn 640 | (make-instance 'xcb:ChangeProperty 641 | :mode xcb:PropMode:Append 642 | :window client-xwin 643 | :property property 644 | :type xcb:Atom:STRING 645 | :format 8 646 | :data-len (length data) 647 | :data data)) 648 | ;; Also send a client message to notify the client about this property. 649 | (setq format 32 650 | client-message-data (make-instance 'xcb:ClientMessageData 651 | :data32 `(,(length data) 652 | ,property 653 | ;; Pad to 20 bytes. 654 | 0 0 0)))) 655 | ;; Send the client message. 656 | (setq client-message (make-instance 'xcb:ClientMessage 657 | :format format 658 | :window client-xwin 659 | :type exwm-xim--_XIM_PROTOCOL 660 | :data client-message-data)) 661 | (xcb:+request conn 662 | (make-instance 'xcb:SendEvent 663 | :propagate 0 664 | :destination client-xwin 665 | :event-mask xcb:EventMask:NoEvent 666 | :event (xcb:marshal client-message conn))))) 667 | 668 | (defun exwm-xim--on-DestroyNotify (data synthetic) 669 | "Do cleanups on receiving DestroyNotify event. 670 | 671 | Such event would be received when the client window is destroyed." 672 | (exwm--log) 673 | (unless synthetic 674 | (let ((evt (make-instance 'xcb:DestroyNotify)) 675 | conn client-xwin server-xwin) 676 | (xcb:unmarshal evt data) 677 | (setq client-xwin (slot-value evt 'window) 678 | server-xwin (plist-get exwm-xim--client-server-plist client-xwin)) 679 | (when server-xwin 680 | (setq conn (aref (plist-get exwm-xim--server-client-plist server-xwin) 681 | 0)) 682 | (cl-remf exwm-xim--server-client-plist server-xwin) 683 | (cl-remf exwm-xim--client-server-plist client-xwin) 684 | ;; Destroy the communication window & connection. 685 | (xcb:+request conn 686 | (make-instance 'xcb:DestroyWindow 687 | :window server-xwin)) 688 | (xcb:disconnect conn))))) 689 | 690 | (cl-defun exwm-xim--init () 691 | "Initialize the XIM module." 692 | (exwm--log) 693 | (when exwm-xim--conn 694 | (cl-return-from exwm-xim--init)) 695 | ;; Initialize atoms. 696 | (setq exwm-xim--@server (exwm--intern-atom "@server=exwm-xim") 697 | exwm-xim--LOCALES (exwm--intern-atom "LOCALES") 698 | exwm-xim--TRANSPORT (exwm--intern-atom "TRANSPORT") 699 | exwm-xim--XIM_SERVERS (exwm--intern-atom "XIM_SERVERS") 700 | exwm-xim--_XIM_PROTOCOL (exwm--intern-atom "_XIM_PROTOCOL") 701 | exwm-xim--_XIM_XCONNECT (exwm--intern-atom "_XIM_XCONNECT")) 702 | ;; Create a new connection and event window. 703 | (setq exwm-xim--conn (xcb:connect) 704 | exwm-xim--event-xwin (xcb:generate-id exwm-xim--conn)) 705 | (set-process-query-on-exit-flag (slot-value exwm-xim--conn 'process) nil) 706 | ;; Initialize xcb:keysyms module. 707 | (xcb:keysyms:init exwm-xim--conn) 708 | ;; Listen to SelectionRequest event for connection establishment. 709 | (xcb:+event exwm-xim--conn 'xcb:SelectionRequest 710 | #'exwm-xim--on-SelectionRequest) 711 | ;; Listen to ClientMessage event on IMS window for new XIM connection. 712 | (xcb:+event exwm-xim--conn 'xcb:ClientMessage #'exwm-xim--on-ClientMessage-0) 713 | ;; Listen to DestroyNotify event to do cleanups. 714 | (xcb:+event exwm-xim--conn 'xcb:DestroyNotify #'exwm-xim--on-DestroyNotify) 715 | ;; Create the event window. 716 | (xcb:+request exwm-xim--conn 717 | (make-instance 'xcb:CreateWindow 718 | :depth 0 719 | :wid exwm-xim--event-xwin 720 | :parent exwm--root 721 | :x 0 722 | :y 0 723 | :width 1 724 | :height 1 725 | :border-width 0 726 | :class xcb:WindowClass:InputOutput 727 | :visual 0 728 | :value-mask xcb:CW:OverrideRedirect 729 | :override-redirect 1)) 730 | ;; Set the selection owner. 731 | (xcb:+request exwm-xim--conn 732 | (make-instance 'xcb:SetSelectionOwner 733 | :owner exwm-xim--event-xwin 734 | :selection exwm-xim--@server 735 | :time xcb:Time:CurrentTime)) 736 | ;; Set XIM_SERVERS property on the root window. 737 | (xcb:+request exwm-xim--conn 738 | (make-instance 'xcb:ChangeProperty 739 | :mode xcb:PropMode:Prepend 740 | :window exwm--root 741 | :property exwm-xim--XIM_SERVERS 742 | :type xcb:Atom:ATOM 743 | :format 32 744 | :data-len 1 745 | :data (funcall (if xcb:lsb 746 | #'xcb:-pack-u4-lsb 747 | #'xcb:-pack-u4) 748 | exwm-xim--@server))) 749 | (xcb:flush exwm-xim--conn)) 750 | 751 | (cl-defun exwm-xim--exit () 752 | "Exit the XIM module." 753 | (exwm--log) 754 | ;; Close IMS communication connections. 755 | (mapc (lambda (i) 756 | (when (vectorp i) 757 | (when (slot-value (elt i 0) 'connected) 758 | (xcb:disconnect (elt i 0))))) 759 | exwm-xim--server-client-plist) 760 | ;; Close the IMS connection. 761 | (unless (and exwm-xim--conn 762 | (slot-value exwm-xim--conn 'connected)) 763 | (cl-return-from exwm-xim--exit)) 764 | ;; Remove exwm-xim from XIM_SERVERS. 765 | (let ((reply (xcb:+request-unchecked+reply exwm-xim--conn 766 | (make-instance 'xcb:GetProperty 767 | :delete 1 768 | :window exwm--root 769 | :property exwm-xim--XIM_SERVERS 770 | :type xcb:Atom:ATOM 771 | :long-offset 0 772 | :long-length 1000))) 773 | unpacked-reply pack unpack) 774 | (unless reply 775 | (cl-return-from exwm-xim--exit)) 776 | (setq reply (slot-value reply 'value)) 777 | (unless (> (length reply) 4) 778 | (cl-return-from exwm-xim--exit)) 779 | (setq reply (vconcat reply) 780 | pack (if xcb:lsb #'xcb:-pack-u4-lsb #'xcb:-pack-u4) 781 | unpack (if xcb:lsb #'xcb:-unpack-u4-lsb #'xcb:-unpack-u4)) 782 | (dotimes (i (/ (length reply) 4)) 783 | (push (funcall unpack reply (* i 4)) unpacked-reply)) 784 | (setq unpacked-reply (delq exwm-xim--@server unpacked-reply) 785 | reply (mapcar pack unpacked-reply)) 786 | (xcb:+request exwm-xim--conn 787 | (make-instance 'xcb:ChangeProperty 788 | :mode xcb:PropMode:Replace 789 | :window exwm--root 790 | :property exwm-xim--XIM_SERVERS 791 | :type xcb:Atom:ATOM 792 | :format 32 793 | :data-len (length reply) 794 | :data reply)) 795 | (xcb:flush exwm-xim--conn)) 796 | (xcb:disconnect exwm-xim--conn) 797 | (setq exwm-xim--conn nil)) 798 | 799 | ;;;###autoload 800 | (define-minor-mode exwm-xim-mode 801 | "Toggle EXWM XIM support." 802 | :global t 803 | :group 'exwm 804 | (exwm--global-minor-mode-body xim)) 805 | 806 | (provide 'exwm-xim) 807 | ;;; exwm-xim.el ends here 808 | -------------------------------------------------------------------------------- /exwm-xsettings.el: -------------------------------------------------------------------------------- 1 | ;;; exwm-xsettings.el --- XSETTINGS Module for EXWM -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2022-2025 Free Software Foundation, Inc. 4 | 5 | ;; Author: Steven Allen 6 | 7 | ;; This file is part of GNU Emacs. 8 | 9 | ;; GNU Emacs is free software: you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation, either version 3 of the License, or 12 | ;; (at your option) any later version. 13 | 14 | ;; GNU Emacs is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with GNU Emacs. If not, see . 21 | 22 | ;;; Commentary: 23 | 24 | ;; Implements the XSETTINGS protocol, allowing Emacs to manage the system theme, 25 | ;; fonts, icons, etc. 26 | ;; 27 | ;; This package can be configured as follows: 28 | ;; 29 | ;; (setq exwm-xsettings-theme '("Adwaita" . "Adwaita-dark") ;; light/dark 30 | ;; exwm-xsettings `(("Xft/HintStyle" . "hintslight") 31 | ;; ("Xft/RGBA" . "rgb") 32 | ;; ("Xft/lcdfilter" . "lcddefault") 33 | ;; ("Xft/Antialias" . 1) 34 | ;; ;; DPI is in 1024ths of an inch, so this is a DPI of 35 | ;; ;; 144, equivalent to ;; a scaling factor of 1.5 36 | ;; ;; (144 = 1.5 * 96). 37 | ;; ("Xft/DPI" . ,(* 144 1024)) 38 | ;; ("Xft/Hinting" . 1))) 39 | ;; (exwm-xsettings-mode 1) 40 | ;; 41 | ;; To modify these settings at runtime, customize them with 42 | ;; `custom-set-variables' or `setopt' (Emacs 29+). E.g., the following will 43 | ;; immediately change the icon theme to "Papirus" at runtime, even in running 44 | ;; applications: 45 | ;; 46 | ;; (setopt exwm-xsettings-icon-theme "Papirus") 47 | 48 | ;;; Code: 49 | 50 | (require 'xcb-ewmh) 51 | (require 'xcb-xsettings) 52 | (require 'exwm-core) 53 | 54 | (defgroup exwm-xsettings nil 55 | "XSETTINGS." 56 | :group 'exwm) 57 | 58 | (defvar exwm-xsettings--connection nil) 59 | (defvar exwm-xsettings--XSETTINGS_SETTINGS-atom nil) 60 | (defvar exwm-xsettings--XSETTINGS_S0-atom nil) 61 | (defvar exwm-xsettings--selection-owner-window nil) 62 | (defvar exwm-xsettings--serial 0) 63 | 64 | ;;;###autoload 65 | (define-minor-mode exwm-xsettings-mode 66 | "Toggle EXWM xsettings support." 67 | :global t 68 | :group 'exwm-xsettings 69 | (exwm--global-minor-mode-body xsettings)) 70 | 71 | (defun exwm-xsettings--rgba-match (_widget value) 72 | "Return t if VALUE is a valid RGBA color." 73 | (and (numberp value) (<= 0 value 1))) 74 | 75 | (defun exwm-xsettings--custom-set (symbol value) 76 | "Setter used by `exwm-xsettings' customization options. 77 | 78 | SYMBOL is the setting being updated and VALUE is the new value." 79 | (set-default-toplevel-value symbol value) 80 | (when exwm-xsettings-mode (exwm-xsettings--update-settings))) 81 | 82 | (defcustom exwm-xsettings nil 83 | "Alist of custom XSETTINGS. 84 | These settings take precedence over `exwm-xsettings-theme' and 85 | `exwm-xsettings-icon-theme'." 86 | :type '(alist :key-type (string :tag "Name") 87 | :value-type (choice :tag "Value" 88 | (string :tag "String") 89 | (integer :tag "Integer") 90 | (list :tag "Color" 91 | (number :tag "Red" 92 | :type-error 93 | "This field should contain a number between 0 and 1." 94 | :match exwm-xsettings--rgba-match) 95 | (number :tag "Green" 96 | :type-error 97 | "This field should contain a number between 0 and 1." 98 | :match exwm-xsettings--rgba-match) 99 | (number :tag "Blue" 100 | :type-error 101 | "This field should contain a number between 0 and 1." 102 | :match exwm-xsettings--rgba-match) 103 | (number :tag "Alpha" 104 | :type-error 105 | "This field should contain a number between 0 and 1." 106 | :match exwm-xsettings--rgba-match 107 | :value 1.0)))) 108 | :initialize #'custom-initialize-default 109 | :set #'exwm-xsettings--custom-set) 110 | 111 | (defcustom exwm-xsettings-theme nil 112 | "The system-wide theme." 113 | :type '(choice (string :tag "Theme") 114 | (cons (string :tag "Light Theme") 115 | (string :tag "Dark Theme"))) 116 | :initialize #'custom-initialize-default 117 | :set #'exwm-xsettings--custom-set) 118 | 119 | (defcustom exwm-xsettings-icon-theme nil 120 | "The system-wide icon theme." 121 | :type '(choice (string :tag "Icon Theme") 122 | (cons (string :tag "Light Icon Theme") 123 | (string :tag "Dark Icon Theme"))) 124 | :initialize #'custom-initialize-default 125 | :set #'exwm-xsettings--custom-set) 126 | 127 | (defun exwm-xsettings--pick-theme (theme) 128 | "Pick a light or dark theme from the given THEME. 129 | If THEME is a string, it's returned directly. 130 | If THEME is a cons of (LIGHT . DARK), the appropriate theme is picked based on 131 | the default face's background color." 132 | (pcase theme 133 | ((cl-type string) theme) 134 | (`(,(cl-type string) . ,(cl-type string)) 135 | (if (color-dark-p (color-name-to-rgb (face-background 'default))) 136 | (cdr theme) (car theme))) 137 | (_ (error "Expected theme to be a string or a pair of strings")))) 138 | 139 | (defun exwm-xsettings--get-settings () 140 | "Get the current settings. 141 | Combines `exwm-xsettings', `exwm-xsettings-theme' (if set), and 142 | `exwm-xsettings-icon-theme' (if set)." 143 | (cl-remove-duplicates 144 | (append 145 | exwm-xsettings 146 | (when exwm-xsettings-theme 147 | (list (cons "Net/ThemeName" (exwm-xsettings--pick-theme exwm-xsettings-theme)))) 148 | (when exwm-xsettings-icon-theme 149 | (list (cons "Net/IconThemeName" (exwm-xsettings--pick-theme exwm-xsettings-icon-theme))))) 150 | :key 'car 151 | :test 'string=)) 152 | 153 | (defun exwm-xsettings--make-settings (settings serial) 154 | "Construct a new settings object. 155 | SETTINGS is an alist of key/value pairs. 156 | SERIAL is a sequence number." 157 | (make-instance 'xcb:xsettings:-Settings 158 | :byte-order (if xcb:lsb 0 1) 159 | :serial serial 160 | :settings-len (length settings) 161 | :settings 162 | (mapcar 163 | (lambda (prop) 164 | (let* ((name (car prop)) 165 | (value (cdr prop)) 166 | (common (list :name name 167 | :name-len (length name) 168 | :last-change-serial serial))) 169 | (pcase value 170 | ((cl-type string) 171 | (apply #'make-instance 'xcb:xsettings:-SETTING_STRING 172 | :value-len (length value) 173 | :value value 174 | common)) 175 | ((cl-type integer) 176 | (apply #'make-instance 'xcb:xsettings:-SETTING_INTEGER 177 | :value value common)) 178 | ((and (cl-type list) (app length (or 3 4))) 179 | ;; Convert from RGB(A) to 16bit integers. 180 | (setq value (mapcar (lambda (x) (round (* x #xffff))) value)) 181 | (apply #'make-instance 'xcb:xsettings:-SETTING_COLOR 182 | :red (pop value) 183 | :green (pop value) 184 | :blue (pop value) 185 | :alpha (or (pop value) #xffff))) 186 | (_ (error "Setting value must be a string, integer, or length 3-4 list"))))) 187 | settings))) 188 | 189 | (defun exwm-xsettings--update-settings () 190 | "Update the xsettings." 191 | (when exwm-xsettings--connection 192 | (setq exwm-xsettings--serial (1+ exwm-xsettings--serial)) 193 | (let* ((settings (exwm-xsettings--get-settings)) 194 | (bytes (xcb:marshal (exwm-xsettings--make-settings settings exwm-xsettings--serial)))) 195 | (xcb:+request exwm-xsettings--connection 196 | (make-instance 'xcb:ChangeProperty 197 | :mode xcb:PropMode:Replace 198 | :window exwm-xsettings--selection-owner-window 199 | :property exwm-xsettings--XSETTINGS_SETTINGS-atom 200 | :type exwm-xsettings--XSETTINGS_SETTINGS-atom 201 | :format 8 202 | :data-len (length bytes) 203 | :data bytes))) 204 | (xcb:flush exwm-xsettings--connection))) 205 | 206 | (defun exwm-xsettings--on-theme-change (&rest _) 207 | "Called when the Emacs theme is changed." 208 | ;; We only bother updating the xsettings if changing the theme could effect 209 | ;; the settings. 210 | (when (or (consp exwm-xsettings-theme) (consp exwm-xsettings-icon-theme)) 211 | (exwm-xsettings--update-settings))) 212 | 213 | (defun exwm-xsettings--on-SelectionClear (_data _synthetic) 214 | "Called when another xsettings daemon takes over." 215 | (exwm--log "XSETTINGS manager has been replaced.") 216 | (exwm-xsettings--exit)) 217 | 218 | (cl-defun exwm-xsettings--init () 219 | "Initialize the XSETTINGS module." 220 | (exwm--log) 221 | 222 | ;; idempotent initialization 223 | (when exwm-xsettings--connection 224 | (cl-return-from exwm-xsettings--init)) 225 | 226 | ;; Connect 227 | (setq exwm-xsettings--connection (xcb:connect)) 228 | (set-process-query-on-exit-flag (slot-value exwm-xsettings--connection 229 | 'process) 230 | nil) 231 | 232 | ;; Intern the atoms. 233 | (setq exwm-xsettings--XSETTINGS_SETTINGS-atom 234 | (exwm--intern-atom "_XSETTINGS_SETTINGS" exwm-xsettings--connection) 235 | 236 | exwm-xsettings--XSETTINGS_S0-atom 237 | (exwm--intern-atom "_XSETTINGS_S0" exwm-xsettings--connection)) 238 | 239 | ;; Detect running XSETTINGS managers. 240 | (with-slots (owner) 241 | (xcb:+request-unchecked+reply exwm-xsettings--connection 242 | (make-instance 'xcb:GetSelectionOwner 243 | :selection exwm-xsettings--XSETTINGS_S0-atom)) 244 | (when (/= owner xcb:Window:None) 245 | (xcb:disconnect exwm-xsettings--connection) 246 | (setq exwm-xsettings--connection nil) 247 | (warn "[EXWM] Other XSETTINGS manager detected") 248 | (cl-return-from exwm-xsettings--init))) 249 | 250 | (let ((id(xcb:generate-id exwm-xsettings--connection))) 251 | (setq exwm-xsettings--selection-owner-window id) 252 | 253 | ;; Create a settings window. 254 | (xcb:+request exwm-xsettings--connection 255 | (make-instance 'xcb:CreateWindow 256 | :wid id 257 | :parent exwm--root 258 | :class xcb:WindowClass:InputOnly 259 | :x 0 260 | :y 0 261 | :width 1 262 | :height 1 263 | :border-width 0 264 | :depth 0 265 | :visual 0 266 | :value-mask xcb:CW:OverrideRedirect 267 | :override-redirect 1)) 268 | 269 | ;; Set _NET_WM_NAME. 270 | (xcb:+request exwm-xsettings--connection 271 | (make-instance 'xcb:ewmh:set-_NET_WM_NAME 272 | :window id 273 | :data "EXWM: exwm-xsettings--selection-owner-window")) 274 | 275 | ;; Apply the XSETTINGS properties. 276 | (exwm-xsettings--update-settings) 277 | 278 | ;; Take ownership and notify. 279 | (xcb:+request exwm-xsettings--connection 280 | (make-instance 'xcb:SetSelectionOwner 281 | :owner id 282 | :selection exwm-xsettings--XSETTINGS_S0-atom 283 | :time xcb:Time:CurrentTime)) 284 | (xcb:+request exwm-xsettings--connection 285 | (make-instance 'xcb:SendEvent 286 | :propagate 0 287 | :destination exwm--root 288 | :event-mask xcb:EventMask:StructureNotify 289 | :event (xcb:marshal 290 | (make-instance 'xcb:icccm:-ManagerSelection 291 | :window exwm--root 292 | :time xcb:Time:CurrentTime 293 | :selection exwm-xsettings--XSETTINGS_S0-atom 294 | :owner id) 295 | exwm-xsettings--connection))) 296 | 297 | ;; Detect loss of XSETTINGS ownership. 298 | (xcb:+event exwm-xsettings--connection 'xcb:SelectionClear 299 | #'exwm-xsettings--on-SelectionClear) 300 | 301 | (xcb:flush exwm-xsettings--connection)) 302 | 303 | ;; Update the xsettings if/when the theme changes. 304 | (add-hook 'enable-theme-functions #'exwm-xsettings--on-theme-change) 305 | (add-hook 'disable-theme-functions #'exwm-xsettings--on-theme-change)) 306 | 307 | (defun exwm-xsettings--exit () 308 | "Exit the XSETTINGS module." 309 | (exwm--log) 310 | 311 | (when exwm-xsettings--connection 312 | (remove-hook 'enable-theme-functions #'exwm-xsettings--on-theme-change) 313 | (remove-hook 'disable-theme-functions #'exwm-xsettings--on-theme-change) 314 | 315 | (xcb:disconnect exwm-xsettings--connection) 316 | 317 | (setq exwm-xsettings--connection nil 318 | exwm-xsettings--XSETTINGS_SETTINGS-atom nil 319 | exwm-xsettings--XSETTINGS_S0-atom nil 320 | exwm-xsettings--selection-owner-window nil))) 321 | 322 | (provide 'exwm-xsettings) 323 | ;;; exwm-xsettings.el ends here 324 | --------------------------------------------------------------------------------