├── .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 |
--------------------------------------------------------------------------------