├── .gitignore
├── COPYING
├── Cargo.lock
├── Cargo.toml
├── README.md
├── assets
└── config.toml
└── src
├── auth
├── auth.rs
├── internal.rs
├── mod.rs
└── pam.rs
├── config
├── auth.rs
├── config.rs
├── interface.rs
├── locker.rs
├── mod.rs
├── saver.rs
└── timer.rs
├── error.rs
├── interface.rs
├── locker
├── display.rs
├── locker.rs
├── mod.rs
└── window.rs
├── main.rs
├── platform
├── display.rs
├── keyboard.rs
├── mod.rs
└── window.rs
├── preview
├── mod.rs
├── preview.rs
└── window.rs
├── saver.rs
└── timer.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
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 |
635 | Copyright (C)
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 | Copyright (C)
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 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | [[package]]
4 | name = "aho-corasick"
5 | version = "0.7.8"
6 | source = "registry+https://github.com/rust-lang/crates.io-index"
7 | dependencies = [
8 | "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
9 | ]
10 |
11 | [[package]]
12 | name = "ansi_term"
13 | version = "0.11.0"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | dependencies = [
16 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
17 | ]
18 |
19 | [[package]]
20 | name = "app_dirs"
21 | version = "1.2.1"
22 | source = "registry+https://github.com/rust-lang/crates.io-index"
23 | dependencies = [
24 | "ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
25 | "shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
26 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
27 | "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
28 | ]
29 |
30 | [[package]]
31 | name = "atty"
32 | version = "0.2.14"
33 | source = "registry+https://github.com/rust-lang/crates.io-index"
34 | dependencies = [
35 | "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
36 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
37 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
38 | ]
39 |
40 | [[package]]
41 | name = "autocfg"
42 | version = "0.1.7"
43 | source = "registry+https://github.com/rust-lang/crates.io-index"
44 |
45 | [[package]]
46 | name = "bitflags"
47 | version = "1.2.1"
48 | source = "registry+https://github.com/rust-lang/crates.io-index"
49 |
50 | [[package]]
51 | name = "c2-chacha"
52 | version = "0.2.3"
53 | source = "registry+https://github.com/rust-lang/crates.io-index"
54 | dependencies = [
55 | "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
56 | ]
57 |
58 | [[package]]
59 | name = "cfg-if"
60 | version = "0.1.10"
61 | source = "registry+https://github.com/rust-lang/crates.io-index"
62 |
63 | [[package]]
64 | name = "clap"
65 | version = "2.33.0"
66 | source = "registry+https://github.com/rust-lang/crates.io-index"
67 | dependencies = [
68 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
69 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
70 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
71 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
72 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
73 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
74 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
75 | ]
76 |
77 | [[package]]
78 | name = "crossbeam-channel"
79 | version = "0.4.0"
80 | source = "registry+https://github.com/rust-lang/crates.io-index"
81 | dependencies = [
82 | "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
83 | ]
84 |
85 | [[package]]
86 | name = "crossbeam-utils"
87 | version = "0.7.0"
88 | source = "registry+https://github.com/rust-lang/crates.io-index"
89 | dependencies = [
90 | "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
91 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
92 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
93 | ]
94 |
95 | [[package]]
96 | name = "dbus"
97 | version = "0.8.1"
98 | source = "registry+https://github.com/rust-lang/crates.io-index"
99 | dependencies = [
100 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
101 | "libdbus-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
102 | ]
103 |
104 | [[package]]
105 | name = "env_logger"
106 | version = "0.7.1"
107 | source = "registry+https://github.com/rust-lang/crates.io-index"
108 | dependencies = [
109 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
110 | "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
111 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
112 | "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
113 | "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
114 | ]
115 |
116 | [[package]]
117 | name = "getrandom"
118 | version = "0.1.14"
119 | source = "registry+https://github.com/rust-lang/crates.io-index"
120 | dependencies = [
121 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
122 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
123 | "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
124 | ]
125 |
126 | [[package]]
127 | name = "hermit-abi"
128 | version = "0.1.6"
129 | source = "registry+https://github.com/rust-lang/crates.io-index"
130 | dependencies = [
131 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
132 | ]
133 |
134 | [[package]]
135 | name = "humantime"
136 | version = "1.3.0"
137 | source = "registry+https://github.com/rust-lang/crates.io-index"
138 | dependencies = [
139 | "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
140 | ]
141 |
142 | [[package]]
143 | name = "json"
144 | version = "0.12.1"
145 | source = "registry+https://github.com/rust-lang/crates.io-index"
146 |
147 | [[package]]
148 | name = "lazy_static"
149 | version = "1.4.0"
150 | source = "registry+https://github.com/rust-lang/crates.io-index"
151 |
152 | [[package]]
153 | name = "libc"
154 | version = "0.2.66"
155 | source = "registry+https://github.com/rust-lang/crates.io-index"
156 |
157 | [[package]]
158 | name = "libdbus-sys"
159 | version = "0.2.1"
160 | source = "registry+https://github.com/rust-lang/crates.io-index"
161 | dependencies = [
162 | "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
163 | ]
164 |
165 | [[package]]
166 | name = "log"
167 | version = "0.4.8"
168 | source = "registry+https://github.com/rust-lang/crates.io-index"
169 | dependencies = [
170 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
171 | ]
172 |
173 | [[package]]
174 | name = "memchr"
175 | version = "2.3.0"
176 | source = "registry+https://github.com/rust-lang/crates.io-index"
177 |
178 | [[package]]
179 | name = "ole32-sys"
180 | version = "0.2.0"
181 | source = "registry+https://github.com/rust-lang/crates.io-index"
182 | dependencies = [
183 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
184 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
185 | ]
186 |
187 | [[package]]
188 | name = "pam-sys"
189 | version = "0.5.6"
190 | source = "registry+https://github.com/rust-lang/crates.io-index"
191 | dependencies = [
192 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
193 | ]
194 |
195 | [[package]]
196 | name = "pkg-config"
197 | version = "0.3.17"
198 | source = "registry+https://github.com/rust-lang/crates.io-index"
199 |
200 | [[package]]
201 | name = "ppv-lite86"
202 | version = "0.2.6"
203 | source = "registry+https://github.com/rust-lang/crates.io-index"
204 |
205 | [[package]]
206 | name = "quick-error"
207 | version = "1.2.3"
208 | source = "registry+https://github.com/rust-lang/crates.io-index"
209 |
210 | [[package]]
211 | name = "rand"
212 | version = "0.7.3"
213 | source = "registry+https://github.com/rust-lang/crates.io-index"
214 | dependencies = [
215 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
216 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
217 | "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
218 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
219 | "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
220 | ]
221 |
222 | [[package]]
223 | name = "rand_chacha"
224 | version = "0.2.1"
225 | source = "registry+https://github.com/rust-lang/crates.io-index"
226 | dependencies = [
227 | "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
228 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
229 | ]
230 |
231 | [[package]]
232 | name = "rand_core"
233 | version = "0.5.1"
234 | source = "registry+https://github.com/rust-lang/crates.io-index"
235 | dependencies = [
236 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
237 | ]
238 |
239 | [[package]]
240 | name = "rand_hc"
241 | version = "0.2.0"
242 | source = "registry+https://github.com/rust-lang/crates.io-index"
243 | dependencies = [
244 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
245 | ]
246 |
247 | [[package]]
248 | name = "regex"
249 | version = "1.3.4"
250 | source = "registry+https://github.com/rust-lang/crates.io-index"
251 | dependencies = [
252 | "aho-corasick 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)",
253 | "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
254 | "regex-syntax 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)",
255 | "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
256 | ]
257 |
258 | [[package]]
259 | name = "regex-syntax"
260 | version = "0.6.14"
261 | source = "registry+https://github.com/rust-lang/crates.io-index"
262 |
263 | [[package]]
264 | name = "screenruster"
265 | version = "0.2.1"
266 | dependencies = [
267 | "app_dirs 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
268 | "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
269 | "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
270 | "dbus 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
271 | "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
272 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
273 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
274 | "pam-sys 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
275 | "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
276 | "screenruster-saver 0.2.2",
277 | "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
278 | "users 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
279 | "xcb 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
280 | "xcb-util 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
281 | "xkb 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
282 | ]
283 |
284 | [[package]]
285 | name = "screenruster-saver"
286 | version = "0.2.2"
287 | dependencies = [
288 | "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
289 | "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
290 | "json 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
291 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
292 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
293 | ]
294 |
295 | [[package]]
296 | name = "serde"
297 | version = "1.0.104"
298 | source = "registry+https://github.com/rust-lang/crates.io-index"
299 |
300 | [[package]]
301 | name = "shell32-sys"
302 | version = "0.1.2"
303 | source = "registry+https://github.com/rust-lang/crates.io-index"
304 | dependencies = [
305 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
306 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
307 | ]
308 |
309 | [[package]]
310 | name = "strsim"
311 | version = "0.8.0"
312 | source = "registry+https://github.com/rust-lang/crates.io-index"
313 |
314 | [[package]]
315 | name = "termcolor"
316 | version = "1.1.0"
317 | source = "registry+https://github.com/rust-lang/crates.io-index"
318 | dependencies = [
319 | "winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
320 | ]
321 |
322 | [[package]]
323 | name = "textwrap"
324 | version = "0.11.0"
325 | source = "registry+https://github.com/rust-lang/crates.io-index"
326 | dependencies = [
327 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
328 | ]
329 |
330 | [[package]]
331 | name = "thread_local"
332 | version = "1.0.1"
333 | source = "registry+https://github.com/rust-lang/crates.io-index"
334 | dependencies = [
335 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
336 | ]
337 |
338 | [[package]]
339 | name = "toml"
340 | version = "0.5.6"
341 | source = "registry+https://github.com/rust-lang/crates.io-index"
342 | dependencies = [
343 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
344 | ]
345 |
346 | [[package]]
347 | name = "unicode-width"
348 | version = "0.1.7"
349 | source = "registry+https://github.com/rust-lang/crates.io-index"
350 |
351 | [[package]]
352 | name = "users"
353 | version = "0.9.1"
354 | source = "registry+https://github.com/rust-lang/crates.io-index"
355 | dependencies = [
356 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
357 | ]
358 |
359 | [[package]]
360 | name = "vec_map"
361 | version = "0.8.1"
362 | source = "registry+https://github.com/rust-lang/crates.io-index"
363 |
364 | [[package]]
365 | name = "wasi"
366 | version = "0.9.0+wasi-snapshot-preview1"
367 | source = "registry+https://github.com/rust-lang/crates.io-index"
368 |
369 | [[package]]
370 | name = "winapi"
371 | version = "0.2.8"
372 | source = "registry+https://github.com/rust-lang/crates.io-index"
373 |
374 | [[package]]
375 | name = "winapi"
376 | version = "0.3.8"
377 | source = "registry+https://github.com/rust-lang/crates.io-index"
378 | dependencies = [
379 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
380 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
381 | ]
382 |
383 | [[package]]
384 | name = "winapi-build"
385 | version = "0.1.1"
386 | source = "registry+https://github.com/rust-lang/crates.io-index"
387 |
388 | [[package]]
389 | name = "winapi-i686-pc-windows-gnu"
390 | version = "0.4.0"
391 | source = "registry+https://github.com/rust-lang/crates.io-index"
392 |
393 | [[package]]
394 | name = "winapi-util"
395 | version = "0.1.3"
396 | source = "registry+https://github.com/rust-lang/crates.io-index"
397 | dependencies = [
398 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
399 | ]
400 |
401 | [[package]]
402 | name = "winapi-x86_64-pc-windows-gnu"
403 | version = "0.4.0"
404 | source = "registry+https://github.com/rust-lang/crates.io-index"
405 |
406 | [[package]]
407 | name = "xcb"
408 | version = "0.9.0"
409 | source = "registry+https://github.com/rust-lang/crates.io-index"
410 | dependencies = [
411 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
412 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
413 | ]
414 |
415 | [[package]]
416 | name = "xcb-util"
417 | version = "0.3.0"
418 | source = "registry+https://github.com/rust-lang/crates.io-index"
419 | dependencies = [
420 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
421 | "xcb 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
422 | ]
423 |
424 | [[package]]
425 | name = "xdg"
426 | version = "2.2.0"
427 | source = "registry+https://github.com/rust-lang/crates.io-index"
428 |
429 | [[package]]
430 | name = "xkb"
431 | version = "0.2.1"
432 | source = "registry+https://github.com/rust-lang/crates.io-index"
433 | dependencies = [
434 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
435 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
436 | "xcb 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
437 | "xkbcommon-sys 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
438 | ]
439 |
440 | [[package]]
441 | name = "xkbcommon-sys"
442 | version = "0.7.4"
443 | source = "registry+https://github.com/rust-lang/crates.io-index"
444 | dependencies = [
445 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
446 | "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
447 | ]
448 |
449 | [metadata]
450 | "checksum aho-corasick 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "743ad5a418686aad3b87fd14c43badd828cf26e214a00f92a384291cf22e1811"
451 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
452 | "checksum app_dirs 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e73a24bad9bd6a94d6395382a6c69fe071708ae4409f763c5475e14ee896313d"
453 | "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
454 | "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
455 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
456 | "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
457 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
458 | "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
459 | "checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c"
460 | "checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4"
461 | "checksum dbus 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2fd043051adb767dd40f869e6034c8ea4d3a0a719e78d4058f705a8038649059"
462 | "checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
463 | "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
464 | "checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772"
465 | "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
466 | "checksum json 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9a38661a28126f8621fb246611288ae28935ddf180f5e21f2d0fbfe5e4131dbe"
467 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
468 | "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
469 | "checksum libdbus-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0"
470 | "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
471 | "checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223"
472 | "checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c"
473 | "checksum pam-sys 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "cd4858311a097f01a0006ef7d0cd50bca81ec430c949d7bf95cbefd202282434"
474 | "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
475 | "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
476 | "checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
477 | "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
478 | "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
479 | "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
480 | "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
481 | "checksum regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8"
482 | "checksum regex-syntax 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "b28dfe3fe9badec5dbf0a79a9cccad2cfc2ab5484bdb3e44cbd1ae8b3ba2be06"
483 | "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
484 | "checksum shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ee04b46101f57121c9da2b151988283b6beb79b34f5bb29a58ee48cb695122c"
485 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
486 | "checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
487 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
488 | "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
489 | "checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
490 | "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
491 | "checksum users 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c72f4267aea0c3ec6d07eaabea6ead7c5ddacfafc5e22bcf8d186706851fb4cf"
492 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
493 | "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
494 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
495 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
496 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
497 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
498 | "checksum winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80"
499 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
500 | "checksum xcb 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "62056f63138b39116f82a540c983cc11f1c90cd70b3d492a70c25eaa50bd22a6"
501 | "checksum xcb-util 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43893e47f27bf7d81d489feef3a0e34a457e90bc314b7e74ad9bb3980e4c1c48"
502 | "checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57"
503 | "checksum xkb 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aec02bc5de902aa579f3d2f2c522edaf40fa42963cbaffe645b058ddcc68fdb2"
504 | "checksum xkbcommon-sys 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fa434980dca02ebf28795d71e570dbb78316d095a228707efd6117bf8246d78b"
505 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "screenruster"
3 | version = "0.2.1"
4 | edition = "2018"
5 |
6 | authors = ["meh. "]
7 | license = "GPL-3.0"
8 |
9 | description = "X11 screen saver and locker."
10 | repository = "https://github.com/meh/screenruster"
11 | keywords = ["x11", "graphics"]
12 |
13 | [features]
14 | default = ["auth-pam"]
15 |
16 | # Internal authorization support.
17 | auth-internal = []
18 |
19 | # PAM authorization support.
20 | auth-pam = ["pam"]
21 |
22 | # Respect PAM account management.
23 | auth-pam-accounts = ["auth-pam"]
24 |
25 | [dependencies]
26 | log = "0.4"
27 | env_logger = "0.7"
28 |
29 | clap = "2"
30 | app_dirs = "1.1"
31 | toml = "0.5"
32 | rand = "0.7"
33 | users = "0.9"
34 | dbus = "0.8"
35 | channel = { package = "crossbeam-channel", version = "0.4" }
36 | pam = { package = "pam-sys", optional = true, version = "0.5" }
37 |
38 | libc = "0.2"
39 | xcb = { version = "0.9", features = ["randr", "dpms", "xkb", "thread"] }
40 | xcbu = { package = "xcb-util", version = "0.3", features = ["icccm", "ewmh", "thread"] }
41 | xkb = { version = "0.2", features = ["x11"] }
42 | api = { package = "screenruster-saver", version = "0.2", default-features = false, path = "../saver" }
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ScreenRuster
2 | ============
3 | An X11 screen saver and locker.
4 |
5 | Installation
6 | ------------
7 | To install the daemon you will need a nightly Rust toolchain, then you can
8 | install it with Cargo:
9 |
10 | ```shell
11 | cargo install screenruster
12 | ```
13 |
14 | Once that's done you can create a configuration file at
15 | `$XDG_CONFIG_HOME/screenruster/config.toml` (`$XDG_CONFIG_HOME` defaults to
16 | `~/.config/`) or copy one from `assets/config.toml`.
17 |
18 | Screen savers have to be in `$PATH` and the executable name has to start with
19 | `screenruster-saver-`.
20 |
21 | A sample screen saver can be installed with Cargo:
22 |
23 | ```shell
24 | cargo install screenruster-saver-laughing_man
25 | ```
26 |
27 | The sample configuration file already has default settings for it.
28 |
29 | Usage
30 | -----
31 |
32 | First, start the daemon.
33 |
34 | ```
35 | screenruster daemon &
36 | ```
37 |
38 | Then if you want to activate the screen saver manually:
39 |
40 | ```
41 | screenruster activate
42 | ```
43 |
44 | Or if you want to lock manually:
45 |
46 | ```
47 | screenruster lock
48 | ```
49 |
50 | To unlock, simply type your password and press enter.
51 |
52 | Authorization
53 | =============
54 | Authorization is handled by various modules, each module tries to authenticate, the first
55 | successful authentication unlocks the screen.
56 |
57 | Internal
58 | --------
59 | The internal module uses a password specified in the configuration file, this
60 | was initially made for testing, and you should probably not use it.
61 |
62 | ```toml
63 | [auth.internal]
64 | password = "password"
65 | ```
66 |
67 | PAM
68 | ---
69 | This module uses the Pluggable Authentication Module for authentication, you
70 | will need to install a configuration file for it in `/etc/pam.d/screenruster`.
71 |
72 | ```config
73 | auth include system-auth
74 | ```
75 |
76 | If you want PAM account management to be respected, make sure to build with the
77 | `auth-pam-accounts` feature.
78 |
79 | Available savers
80 | ================
81 | This is a list of available screen savers that will be updated over time, if
82 | you made a saver and want it added here just open a pull request.
83 |
84 | - [Laughing Man](https://github.com/meh/screenruster-saver-laughing_man) from Ghost in the Shell: Stand Alone Complex
85 | - [XScreensaer & XLock](https://github.com/meh/screenruster-saver-hacks) support
86 |
87 | Why?
88 | ====
89 | Because I wanted to make a fancy screen saver and `xscreensaver` would have been
90 | a bigger pain to work with than to make this from scratch.
91 |
92 | Why should I use this instead of `xscreensaver`?
93 | ------------------------------------------------
94 | If you feel adventurous and want to use a screen locker that hasn't been tested
95 | as thorougly as `xscreensaver`.
96 |
97 | Or if you like any of the screen savers made for `ScreenRuster`.
98 |
99 | Theoretically `ScreenRuster` should be safer than `xscreensaver` because it's
100 | written in Rust and the architecture is slightly different from the one from
101 | `xscreensaver` further reducing the attack surface.
102 |
103 | Practically `ScreenRuster` is still young and there's some unsafe code around
104 | the usage of `PAM` (_but it's small enough to be safe_).
105 |
106 | Why should I use this instead of `gnome-screensaver`?
107 | -----------------------------------------------------
108 | Because `ScreenRuster` is still safer than `gnome-screensaver` and it
109 | implements the same DBus interface, so it should work even with the rest of the
110 | GNOME environment.
111 |
112 | But really, just use `xscreensaver` for now, anything is better than
113 | `gnome-screensaver`.
114 |
115 | Architecture
116 | ============
117 | The architecture loosely follows xscreensaver because it's the best approach,
118 | keep the screen locking simple and delegate the fancy graphics to a separate
119 | proccess, this has the nice property of making buggy savers not bring down the
120 | whole locking mechanism, thus protecting from vulnerabilities.
121 |
122 | The savers can be written in any language that can draw to an X11 window, parse
123 | and generate JSON, write to `stdout` and read from `stdin`.
124 |
125 | JSON is used for IPC between the daemon and the saver, the daemon writes to the
126 | saver process `stdin`, reads from the process `stdout` and forwards anything
127 | coming from `stderr` to allow for debugging or logging.
128 |
129 | The job of the saver is merely to do the rendering, this includes any fade
130 | in/out or dialog boxes, this further reduces the attack surface of the locker.
131 | _Note that the saver does not actually get the input, it just gets `Insert` or `Delete` events, so
132 | it can fill its dialog box._
133 |
134 | Protocol
135 | ========
136 | The protocol is line based, where each line contains a JSON encoded message,
137 | each message has a `type` field with the name of the message, the parameters
138 | are attributes on the same object.
139 |
140 | Requests
141 | --------
142 | Requests are messages sent from the daemon to the spawned saver process.
143 |
144 | ### Configuration
145 |
146 | The configuration request is part of the handshake and it's the first request sent when
147 | a process is spawned.
148 |
149 | The configuration is monolithic and managed by the daemon in a TOML file, the
150 | related TOML map is converted to JSON and sent to the saver.
151 |
152 | - `type` = `"config"`
153 | - `config` = `Object`
154 |
155 | ### Target
156 |
157 | The target request is part of the handshake and is the second request sent when
158 | a process is spawned.
159 |
160 | It contains the details required to get the X11 window, the display name, the
161 | screen number and the window `XID`.
162 |
163 | - `type` = `"target"`
164 | - `display` = `String`
165 | - `screen` = `Integer`
166 | - `window` = `Integer`
167 |
168 | ### Resize
169 |
170 | The resize request is sent when a locker window is resized, this can happen if
171 | XRandr is used to change resolution or rotate the screen.
172 |
173 | - `type` = `"resize"`
174 | - `width` = `Integer`
175 | - `height` = `Integer`
176 |
177 | ### Throttle
178 |
179 | The throttle request is sent when the saver should try and reduce power usage.
180 |
181 | - `type` = `"throttle"`
182 | - `throttle` = `Boolean`
183 |
184 | ### Blank
185 |
186 | The blank request is sent when the screen has been blanked or unblanked.
187 |
188 | - `type` = `"blank"`
189 | - `throttle` = `Boolean`
190 |
191 | ### Pointer
192 |
193 | The pointer request is sent when a pointer event on the saver window has happened.
194 |
195 | - `type` = `"pointer"`
196 | - `move` = `Object { x, y }`
197 | - `button` = `Object { x, y, button, press }`
198 |
199 | ### Password
200 |
201 | The password request is sent when any authorization related changes happened,
202 | this includes when characters are being inserted or deleted, the password is
203 | being checked or authorization failed or succeded.
204 |
205 | - `type` = `"password"`
206 | - `password` = `"insert"`, `"delete"`, `"reset"`, `"check"`, `"success"`, `"failure"`
207 |
208 | ### Start
209 |
210 | The start request is sent when the saver should start its rendering, this may
211 | include a fade in or other fancy graphics.
212 |
213 | - `type` = `"start"`
214 |
215 | ### Lock
216 |
217 | The lock request is sent when the screen has been locked, this is useful to
218 | render a notification that the screen is currently locked.
219 |
220 | - `type` = `"lock"`
221 |
222 | ### Stop
223 |
224 | The stop request is sent when the saver should stop its rendering, this may
225 | include a fade out or other fancy graphics.
226 |
227 | - `type` = `"stop"`
228 |
229 | Responses
230 | ---------
231 | Responses are messages sent from the spawned saver process to the daemon.
232 |
233 | ### Initialized
234 |
235 | The initialized response is sent after the handshake is done and the saver is
236 | ready to start, since fancy graphics may require loading textures and such, the
237 | saver is given some leeway to get ready to render.
238 |
239 | - `type` = `"initialized"`
240 |
241 | ### Started
242 |
243 | The started response is sent after a `start` request has been received and the
244 | saver started its rendering, it tells the daemon it can show the window.
245 |
246 | - `type` = `"started"`
247 |
248 | ### Stopped
249 |
250 | The stopped response is sent after a `stop` request has been received and the
251 | saver stopped its rendering, it tells the daemon it can hide the window.
252 |
--------------------------------------------------------------------------------
/assets/config.toml:
--------------------------------------------------------------------------------
1 | # X11 related settings.
2 | [locker]
3 | # The name of the display.
4 | # display = ":0.0"
5 |
6 | # Whether to take control of DPMS settings or not.
7 | dpms = true
8 |
9 | # What to do on suspension.
10 | #
11 | # - "ignore" will do nothing.
12 | # - "use-system-time" will try to use system time to correct the monotonic
13 | # timers.
14 | # - "activate" will activate the screen saver.
15 | # - "lock" will lock the screen.
16 | on-suspend = "use-system-time"
17 |
18 | # DBus related settings.
19 | [server]
20 | # A list of types of messages to ignore.
21 | #
22 | # - "inhibit" will ignore screen saver inhibition requests.
23 | # - "throttle" will ignore screen saver throttling requests.
24 | # - "suspend" will ignore timer suspension requests.
25 | #
26 | # ignore = ["inhibit", "throttle"]
27 |
28 | [timer]
29 | # How many seconds to wait between each heartbeat.
30 | #
31 | # Heartbeats are used to sanitize X11 windows and other things.
32 | beat = 10
33 |
34 | # How many seconds to wait before starting the screen saver.
35 | timeout = "5:00"
36 |
37 | # How many seconds to wait before locking the screen after the screen saver has
38 | # been started.
39 | lock = false
40 |
41 | # How many seconds to wait before blanking the screen.
42 | #
43 | # `false` makes it never blank.
44 | blank = false
45 |
46 | # General screen saver settings.
47 | [saver]
48 | # How many seconds before an unresponsive saver is killed.
49 | timeout = 5
50 |
51 | # A list of screen savers to use.
52 | use = ["laughing_man"]
53 |
54 | # Whether to always throttle or not.
55 | throttle = false
56 |
57 | # Settings for a specific screen saver.
58 | [saver.laughing_man]
59 | blur = { max = 1.2, step = 0.01, count = 4 }
60 | man = { rotate = 0.005, scale = 400.0 }
61 |
--------------------------------------------------------------------------------
/src/auth/auth.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::thread;
19 | use std::ops::Deref;
20 | use channel::{self, Receiver, Sender, SendError};
21 |
22 | use users;
23 | use log::warn;
24 |
25 | use crate::error;
26 | use crate::config;
27 | use super::Authenticate;
28 |
29 | pub struct Auth {
30 | receiver: Receiver,
31 | sender: Sender,
32 | }
33 |
34 | #[derive(Clone, Debug)]
35 | pub enum Request {
36 | Authenticate(String),
37 | }
38 |
39 | #[derive(Clone, Debug)]
40 | pub enum Response {
41 | Success,
42 | Failure,
43 | }
44 |
45 | impl Auth {
46 | pub fn spawn(config: config::Auth) -> error::Result {
47 | let user = users::get_current_username().ok_or(error::Auth::UnknownUser)?;
48 | let mut methods = Vec::>::new();
49 |
50 | #[cfg(feature = "auth-internal")]
51 | methods.push(box super::internal::new(config.get("internal"))?);
52 |
53 | #[cfg(feature = "auth-pam")]
54 | methods.push(Box::new(super::pam::new(config.get("pam"))?));
55 |
56 | let (sender, i_receiver) = channel::unbounded();
57 | let (i_sender, receiver) = channel::unbounded();
58 |
59 | thread::spawn(move || {
60 | 'main: while let Ok(request) = receiver.recv() {
61 | match request {
62 | Request::Authenticate(password) => {
63 | if methods.is_empty() {
64 | warn!("no authentication method");
65 |
66 | sender.send(Response::Success).unwrap();
67 | continue 'main;
68 | }
69 |
70 | for method in &mut methods {
71 | if let Ok(true) = method.authenticate(user.to_str().unwrap(), &password) {
72 | sender.send(Response::Success).unwrap();
73 | continue 'main;
74 | }
75 | }
76 |
77 | sender.send(Response::Failure).unwrap();
78 | }
79 | }
80 | }
81 | });
82 |
83 | Ok(Auth {
84 | receiver: i_receiver,
85 | sender: i_sender,
86 | })
87 | }
88 |
89 | pub fn authenticate>(&self, password: S) -> Result<(), SendError> {
90 | self.sender.send(Request::Authenticate(password.into()))
91 | }
92 | }
93 |
94 | impl Deref for Auth {
95 | type Target = Receiver;
96 |
97 | fn deref(&self) -> &Receiver {
98 | &self.receiver
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/auth/internal.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use toml;
19 |
20 | use error;
21 | use super::Authenticate;
22 |
23 | pub struct Auth {
24 | password: String,
25 | }
26 |
27 | pub fn new(config: toml::value::Table) -> error::Result {
28 | Ok(Auth {
29 | password: config.get("password")
30 | .and_then(|v| v.as_str())
31 | .map(|s| s.to_string())
32 | .ok_or(error::auth::Internal::Creation)?,
33 | })
34 | }
35 |
36 | impl Authenticate for Auth {
37 | fn authenticate(&mut self, _user: &str, password: &str) -> error::Result {
38 | Ok(self.password == password)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/auth/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use crate::error;
19 |
20 | pub trait Authenticate: Send + 'static {
21 | fn authenticate(&mut self, user: &str, password: &str) -> error::Result;
22 | }
23 |
24 | mod auth;
25 | pub use self::auth::{Auth, Request, Response};
26 |
27 | #[cfg(feature = "auth-internal")]
28 | mod internal;
29 |
30 | #[cfg(feature = "auth-pam")]
31 | mod pam;
32 |
--------------------------------------------------------------------------------
/src/auth/pam.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::mem;
19 | use std::ptr;
20 | use std::ffi::{CStr, CString};
21 |
22 | use toml;
23 | use log::{info, error};
24 | use pam::types::*;
25 | use pam::raw::*;
26 | use libc::{c_char, c_int, c_void, size_t};
27 | use libc::{calloc, free, strdup};
28 |
29 | use crate::error;
30 | use super::Authenticate;
31 |
32 | pub struct Auth {
33 | accounts: bool,
34 | }
35 |
36 | pub fn new(_config: toml::value::Table) -> error::Result {
37 | Ok(Auth {
38 | accounts: cfg!(feature = "auth-pam-accounts"),
39 | })
40 | }
41 |
42 | struct Info {
43 | user: *const c_char,
44 | password: *const c_char,
45 | }
46 |
47 | impl Authenticate for Auth {
48 | fn authenticate(&mut self, user: &str, password: &str) -> error::Result {
49 | let user = CString::new(user)?;
50 | let password = CString::new(password)?;
51 |
52 | unsafe {
53 | let mut handle = mem::MaybeUninit::<*mut PamHandle>::uninit();
54 | let conv = PamConversation {
55 | conv: Some(conversation),
56 | data_ptr: &Info { user: user.as_ptr(), password: password.as_ptr() } as *const _ as *mut _,
57 | };
58 |
59 | macro_rules! pam {
60 | (check $body:expr) => (
61 | match $body.into() {
62 | PamReturnCode::SUCCESS =>
63 | Ok(()),
64 |
65 | error =>
66 | Err(error::auth::Pam(error))
67 | }
68 | );
69 |
70 | (checked $body:expr) => (
71 | match $body.into() {
72 | PamReturnCode::SUCCESS =>
73 | Ok(()),
74 |
75 | error => {
76 | pam_end(&mut *handle.assume_init(), error as i32);
77 | Err(error::auth::Pam(error))
78 | }
79 | }
80 | );
81 |
82 | (start) => (
83 | pam!(check pam_start(b"screenruster\x00".as_ptr() as *const _, ptr::null(), &conv, handle.as_mut_ptr() as *mut *const _))
84 | );
85 |
86 | (set_item $ty:ident => $value:expr) => (
87 | pam!(checked pam_set_item(&mut *handle.assume_init(), PamItemType::$ty as i32, strdup($value.as_ptr() as *const _) as *mut _))
88 | );
89 |
90 | (authenticate) => (
91 | pam!(checked pam_authenticate(&mut *handle.assume_init(), PamFlag::NONE as i32))
92 | );
93 |
94 | (end) => (
95 | pam_end(&mut *handle.assume_init(), PamReturnCode::SUCCESS as i32);
96 | );
97 |
98 | ($name:ident $flag:ident) => (
99 | pam!(checked $name(&mut *handle.assume_init(), PamFlag::$flag as i32))
100 | );
101 |
102 | ($name:ident) => (
103 | pam!($name NONE)
104 | );
105 | }
106 |
107 | pam!(start)?;
108 | pam!(set_item TTY => b":0.0\0")?;
109 | pam!(authenticate)?;
110 |
111 | // On some systems account management is not configured properly, but
112 | // some PAM modules require it to be called to work properly, so make the
113 | // erroring optional.
114 | if self.accounts {
115 | pam!(pam_acct_mgmt)?;
116 | pam!(pam_setcred REINITIALIZE_CRED)?;
117 | pam!(end);
118 | }
119 | else {
120 | if pam!(pam_acct_mgmt).is_ok() {
121 | pam!(end);
122 | }
123 | }
124 |
125 | Ok(true)
126 | }
127 | }
128 | }
129 |
130 | extern "C" fn conversation(count: c_int, messages: *mut *mut PamMessage, responses: *mut *mut PamResponse, data: *mut c_void) -> c_int {
131 | unsafe {
132 | let info = &*(data as *mut Info as *const Info);
133 | let mut result = PamReturnCode::SUCCESS;
134 |
135 | *responses = calloc(count as size_t, mem::size_of::() as size_t).as_mut().unwrap() as *mut _ as *mut _;
136 |
137 | for i in 0 .. count as isize {
138 | let message = &**messages.offset(i);
139 | let response = &mut *((*responses).offset(i));
140 |
141 | match PamMessageStyle::from(message.msg_style) {
142 | // Probably the username, since it wants us to echo it.
143 | PamMessageStyle::PROMPT_ECHO_ON => {
144 | response.resp = strdup(info.user);
145 | }
146 |
147 | // Probably the password, since it doesn't want us to echo it.
148 | PamMessageStyle::PROMPT_ECHO_OFF => {
149 | response.resp = strdup(info.password);
150 | }
151 |
152 | PamMessageStyle::ERROR_MSG => {
153 | result = PamReturnCode::CONV_ERR;
154 | error!("{}", String::from_utf8_lossy(CStr::from_ptr(message.msg).to_bytes()));
155 | }
156 |
157 | PamMessageStyle::TEXT_INFO => {
158 | info!("{}", String::from_utf8_lossy(CStr::from_ptr(message.msg).to_bytes()));
159 | }
160 | }
161 | }
162 |
163 | if result != PamReturnCode::SUCCESS {
164 | free(*responses as *mut _);
165 | }
166 |
167 | result as c_int
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/config/auth.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::sync::{Arc, RwLock};
19 |
20 | use toml;
21 |
22 | #[derive(Clone, Default, Debug)]
23 | pub struct Auth(pub(super) Arc>);
24 |
25 | #[derive(Debug)]
26 | pub(super) struct Data {
27 | pub table: toml::value::Table,
28 | }
29 |
30 | impl Default for Data {
31 | fn default() -> Data {
32 | Data {
33 | table: Default::default(),
34 | }
35 | }
36 | }
37 |
38 | impl Auth {
39 | pub fn load(&self, table: &toml::value::Table) {
40 | if let Some(table) = table.get("auth").and_then(|v| v.as_table()) {
41 | self.0.write().unwrap().table = table.clone();
42 | }
43 | }
44 |
45 | /// Get the configuration for a specific authorization module.
46 | pub fn get>(&self, name: S) -> toml::value::Table {
47 | self.0.read().unwrap().table.get(name.as_ref())
48 | .and_then(|v| v.as_table()).cloned().unwrap_or_default()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/config/config.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::fs::File;
19 | use std::path::{Path, PathBuf};
20 | use std::io::Read;
21 | use std::sync::{Arc, RwLock};
22 |
23 | use toml;
24 | use log::error;
25 | use app_dirs::{AppInfo, AppDataType, get_app_root};
26 |
27 | use crate::error;
28 | use super::{Locker, Interface, Timer, Auth, Saver};
29 |
30 | #[derive(Clone, Debug, Default)]
31 | pub struct Config {
32 | path: Arc>>,
33 |
34 | locker: Locker,
35 | interface: Interface,
36 | timer: Timer,
37 | auth: Auth,
38 | saver: Saver,
39 | }
40 |
41 | impl Config {
42 | pub fn load>(path: Option) -> error::Result {
43 | let config = Config::default();
44 | config.reload(path)?;
45 |
46 | Ok(config)
47 | }
48 |
49 | pub fn reset(&self) {
50 | *self.locker.0.write().unwrap() = Default::default();
51 | *self.interface.0.write().unwrap() = Default::default();
52 | *self.timer.0.write().unwrap() = Default::default();
53 | *self.auth.0.write().unwrap() = Default::default();
54 | *self.saver.0.write().unwrap() = Default::default();
55 | }
56 |
57 | pub fn reload>(&self, path: Option) -> error::Result<()> {
58 | let path = if let Some(path) = path {
59 | *self.path.write().unwrap() = Some(path.as_ref().into());
60 | path.as_ref().into()
61 | }
62 | else if let Some(path) = self.path.read().unwrap().clone() {
63 | path
64 | }
65 | else {
66 | get_app_root(AppDataType::UserConfig,
67 | &AppInfo { name: "screenruster", author: "meh." })?.join("config.toml")
68 | };
69 |
70 | let table = if let Ok(mut file) = File::open(path) {
71 | let mut content = String::new();
72 | file.read_to_string(&mut content)?;
73 |
74 | match content.parse::() {
75 | Ok(table) => {
76 | table.as_table().unwrap().clone()
77 | }
78 |
79 | Err(error) => {
80 | error!("could not load configuration file");
81 | error!("{:?}", error);
82 |
83 | toml::value::Table::new()
84 | }
85 | }
86 | }
87 | else {
88 | toml::value::Table::new()
89 | };
90 |
91 | self.locker.load(&table);
92 | self.interface.load(&table);
93 | self.timer.load(&table);
94 | self.auth.load(&table);
95 | self.saver.load(&table);
96 |
97 | Ok(())
98 | }
99 |
100 | pub fn timer(&self) -> Timer {
101 | self.timer.clone()
102 | }
103 |
104 | pub fn interface(&self) -> Interface {
105 | self.interface.clone()
106 | }
107 |
108 | pub fn locker(&self) -> Locker {
109 | self.locker.clone()
110 | }
111 |
112 | pub fn auth(&self) -> Auth {
113 | self.auth.clone()
114 | }
115 |
116 | pub fn saver(&self) -> Saver {
117 | self.saver.clone()
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/config/interface.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::sync::{Arc, RwLock};
19 | use std::collections::HashSet;
20 |
21 | use toml;
22 |
23 | #[derive(Clone, Default, Debug)]
24 | pub struct Interface(pub(super) Arc>);
25 |
26 | #[derive(Debug)]
27 | pub(super) struct Data {
28 | pub ignore: HashSet,
29 | }
30 |
31 | impl Default for Data {
32 | fn default() -> Data {
33 | Data {
34 | ignore: HashSet::new(),
35 | }
36 | }
37 | }
38 |
39 | impl Interface {
40 | pub fn load(&self, table: &toml::value::Table) {
41 | if let Some(table) = table.get("interface").and_then(|v| v.as_table()) {
42 | if let Some(array) = table.get("ignore").and_then(|v| v.as_array()) {
43 | self.0.write().unwrap().ignore = array.iter()
44 | .filter(|v| v.as_str().is_some())
45 | .map(|v| v.as_str().unwrap().to_string())
46 | .collect();
47 | }
48 | }
49 | }
50 |
51 | pub fn ignores>(&self, name: T) -> bool {
52 | self.0.read().unwrap().ignore.contains(name.as_ref())
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/config/locker.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::sync::{Arc, RwLock};
19 |
20 | use toml;
21 |
22 | use super::OnSuspend;
23 |
24 | #[derive(Clone, Default, Debug)]
25 | pub struct Locker(pub(super) Arc>);
26 |
27 | #[derive(Debug)]
28 | pub(super) struct Data {
29 | pub display: Option,
30 | pub dpms: bool,
31 |
32 | pub on_suspend: OnSuspend,
33 | }
34 |
35 | impl Default for Data {
36 | fn default() -> Data {
37 | Data {
38 | display: None,
39 | dpms: true,
40 |
41 | on_suspend: Default::default(),
42 | }
43 | }
44 | }
45 |
46 | impl Locker {
47 | pub fn load(&self, table: &toml::value::Table) {
48 | if let Some(table) = table.get("locker").and_then(|v| v.as_table()) {
49 | if let Some(value) = table.get("display").and_then(|v| v.as_str()) {
50 | self.0.write().unwrap().display = Some(value.into());
51 | }
52 |
53 | if let Some(false) = table.get("dpms").and_then(|v| v.as_bool()) {
54 | self.0.write().unwrap().dpms = false;
55 | }
56 |
57 | if let Some(value) = table.get("on-suspend").and_then(|v| v.as_str()) {
58 | self.0.write().unwrap().on_suspend = match value {
59 | "use-system-time" =>
60 | OnSuspend::UseSystemTime,
61 |
62 | "lock" =>
63 | OnSuspend::Lock,
64 |
65 | "activate" =>
66 | OnSuspend::Activate,
67 |
68 | _ =>
69 | Default::default()
70 | };
71 | }
72 | }
73 | }
74 |
75 | pub fn display(&self) -> Option {
76 | self.0.read().unwrap().display.clone()
77 | }
78 |
79 | pub fn dpms(&self) -> bool {
80 | self.0.read().unwrap().dpms
81 | }
82 |
83 | pub fn on_suspend(&self) -> OnSuspend {
84 | self.0.read().unwrap().on_suspend
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/config/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use toml;
19 |
20 | mod locker;
21 | pub use self::locker::Locker;
22 |
23 | mod interface;
24 | pub use self::interface::Interface;
25 |
26 | mod timer;
27 | pub use self::timer::Timer;
28 |
29 | mod auth;
30 | pub use self::auth::Auth;
31 |
32 | mod saver;
33 | pub use self::saver::Saver;
34 |
35 | mod config;
36 | pub use self::config::Config;
37 |
38 | #[derive(Eq, PartialEq, Copy, Clone, Debug)]
39 | pub enum OnSuspend {
40 | Ignore,
41 | UseSystemTime,
42 | Activate,
43 | Lock,
44 | }
45 |
46 | impl Default for OnSuspend {
47 | fn default() -> OnSuspend {
48 | OnSuspend::Ignore
49 | }
50 | }
51 |
52 | fn seconds(value: Option<&toml::Value>) -> Option {
53 | if value.is_none() {
54 | return None;
55 | }
56 |
57 | match *value.unwrap() {
58 | toml::Value::Integer(value) => {
59 | Some(value as u32)
60 | }
61 |
62 | toml::Value::Float(value) => {
63 | Some(value.round() as u32)
64 | }
65 |
66 | toml::Value::String(ref value) => {
67 | match value.split(':').collect::>()[..] {
68 | [hours, minutes, seconds] =>
69 | Some(hours.parse::().ok()? * 60 * 60 + minutes.parse::().ok()? * 60 + seconds.parse::().ok()?),
70 |
71 | [minutes, seconds] =>
72 | Some(minutes.parse::().ok()? * 60 + seconds.parse::().ok()?),
73 |
74 | [seconds] =>
75 | Some(seconds.parse::().ok()?),
76 |
77 | _ =>
78 | None
79 | }
80 | }
81 |
82 | _ =>
83 | None
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/config/saver.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::sync::{Arc, RwLock};
19 |
20 | use toml;
21 |
22 | #[derive(Clone, Default, Debug)]
23 | pub struct Saver(pub(super) Arc>);
24 |
25 | #[derive(Debug)]
26 | pub(super) struct Data {
27 | pub timeout: u32,
28 | pub throttle: bool,
29 |
30 | pub using: Vec,
31 | pub table: toml::value::Table,
32 | }
33 |
34 | impl Default for Data {
35 | fn default() -> Data {
36 | Data {
37 | timeout: 5,
38 | throttle: false,
39 |
40 | using: Default::default(),
41 | table: Default::default(),
42 | }
43 | }
44 | }
45 |
46 | impl Saver {
47 | pub fn load(&self, table: &toml::value::Table) {
48 | if let Some(table) = table.get("saver").and_then(|v| v.as_table()) {
49 | if let Some(value) = super::seconds(table.get("timeout")) {
50 | self.0.write().unwrap().timeout = value;
51 | }
52 |
53 | if let Some(value) = table.get("throttle").and_then(|v| v.as_bool()) {
54 | self.0.write().unwrap().throttle = value;
55 | }
56 |
57 | if let Some(value) = table.get("use").and_then(|v| v.as_array()) {
58 | self.0.write().unwrap().using = value.iter()
59 | .filter(|v| v.as_str().is_some())
60 | .map(|v| v.as_str().unwrap().into())
61 | .collect();
62 | }
63 |
64 | self.0.write().unwrap().table = table.clone();
65 | }
66 | }
67 |
68 | /// The timeout for saver requests.
69 | pub fn timeout(&self) -> u32 {
70 | self.0.read().unwrap().timeout
71 | }
72 |
73 | /// Whether throttling is enabled by default.
74 | pub fn throttle(&self) -> bool {
75 | self.0.read().unwrap().throttle
76 | }
77 |
78 | /// List of savers being used.
79 | pub fn using(&self) -> Vec {
80 | self.0.read().unwrap().using.clone()
81 | }
82 |
83 | /// Get the configuration for a specific saver.
84 | pub fn get>(&self, name: S) -> toml::value::Table {
85 | self.0.read().unwrap().table.get(name.as_ref())
86 | .and_then(|v| v.as_table()).cloned().unwrap_or_default()
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/config/timer.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::sync::{Arc, RwLock};
19 |
20 | use toml;
21 |
22 | #[derive(Clone, Default, Debug)]
23 | pub struct Timer(pub(super) Arc>);
24 |
25 | #[derive(Debug)]
26 | pub(super) struct Data {
27 | pub beat: u32,
28 | pub timeout: u32,
29 | pub lock: Option,
30 | pub blank: Option,
31 | }
32 |
33 | impl Default for Data {
34 | fn default() -> Data {
35 | Data {
36 | beat: 30,
37 | timeout: 360,
38 | lock: None,
39 | blank: None,
40 | }
41 | }
42 | }
43 |
44 | impl Timer {
45 | pub fn load(&self, table: &toml::value::Table) {
46 | if let Some(table) = table.get("timer").and_then(|v| v.as_table()) {
47 | if let Some(value) = super::seconds(table.get("beat")) {
48 | self.0.write().unwrap().beat = value;
49 | }
50 |
51 | if let Some(value) = super::seconds(table.get("timeout")) {
52 | self.0.write().unwrap().timeout = value;
53 | }
54 |
55 | if let Some(value) = super::seconds(table.get("lock")) {
56 | self.0.write().unwrap().lock = Some(value);
57 | }
58 |
59 | if let Some(value) = super::seconds(table.get("blank")) {
60 | self.0.write().unwrap().blank = Some(value);
61 | }
62 | }
63 | }
64 |
65 | pub fn beat(&self) -> u32 {
66 | self.0.read().unwrap().beat
67 | }
68 |
69 | pub fn timeout(&self) -> u32 {
70 | self.0.read().unwrap().timeout
71 | }
72 |
73 | pub fn lock(&self) -> Option {
74 | self.0.read().unwrap().lock
75 | }
76 |
77 | pub fn blank(&self) -> Option {
78 | self.0.read().unwrap().blank
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/error.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::fmt;
19 | use std::error;
20 | use std::io;
21 | use std::ffi;
22 |
23 | use xcb;
24 | use dbus;
25 | use clap;
26 | use app_dirs;
27 |
28 | pub type Result = ::std::result::Result;
29 |
30 | #[derive(Debug)]
31 | pub enum Error {
32 | Io(io::Error),
33 | Message(String),
34 | Nul(ffi::NulError),
35 | Unknown,
36 | Parse,
37 |
38 | X(X),
39 | DBus(DBus),
40 | Cli(clap::Error),
41 | Directory(app_dirs::AppDirsError),
42 | Grab(Grab),
43 | Auth(Auth),
44 | }
45 |
46 | #[derive(Debug)]
47 | pub enum X {
48 | MissingExtension,
49 | Request(u8, u8),
50 | Connection(xcb::ConnError),
51 | }
52 |
53 | #[derive(Debug)]
54 | pub enum DBus {
55 | AlreadyRegistered,
56 | Internal(dbus::Error),
57 | }
58 |
59 | #[derive(Eq, PartialEq, Copy, Clone, Debug)]
60 | pub enum Grab {
61 | Conflict,
62 | Frozen,
63 | Unmapped,
64 | }
65 |
66 | #[derive(Clone, Debug)]
67 | pub enum Auth {
68 | UnknownUser,
69 |
70 | #[cfg(feature = "auth-internal")]
71 | Internal(auth::Internal),
72 |
73 | #[cfg(feature = "auth-pam")]
74 | Pam(auth::Pam),
75 | }
76 |
77 | pub mod auth {
78 | #[cfg(feature = "auth-pam")]
79 | use pam;
80 |
81 | #[derive(Clone, Debug)]
82 | #[cfg(feature = "auth-internal")]
83 | pub enum Internal {
84 | MissingPassword,
85 | }
86 |
87 | #[derive(Clone, Debug)]
88 | #[cfg(feature = "auth-pam")]
89 | pub struct Pam(pub pam::PamReturnCode);
90 | }
91 |
92 | impl From for Error {
93 | fn from(value: io::Error) -> Self {
94 | Error::Io(value)
95 | }
96 | }
97 |
98 | impl From for Error {
99 | fn from(value: ffi::NulError) -> Self {
100 | Error::Nul(value)
101 | }
102 | }
103 |
104 | impl From for Error {
105 | fn from(value: String) -> Self {
106 | Error::Message(value)
107 | }
108 | }
109 |
110 | impl From<()> for Error {
111 | fn from(_value: ()) -> Self {
112 | Error::Unknown
113 | }
114 | }
115 |
116 | impl From for Error {
117 | fn from(value: X) -> Error {
118 | Error::X(value)
119 | }
120 | }
121 |
122 | impl From for Error {
123 | fn from(value: xcb::ConnError) -> Error {
124 | Error::X(X::Connection(value))
125 | }
126 | }
127 |
128 | impl From> for Error {
129 | fn from(value: xcb::Error) -> Error {
130 | Error::X(X::Request(value.response_type(), value.error_code()))
131 | }
132 | }
133 |
134 | impl From for Error {
135 | fn from(value: dbus::Error) -> Self {
136 | Error::DBus(DBus::Internal(value))
137 | }
138 | }
139 |
140 | impl From for Error {
141 | fn from(value: clap::Error) -> Self {
142 | Error::Cli(value)
143 | }
144 | }
145 |
146 | impl From for Error {
147 | fn from(value: DBus) -> Self {
148 | Error::DBus(value)
149 | }
150 | }
151 |
152 | impl From for Error {
153 | fn from(value: app_dirs::AppDirsError) -> Self {
154 | Error::Directory(value)
155 | }
156 | }
157 |
158 | impl From for Error {
159 | fn from(value: Grab) -> Self {
160 | Error::Grab(value)
161 | }
162 | }
163 |
164 | impl From for Error {
165 | fn from(value: Auth) -> Self {
166 | Error::Auth(value)
167 | }
168 | }
169 |
170 | #[cfg(feature = "auth-internal")]
171 | impl From for Error {
172 | fn from(value: auth::Internal) -> Self {
173 | Error::Auth(Auth::Internal(value))
174 | }
175 | }
176 |
177 | #[cfg(feature = "auth-pam")]
178 | impl From for Error {
179 | fn from(value: auth::Pam) -> Self {
180 | Error::Auth(Auth::Pam(value))
181 | }
182 | }
183 |
184 | impl fmt::Display for Error {
185 | fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
186 | f.write_str(error::Error::description(self))
187 | }
188 | }
189 |
190 | impl error::Error for Error {
191 | fn description(&self) -> &str {
192 | match *self {
193 | Error::Io(ref err) =>
194 | err.description(),
195 |
196 | Error::Nul(ref err) =>
197 | err.description(),
198 |
199 | Error::Message(ref msg) =>
200 | msg.as_ref(),
201 |
202 | Error::Unknown =>
203 | "Unknown error.",
204 |
205 | Error::Parse =>
206 | "Parse error.",
207 |
208 | Error::X(ref err) => match *err {
209 | X::Request(..) =>
210 | "An X request failed.",
211 |
212 | X::MissingExtension =>
213 | "A required X extension is missing.",
214 |
215 | X::Connection(..) =>
216 | "Connection to the X display failed.",
217 | },
218 |
219 | Error::DBus(ref err) => match *err {
220 | DBus::AlreadyRegistered =>
221 | "The name has already been registered.",
222 |
223 | DBus::Internal(ref err) =>
224 | err.description(),
225 | },
226 |
227 | Error::Cli(ref err) =>
228 | err.description(),
229 |
230 | Error::Directory(ref err) =>
231 | err.description(),
232 |
233 | Error::Grab(ref err) => match *err {
234 | Grab::Conflict =>
235 | "A grab is already present.",
236 |
237 | Grab::Frozen =>
238 | "The grab is frozen.",
239 |
240 | Grab::Unmapped =>
241 | "The grabbing window is not mapped.",
242 | },
243 |
244 | Error::Auth(ref err) => match *err {
245 | Auth::UnknownUser =>
246 | "Unknown user.",
247 |
248 | #[cfg(feature = "auth-internal")]
249 | Auth::Internal(ref err) => match *err {
250 | auth::Internal::MissingPassword =>
251 | "Missing internal password.",
252 | },
253 |
254 | #[cfg(feature = "auth-pam")]
255 | Auth::Pam(auth::Pam(_code)) =>
256 | "PAM error.",
257 | },
258 | }
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/src/interface.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::path::Path;
19 | use std::time::{SystemTime, Duration};
20 | use std::thread;
21 | use std::sync::Arc;
22 | use std::ops::Deref;
23 | use channel::{self, Receiver, Sender, SendError};
24 |
25 | use dbus::{
26 | Message,
27 | blocking::{
28 | LocalConnection as Connection,
29 | stdintf::org_freedesktop_dbus::RequestNameReply,
30 | BlockingSender as _,
31 | },
32 | channel::{Sender as _}
33 | };
34 | use log::error;
35 |
36 | use crate::error;
37 | use crate::config;
38 |
39 | /// The DBus interface.
40 | ///
41 | /// It mimics the GNOME screensaver interface for simple integration with a
42 | /// GNOME environment, and also implements some ScreenRuster specific
43 | /// interfaces.
44 | ///
45 | /// It listens for relevant system events:
46 | ///
47 | /// - `PrepareForSleep` from SystemD
48 | pub struct Interface {
49 | receiver: Receiver,
50 | sender: Sender,
51 | signals: Sender,
52 | }
53 |
54 | #[derive(Debug)]
55 | pub enum Request {
56 | /// Reload the configuration file.
57 | Reload(Option),
58 |
59 | /// Lock the screen.
60 | Lock,
61 |
62 | /// Cycle the saver.
63 | Cycle,
64 |
65 | /// Simulate user activity.
66 | SimulateUserActivity,
67 |
68 | /// Inhibit the starting of screen saving.
69 | Inhibit {
70 | application: String,
71 | reason: String,
72 | },
73 |
74 | /// Remove a previous Inhibit.
75 | UnInhibit(u32),
76 |
77 | /// Throttle the resource usage of the screen saving.
78 | Throttle {
79 | application: String,
80 | reason: String,
81 | },
82 |
83 | /// Remove a previous Throttle.
84 | UnThrottle(u32),
85 |
86 | /// Suspend any screen saver activity.
87 | Suspend {
88 | application: String,
89 | reason: String,
90 | },
91 |
92 | /// Remove a previous Suspend.
93 | Resume(u32),
94 |
95 | /// Change the active status of the screen saver.
96 | SetActive(bool),
97 |
98 | /// Get the active status of the screen saver.
99 | GetActive,
100 |
101 | /// Get how many seconds the screen saver has been active.
102 | GetActiveTime,
103 |
104 | /// Get the idle status of the session.
105 | GetSessionIdle,
106 |
107 | /// Get how many seconds the session has been idle.
108 | GetSessionIdleTime,
109 |
110 | /// The system is preparing for sleep or coming out of sleep.
111 | PrepareForSleep(Option),
112 | }
113 |
114 | #[derive(Debug)]
115 | pub enum Response {
116 | /// Whether the reload was successful or not.
117 | Reload(bool),
118 |
119 | /// The cookie for the inhibition.
120 | Inhibit(u32),
121 |
122 | /// The cookie for the throttle.
123 | Throttle(u32),
124 |
125 | /// The cookie for the suspend.
126 | Suspend(u32),
127 |
128 | /// Whether the screen is active or not.
129 | Active(bool),
130 |
131 | /// How many seconds the saver has been active.
132 | ActiveTime(u64),
133 |
134 | /// Whether the session is idle or not.
135 | SessionIdle(bool),
136 |
137 | /// How many seconds the session has been idle.
138 | SessionIdleTime(u64),
139 | }
140 |
141 | #[derive(Debug)]
142 | pub enum Signal {
143 | /// The saver has been activated or deactivated.
144 | Active(bool),
145 |
146 | /// The session has become idle or active.
147 | SessionIdle(bool),
148 |
149 | /// An authentication request was initiated or completed.
150 | AuthenticationRequest(bool),
151 | }
152 |
153 | impl Interface {
154 | /// Send a reload request.
155 | pub fn reload>(path: Option
) -> error::Result<()> {
156 | let mut message = Message::new_method_call(
157 | "meh.rust.ScreenSaver",
158 | "/meh/rust/ScreenSaver",
159 | "meh.rust.ScreenSaver",
160 | "Reload")?;
161 |
162 | if let Some(value) = path {
163 | message = message.append1(value.as_ref().to_string_lossy().into_owned());
164 | }
165 |
166 | Connection::new_session()?.send(message)?;
167 |
168 | Ok(())
169 | }
170 |
171 | /// Send a lock request.
172 | pub fn lock() -> error::Result<()> {
173 | Connection::new_session()?.send(Message::new_method_call(
174 | "org.gnome.ScreenSaver",
175 | "/org/gnome/ScreenSaver",
176 | "org.gnome.ScreenSaver",
177 | "Lock")?)?;
178 |
179 | Ok(())
180 | }
181 |
182 | /// Send an activation request.
183 | pub fn activate() -> error::Result<()> {
184 | Connection::new_session()?.send(Message::new_method_call(
185 | "org.gnome.ScreenSaver",
186 | "/org/gnome/ScreenSaver",
187 | "org.gnome.ScreenSaver",
188 | "SetActive")?
189 | .append1(true))?;
190 |
191 | Ok(())
192 | }
193 |
194 | /// Send a deactivation request.
195 | pub fn deactivate() -> error::Result<()> {
196 | Connection::new_session()?.send(Message::new_method_call(
197 | "org.gnome.ScreenSaver",
198 | "/org/gnome/ScreenSaver",
199 | "org.gnome.ScreenSaver",
200 | "SimulateUserActivity")?)?;
201 |
202 | Ok(())
203 | }
204 |
205 | /// Send an inhibition request.
206 | pub fn inhibit() -> error::Result {
207 | Connection::new_session()?.send_with_reply_and_block(Message::new_method_call(
208 | "org.gnome.ScreenSaver",
209 | "/org/gnome/ScreenSaver",
210 | "org.gnome.ScreenSaver",
211 | "Inhibit")?
212 | .append2("screenruster", "requested by user")
213 | , Duration::from_millis(5_000))?
214 | .get1::()
215 | .ok_or(dbus::Error::new_custom("inibhition", "wrong response").into())
216 | }
217 |
218 | /// Send an uninhibition request.
219 | pub fn uninhibit(cookie: u32) -> error::Result<()> {
220 | Connection::new_session()?.send(Message::new_method_call(
221 | "org.gnome.ScreenSaver",
222 | "/org/gnome/ScreenSaver",
223 | "org.gnome.ScreenSaver",
224 | "UnInhibit")?
225 | .append1(cookie))?;
226 |
227 | Ok(())
228 | }
229 |
230 | /// Send a throttle request.
231 | pub fn throttle() -> error::Result {
232 | Connection::new_session()?.send_with_reply_and_block(Message::new_method_call(
233 | "org.gnome.ScreenSaver",
234 | "/org/gnome/ScreenSaver",
235 | "org.gnome.ScreenSaver",
236 | "Throttle")?
237 | .append2("screenruster", "requested by user")
238 | , Duration::from_millis(5_000))?
239 | .get1::()
240 | .ok_or(dbus::Error::new_custom("throttle", "wrong response").into())
241 | }
242 |
243 | /// Send an unthrottle request.
244 | pub fn unthrottle(cookie: u32) -> error::Result<()> {
245 | Connection::new_session()?.send(Message::new_method_call(
246 | "org.gnome.ScreenSaver",
247 | "/org/gnome/ScreenSaver",
248 | "org.gnome.ScreenSaver",
249 | "UnThrottle")?
250 | .append1(cookie))?;
251 |
252 | Ok(())
253 | }
254 |
255 | /// Send a suspension request.
256 | pub fn suspend() -> error::Result {
257 | Connection::new_session()?.send_with_reply_and_block(Message::new_method_call(
258 | "meh.rust.ScreenSaver",
259 | "/meh/rust/ScreenSaver",
260 | "meh.rust.ScreenSaver",
261 | "Suspend")?
262 | .append2("screenruster", "requested by user")
263 | , Duration::from_millis(5_000))?
264 | .get1::()
265 | .ok_or(dbus::Error::new_custom("suspend", "wrong response").into())
266 | }
267 |
268 | /// Send a resume request.
269 | pub fn resume(cookie: u32) -> error::Result<()> {
270 | Connection::new_session()?.send(Message::new_method_call(
271 | "meh.rust.ScreenSaver",
272 | "/meh/rust/ScreenSaver",
273 | "meh.rust.ScreenSaver",
274 | "Resume")?
275 | .append1(cookie))?;
276 |
277 | Ok(())
278 | }
279 |
280 | /// Spawn a DBus interface with the given configuration.
281 | pub fn spawn(config: config::Interface) -> error::Result {
282 | let (sender, i_receiver) = channel::unbounded();
283 | let (i_sender, receiver) = channel::unbounded();
284 | let (s_sender, signals) = channel::unbounded();
285 | let (g_sender, g_receiver) = channel::unbounded::>();
286 |
287 | macro_rules! dbus {
288 | (connect system) => (
289 | Connection::new_system()
290 | );
291 |
292 | (connect session) => (
293 | match Connection::new_session() {
294 | Ok(value) => {
295 | value
296 | }
297 |
298 | Err(error) => {
299 | g_sender.send(Err(error.into())).unwrap();
300 | return;
301 | }
302 | }
303 | );
304 |
305 | (register $conn:expr, $name:expr) => (
306 | match $conn.request_name($name, false, false, true) {
307 | Ok(RequestNameReply::Exists) => {
308 | g_sender.send(Err(error::DBus::AlreadyRegistered.into())).unwrap();
309 | return;
310 | }
311 |
312 | Err(error) => {
313 | g_sender.send(Err(error.into())).unwrap();
314 | return;
315 | }
316 |
317 | Ok(value) => {
318 | value
319 | }
320 | }
321 | );
322 |
323 | (watch $conn:expr, $filter:expr) => (
324 | $conn.add_match_no_cb($filter)
325 | );
326 |
327 | (ready) => (
328 | g_sender.send(Ok(())).unwrap();
329 | );
330 |
331 | (check) => (
332 | g_receiver.recv().unwrap()
333 | );
334 |
335 | (try $body:expr) => (
336 | match $body {
337 | Ok(value) => {
338 | value
339 | }
340 |
341 | Err(err) => {
342 | error!("{:?}", err);
343 | return None;
344 | }
345 | }
346 | );
347 | }
348 |
349 | macro_rules! cloning {
350 | ([$($var:ident),*] $closure:expr) => ({
351 | $(let $var = $var.clone();)*
352 | $closure
353 | });
354 | }
355 |
356 | // System DBus handler.
357 | {
358 | let sender = sender.clone();
359 |
360 | thread::spawn(move || {
361 | /// Inhibits system suspension temporarily.
362 | fn inhibit(c: &Connection) -> Option {
363 | dbus!(try c.send_with_reply_and_block(dbus!(try Message::new_method_call(
364 | "org.freedesktop.login1",
365 | "/org/freedesktop/login1",
366 | "org.freedesktop.login1.Manager",
367 | "Inhibit"))
368 | .append1("sleep")
369 | .append1("ScreenRuster")
370 | .append1("Preparing for sleep.")
371 | .append1("delay"), Duration::from_millis(1_000)))
372 | .get1()
373 | }
374 |
375 | let system = dbus!(connect system).unwrap();
376 |
377 | // Delay the next suspension.
378 | let mut inhibitor = inhibit(&system);
379 |
380 | // Watch for PrepareForSleep events from SystemD.
381 | dbus!(watch system, "path='/org/freedesktop/login1',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'").unwrap();
382 |
383 | #[derive(Debug)]
384 | pub struct PrepareForSleep {
385 | pub arg0: bool,
386 | }
387 |
388 | impl dbus::arg::AppendAll for PrepareForSleep {
389 | fn append(&self, i: &mut dbus::arg::IterAppend) {
390 | dbus::arg::RefArg::append(&self.arg0, i);
391 | }
392 | }
393 |
394 | impl dbus::arg::ReadAll for PrepareForSleep {
395 | fn read(i: &mut dbus::arg::Iter) -> Result {
396 | Ok(PrepareForSleep {
397 | arg0: i.read()?,
398 | })
399 | }
400 | }
401 |
402 | impl dbus::message::SignalArgs for PrepareForSleep {
403 | const NAME: &'static str = "PrepareForSleep";
404 | const INTERFACE: &'static str = "org.freedesktop.login1.Manager";
405 | }
406 |
407 | system.with_proxy("org.freedesktop.login1.Manager", "/org/freedesktop/login1", Duration::from_micros(5_000))
408 | .match_signal(|p: PrepareForSleep, _: &Connection, _: &Message| {
409 | sender.send(Request::PrepareForSleep(
410 | if p.arg0 { Some(SystemTime::now()) } else { None })).unwrap();
411 |
412 | // In case the system is suspending, unlock the suspension,
413 | // otherwise delay the next.
414 | if p.arg0 {
415 | inhibitor.take();
416 | }
417 | else {
418 | inhibitor = inhibit(&system);
419 | }
420 |
421 | true
422 | });
423 | });
424 | }
425 |
426 | // Session DBus handler.
427 | {
428 | let sender = sender.clone();
429 |
430 | thread::spawn(move || {
431 | let mut session = dbus!(connect session);
432 | let f = dbus::tree::Factory::new_sync::<()>();
433 |
434 | dbus!(register session, "org.gnome.ScreenSaver");
435 | dbus!(register session, "meh.rust.ScreenSaver");
436 | dbus!(ready);
437 |
438 | // GNOME screensaver signals.
439 | let active = Arc::new(f.signal("ActiveChanged", ()).sarg::("status"));
440 | let idle = Arc::new(f.signal("SessionIdleChanged", ()).sarg::("status"));
441 | let begin = Arc::new(f.signal("AuthenticationRequestBegin", ()));
442 | let end = Arc::new(f.signal("AuthenticationRequestEnd", ()));
443 |
444 | let tree = f.tree(())
445 | // ScreenRuster interface.
446 | .add(f.object_path("/meh/rust/ScreenSaver", ()).introspectable().add(f.interface("meh.rust.ScreenSaver", ())
447 | .add_m(f.method("Reload", (), cloning!([config, sender, receiver] move |m| {
448 | if config.ignores("reload") {
449 | return Err(dbus::tree::MethodErr::failed(&"Reload is ignored"));
450 | }
451 |
452 | sender.send(Request::Reload(m.msg.get1())).unwrap();
453 |
454 | if let Response::Reload(value) = receiver.recv().unwrap() {
455 | Ok(vec![m.msg.method_return().append1(value)])
456 | }
457 | else {
458 | unreachable!();
459 | }
460 | })).inarg::("path").outarg::("success"))
461 |
462 | .add_m(f.method("Suspend", (), cloning!([config, sender, receiver] move |m| {
463 | if config.ignores("suspend") {
464 | return Err(dbus::tree::MethodErr::failed(&"Suspend is ignored"));
465 | }
466 |
467 | if let (Some(application), Some(reason)) = m.msg.get2() {
468 | sender.send(Request::Suspend {
469 | application: application,
470 | reason: reason
471 | }).unwrap();
472 |
473 | if let Response::Suspend(value) = receiver.recv().unwrap() {
474 | Ok(vec![m.msg.method_return().append1(value)])
475 | }
476 | else {
477 | unreachable!();
478 | }
479 | }
480 | else {
481 | Err(dbus::tree::MethodErr::no_arg())
482 | }
483 | })).in_args(vec![dbus::Signature::make::(), dbus::Signature::make::()]))
484 |
485 | .add_m(f.method("Resume", (), cloning!([config, sender] move |m| {
486 | if config.ignores("suspend") {
487 | return Err(dbus::tree::MethodErr::failed(&"Suspend is ignored"));
488 | }
489 |
490 | if let Some(cookie) = m.msg.get1() {
491 | sender.send(Request::Resume(cookie)).unwrap();
492 |
493 | Ok(vec![m.msg.method_return()])
494 | }
495 | else {
496 | Err(dbus::tree::MethodErr::no_arg())
497 | }
498 | })).inarg::("cookie"))))
499 |
500 | // GNOME screensaver interface.
501 | .add(f.object_path("/org/gnome/ScreenSaver", ()).introspectable().add(f.interface("org.gnome.ScreenSaver", ())
502 | .add_m(f.method("Lock", (), cloning!([sender] move |m| {
503 | sender.send(Request::Lock).unwrap();
504 |
505 | Ok(vec![m.msg.method_return()])
506 | })))
507 |
508 | .add_m(f.method("Cycle", (), cloning!([sender] move |m| {
509 | sender.send(Request::Cycle).unwrap();
510 |
511 | Ok(vec![m.msg.method_return()])
512 | })))
513 |
514 | .add_m(f.method("SimulateUserActivity", (), cloning!([sender] move |m| {
515 | sender.send(Request::SimulateUserActivity).unwrap();
516 |
517 | Ok(vec![m.msg.method_return()])
518 | })))
519 |
520 | .add_m(f.method("Inhibit", (), cloning!([config, sender, receiver] move |m| {
521 | if config.ignores("inhibit") {
522 | return Err(dbus::tree::MethodErr::failed(&"Inhibit is ignored"));
523 | }
524 |
525 | if let (Some(application), Some(reason)) = m.msg.get2() {
526 | sender.send(Request::Inhibit {
527 | application: application,
528 | reason: reason
529 | }).unwrap();
530 |
531 | if let Response::Inhibit(value) = receiver.recv().unwrap() {
532 | Ok(vec![m.msg.method_return().append1(value)])
533 | }
534 | else {
535 | unreachable!();
536 | }
537 | }
538 | else {
539 | Err(dbus::tree::MethodErr::no_arg())
540 | }
541 | })).in_args(vec![dbus::Signature::make::(), dbus::Signature::make::()]))
542 |
543 | .add_m(f.method("UnInhibit", (), cloning!([config, sender] move |m| {
544 | if config.ignores("inhibit") {
545 | return Err(dbus::tree::MethodErr::failed(&"Inhibit is ignored"));
546 | }
547 |
548 | if let Some(cookie) = m.msg.get1() {
549 | sender.send(Request::UnInhibit(cookie)).unwrap();
550 |
551 | Ok(vec![m.msg.method_return()])
552 | }
553 | else {
554 | Err(dbus::tree::MethodErr::no_arg())
555 | }
556 | })).inarg::("cookie"))
557 |
558 | .add_m(f.method("Throttle", (), cloning!([config, sender, receiver] move |m| {
559 | if config.ignores("throttle") {
560 | return Err(dbus::tree::MethodErr::failed(&"Inhibit is ignored"));
561 | }
562 |
563 | if let (Some(application), Some(reason)) = m.msg.get2() {
564 | sender.send(Request::Throttle {
565 | application: application,
566 | reason: reason
567 | }).unwrap();
568 |
569 | if let Response::Throttle(value) = receiver.recv().unwrap() {
570 | Ok(vec![m.msg.method_return().append1(value)])
571 | }
572 | else {
573 | unreachable!();
574 | }
575 | }
576 | else {
577 | Err(dbus::tree::MethodErr::no_arg())
578 | }
579 | })).in_args(vec![dbus::Signature::make::(), dbus::Signature::make::()]))
580 |
581 | .add_m(f.method("UnThrottle", (), cloning!([config, sender] move |m| {
582 | if config.ignores("throttle") {
583 | return Err(dbus::tree::MethodErr::failed(&"Inhibit is ignored"));
584 | }
585 |
586 | if let Some(cookie) = m.msg.get1() {
587 | sender.send(Request::UnThrottle(cookie)).unwrap();
588 |
589 | Ok(vec![m.msg.method_return()])
590 | }
591 | else {
592 | Err(dbus::tree::MethodErr::no_arg())
593 | }
594 | })).inarg::("cookie"))
595 |
596 | .add_m(f.method("SetActive", (), cloning!([sender] move |m| {
597 | if let Some(value) = m.msg.get1() {
598 | sender.send(Request::SetActive(value)).unwrap();
599 |
600 | Ok(vec![m.msg.method_return()])
601 | }
602 | else {
603 | Err(dbus::tree::MethodErr::no_arg())
604 | }
605 | })).inarg::("active"))
606 |
607 | .add_m(f.method("GetActive", (), cloning!([sender, receiver] move |m| {
608 | sender.send(Request::GetActive).unwrap();
609 |
610 | if let Response::Active(value) = receiver.recv().unwrap() {
611 | Ok(vec![m.msg.method_return().append1(value)])
612 | }
613 | else {
614 | unreachable!();
615 | }
616 | })).outarg::("active"))
617 |
618 | .add_m(f.method("GetActiveTime", (), cloning!([sender, receiver] move |m| {
619 | sender.send(Request::GetActiveTime).unwrap();
620 |
621 | if let Response::ActiveTime(time) = receiver.recv().unwrap() {
622 | Ok(vec![m.msg.method_return().append1(time)])
623 | }
624 | else {
625 | unreachable!();
626 | }
627 | })).outarg::("time"))
628 |
629 | .add_m(f.method("GetSessionIdle", (), cloning!([sender, receiver] move |m| {
630 | sender.send(Request::GetSessionIdle).unwrap();
631 |
632 | if let Response::SessionIdle(value) = receiver.recv().unwrap() {
633 | Ok(vec![m.msg.method_return().append1(value)])
634 | }
635 | else {
636 | unreachable!();
637 | }
638 | })).outarg::("idle"))
639 |
640 | .add_m(f.method("GetSessionIdleTime", (), cloning!([sender, receiver] move |m| {
641 | sender.send(Request::GetSessionIdleTime).unwrap();
642 |
643 | if let Response::SessionIdleTime(time) = receiver.recv().unwrap() {
644 | Ok(vec![m.msg.method_return().append1(time)])
645 | }
646 | else {
647 | unreachable!();
648 | }
649 | })).outarg::("time"))
650 |
651 | .add_s(active.clone())
652 | .add_s(idle.clone())
653 | .add_s(begin.clone())
654 | .add_s(end.clone())));
655 |
656 | tree.start_receive(&session);
657 |
658 | loop {
659 | session.process(Duration::from_millis(500));
660 |
661 | while let Ok(signal) = signals.try_recv() {
662 | session.send(match signal {
663 | Signal::Active(status) =>
664 | active.msg(&"/meh/rust/ScreenSaver".into(), &"org.gnome.ScreenSaver".into()).append1(status),
665 |
666 | Signal::SessionIdle(status) =>
667 | idle.msg(&"/meh/rust/ScreenSaver".into(), &"org.gnome.ScreenSaver".into()).append1(status),
668 |
669 | Signal::AuthenticationRequest(true) =>
670 | begin.msg(&"/meh/rust/ScreenSaver".into(), &"org.gnome.ScreenSaver".into()),
671 |
672 | Signal::AuthenticationRequest(false) =>
673 | end.msg(&"/meh/rust/ScreenSaver".into(), &"org.gnome.ScreenSaver".into()),
674 | }).unwrap();
675 | }
676 | }
677 | });
678 | }
679 |
680 | dbus!(check)?;
681 |
682 | Ok(Interface {
683 | receiver: i_receiver,
684 | sender: i_sender,
685 | signals: s_sender,
686 | })
687 | }
688 |
689 | pub fn response(&self, value: Response) -> Result<(), SendError> {
690 | self.sender.send(value)
691 | }
692 |
693 | pub fn signal(&self, value: Signal) -> Result<(), SendError> {
694 | self.signals.send(value)
695 | }
696 | }
697 |
698 | impl Deref for Interface {
699 | type Target = Receiver;
700 |
701 | fn deref(&self) -> &Receiver {
702 | &self.receiver
703 | }
704 | }
705 |
--------------------------------------------------------------------------------
/src/locker/display.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::sync::Arc;
19 | use std::ops::Deref;
20 |
21 | use xcb;
22 | use crate::{error, config, platform};
23 |
24 | pub struct Display {
25 | display: Arc,
26 |
27 | randr: bool,
28 | dpms: bool,
29 | }
30 |
31 | unsafe impl Send for Display { }
32 | unsafe impl Sync for Display { }
33 |
34 | impl Display {
35 | /// Open the display.
36 | pub fn open(config: config::Locker) -> error::Result> {
37 | let display = platform::Display::open(config.display())?;
38 | let randr = display.get_extension_data(xcb::randr::id());
39 | let mut dpms = display.get_extension_data(xcb::dpms::id());
40 |
41 | if randr.is_some() {
42 | let version = xcb::randr::query_version(&display, 1, 1).get_reply()?;
43 |
44 | if version.major_version() < 1 || (version.major_version() >= 1 && version.minor_version() < 1) {
45 | return Err(error::X::MissingExtension.into());
46 | }
47 | }
48 |
49 | if let Some(ext) = dpms.take() {
50 | if config.dpms() && xcb::dpms::capable(&display).get_reply()?.capable() {
51 | dpms = Some(ext);
52 | }
53 | }
54 |
55 | let display = Arc::new(Display {
56 | display: display.clone(),
57 |
58 | randr: randr.is_some(),
59 | dpms: dpms.is_some(),
60 | });
61 |
62 | display.sanitize();
63 |
64 | Ok(display)
65 | }
66 |
67 | /// Get the XRandr extension data.
68 | pub fn randr(&self) -> Option {
69 | if self.randr {
70 | Some(self.display.get_extension_data(xcb::randr::id()).unwrap())
71 | }
72 | else {
73 | None
74 | }
75 | }
76 |
77 | /// Get the DPMS extension data.
78 | pub fn dpms(&self) -> Option {
79 | if self.dpms {
80 | Some(self.display.get_extension_data(xcb::dpms::id()).unwrap())
81 | }
82 | else {
83 | None
84 | }
85 | }
86 |
87 | /// Check if the monitor is powered on or not.
88 | pub fn is_powered(&self) -> bool {
89 | if !self.dpms {
90 | return true;
91 | }
92 |
93 | if let Ok(reply) = xcb::dpms::info(self).get_reply() {
94 | if !reply.state() {
95 | return true;
96 | }
97 |
98 | match reply.power_level() as u32 {
99 | xcb::dpms::DPMS_MODE_ON =>
100 | true,
101 |
102 | xcb::dpms::DPMS_MODE_OFF |
103 | xcb::dpms::DPMS_MODE_STANDBY |
104 | xcb::dpms::DPMS_MODE_SUSPEND =>
105 | false,
106 |
107 | _ => unreachable!()
108 | }
109 | }
110 | else {
111 | false
112 | }
113 | }
114 |
115 | /// Turn the monitor on or off.
116 | pub fn power(&self, value: bool) {
117 | if !self.dpms || self.is_powered() == value {
118 | return;
119 | }
120 |
121 | xcb::dpms::force_level(self, if value {
122 | xcb::dpms::DPMS_MODE_ON
123 | } else {
124 | xcb::dpms::DPMS_MODE_OFF
125 | } as u16);
126 |
127 | self.flush();
128 | }
129 |
130 | /// Sanitize the display from bad X11 things.
131 | pub fn sanitize(&self) {
132 | // Reset DPMS settings to usable.
133 | if self.dpms {
134 | xcb::dpms::set_timeouts(self, 0, 0, 0);
135 | xcb::dpms::enable(self);
136 | }
137 |
138 | // Reset screen saver timeout.
139 | xcb::set_screen_saver(self, 0, 0, 0, xcb::EXPOSURES_ALLOWED as u8);
140 | }
141 |
142 | /// Observe events on the given window and all its children.
143 | pub fn observe(&self, window: u32) -> error::Result<()> {
144 | let query = xcb::query_tree(self, window).get_reply()?;
145 |
146 | // Return if the window is one of ours.
147 | {
148 | let reply = xcb::get_property(self, false, window,
149 | xcb::intern_atom(self, false, "SCREENRUSTER_SAVER").get_reply().unwrap().atom(),
150 | xcb::ATOM_CARDINAL, 0, 1).get_reply();
151 |
152 | if let Ok(reply) = reply {
153 | if reply.type_() == xcb::ATOM_CARDINAL {
154 | return Ok(());
155 | }
156 | }
157 | }
158 |
159 | // Start listening for activity events from the window making sure to not
160 | // break it, by excluding various events.
161 | let attrs = xcb::get_window_attributes(self, window).get_reply()?;
162 | xcb::change_window_attributes_checked(self, window, &[
163 | (xcb::CW_EVENT_MASK, (attrs.all_event_masks() | attrs.do_not_propagate_mask() as u32) &
164 | (xcb::EVENT_MASK_KEY_PRESS | xcb::EVENT_MASK_KEY_RELEASE) |
165 | (xcb::EVENT_MASK_POINTER_MOTION | xcb::EVENT_MASK_SUBSTRUCTURE_NOTIFY))]).request_check()?;
166 |
167 | for &child in query.children() {
168 | self.observe(child)?;
169 | }
170 |
171 | Ok(())
172 | }
173 | }
174 |
175 | impl Deref for Display {
176 | type Target = Arc;
177 |
178 | fn deref(&self) -> &Self::Target {
179 | &self.display
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/locker/locker.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::collections::HashMap;
19 | use std::thread;
20 | use std::ops::Deref;
21 | use channel::{self, Receiver, Sender, SendError, select};
22 |
23 | use rand::{self, Rng};
24 | use xcb;
25 | use xkb::{self, key};
26 |
27 | use crate::error;
28 | use crate::config::Config;
29 | use crate::timer;
30 | use crate::saver::{self, Saver, Safety, Password, Pointer};
31 | use super::{Display, Window};
32 | use crate::platform::{self, Keyboard};
33 | use api;
34 |
35 | pub struct Locker {
36 | receiver: Receiver,
37 | sender: Sender,
38 | }
39 |
40 | #[derive(Clone)]
41 | pub enum Request {
42 | Sanitize,
43 | Timeout { id: u64 },
44 | Activity,
45 | Power(bool),
46 | Throttle(bool),
47 |
48 | Start,
49 | Lock,
50 | Auth(bool),
51 | Stop,
52 | }
53 |
54 | #[derive(Clone)]
55 | pub enum Response {
56 | Timeout(timer::Timeout),
57 | Activity,
58 | Password(String),
59 | Stopped,
60 | }
61 |
62 | impl Locker {
63 | pub fn spawn(config: Config) -> error::Result {
64 | let display = Display::open(config.locker())?;
65 | let mut keyboard = Keyboard::new((*display).clone(), None)?;
66 | let mut windows = HashMap::::new();
67 | let mut savers = HashMap::::new();
68 | let mut checking = false;
69 | let mut password = String::new();
70 |
71 | for screen in 0 .. display.screens() {
72 | let window = Window::create(display.clone(), screen as i32)?;
73 |
74 | display.observe(window.root()).unwrap();
75 | windows.insert(window.id(), window);
76 | }
77 |
78 | let (sender, i_receiver) = channel::unbounded();
79 | let (i_sender, receiver) = channel::unbounded();
80 | let (s_sender, s_receiver) = channel::unbounded();
81 |
82 | thread::spawn(move || {
83 | macro_rules! window {
84 | (list) => (
85 | windows.values_mut()
86 | );
87 |
88 | (? $id:expr) => (
89 | windows.get_mut(&$id)
90 | );
91 |
92 | ($id:expr) => (
93 | windows.get_mut(&$id).unwrap()
94 | );
95 | }
96 |
97 | macro_rules! saver {
98 | (list) => (
99 | savers.values_mut()
100 | );
101 |
102 | (add $id:expr => $saver:expr) => (
103 | savers.insert($id, $saver);
104 | );
105 |
106 | (remove $id:expr) => (
107 | savers.remove(&$id);
108 | );
109 |
110 | (safety $id:expr) => (
111 | saver!(safety on window!($id));
112 | );
113 |
114 | (safety on $window:expr) => (
115 | if let Some(saver) = saver!(? $window.id()) {
116 | if $window.has_keyboard() && $window.has_pointer() {
117 | saver.safety(Safety::High).unwrap();
118 | }
119 | else if $window.has_keyboard() {
120 | saver.safety(Safety::Medium).unwrap();
121 | }
122 | else {
123 | saver.safety(Safety::Low).unwrap();
124 | }
125 | }
126 | );
127 |
128 | (? $id:expr) => (
129 | savers.get_mut(&$id)
130 | );
131 |
132 | ($id:expr) => (
133 | savers.get_mut(&$id).unwrap()
134 | );
135 | }
136 |
137 | let x = platform::display::sink(&display);
138 |
139 | loop {
140 | select! {
141 | // Handle control events.
142 | recv(receiver) -> event => {
143 | match event.unwrap() {
144 | Request::Timeout { id } => {
145 | if let Some(saver) = saver!(? id as u32) {
146 | saver.kill();
147 | }
148 | }
149 |
150 | Request::Sanitize => {
151 | display.sanitize();
152 |
153 | for window in window!(list) {
154 | let keyboard = window.has_keyboard();
155 | let pointer = window.has_pointer();
156 |
157 | window.sanitize();
158 |
159 | if keyboard == window.has_keyboard() && pointer == window.has_pointer() {
160 | continue;
161 | }
162 |
163 | saver!(safety on window);
164 | }
165 | }
166 |
167 | Request::Activity => {
168 | sender.send(Response::Activity).unwrap();
169 | }
170 |
171 | Request::Throttle(value) => {
172 | for saver in saver!(list) {
173 | saver.throttle(value).unwrap();
174 | }
175 | }
176 |
177 | Request::Power(value) => {
178 | for window in window!(list) {
179 | window.power(value);
180 | }
181 |
182 | for saver in saver!(list) {
183 | saver.blank(!value).unwrap();
184 | }
185 |
186 | display.power(value);
187 | }
188 |
189 | Request::Start => {
190 | for window in window!(list) {
191 | if !config.saver().using().is_empty() {
192 | let name = &config.saver().using()[rand::thread_rng().gen_range(0, config.saver().using().len())];
193 |
194 | if let Ok(mut saver) = Saver::spawn(&name) {
195 | let id = window.id();
196 |
197 | sender.send(Response::Timeout(timer::Timeout::Set {
198 | id: id as u64,
199 | seconds: config.saver().timeout() as u64,
200 | })).unwrap();
201 |
202 | let receiver = saver.take().unwrap();
203 | let sender = s_sender.clone();
204 |
205 | thread::spawn(move || {
206 | while let Ok(event) = receiver.recv() {
207 | sender.send((id, event)).unwrap();
208 | }
209 | });
210 |
211 | saver.config(config.saver().get(&name)).unwrap();
212 | saver.target(display.name(), window.screen(), id as u64).unwrap();
213 |
214 | if config.saver().throttle() {
215 | saver.throttle(true).unwrap();
216 | }
217 |
218 | saver!(add id => saver);
219 |
220 | continue;
221 | }
222 | }
223 |
224 | window.lock().unwrap();
225 | window.blank();
226 | }
227 | }
228 |
229 | Request::Lock => {
230 | for saver in saver!(list) {
231 | saver.lock().unwrap();
232 | }
233 | }
234 |
235 | Request::Auth(state) => {
236 | checking = false;
237 |
238 | for saver in saver!(list) {
239 | saver.password(if state { Password::Success } else { Password::Failure }).unwrap();
240 | }
241 | }
242 |
243 | Request::Stop => {
244 | for (&id, window) in &mut windows {
245 | if let Some(saver) = saver!(? id) {
246 | sender.send(Response::Timeout(timer::Timeout::Set {
247 | id: id as u64,
248 | seconds: config.saver().timeout() as u64,
249 | })).unwrap();
250 |
251 | saver.stop().unwrap();
252 | }
253 | else {
254 | window.unlock().unwrap();
255 | }
256 | }
257 | }
258 | }
259 | },
260 |
261 | // Handle saver events.
262 | recv(s_receiver) -> event => {
263 | let (id, event) = event.unwrap();
264 |
265 | match event {
266 | saver::Response::Forward(api::Response::Initialized) => {
267 | saver!(id).start().unwrap();
268 | }
269 |
270 | saver::Response::Forward(api::Response::Started) => {
271 | if saver!(id).was_started() {
272 | sender.send(Response::Timeout(timer::Timeout::Cancel { id: id as u64 })).unwrap();
273 |
274 | window!(id).lock().unwrap();
275 | saver!(safety id);
276 | }
277 | else {
278 | saver!(id).kill();
279 | }
280 | }
281 |
282 | saver::Response::Forward(api::Response::Stopped) => {
283 | if !saver!(id).was_stopped() {
284 | saver!(id).kill();
285 | }
286 | else {
287 | sender.send(Response::Timeout(timer::Timeout::Cancel { id: id as u64 })).unwrap();
288 | }
289 | }
290 |
291 | saver::Response::Exit(..) => {
292 | if saver!(id).was_stopped() {
293 | window!(id).unlock().unwrap();
294 |
295 | if savers.len() == 1 {
296 | sender.send(Response::Stopped).unwrap();
297 | }
298 | }
299 | else {
300 | window!(id).lock().unwrap();
301 | window!(id).blank();
302 | }
303 |
304 | saver!(remove id);
305 | }
306 | }
307 | },
308 |
309 | // Handle X events.
310 | recv(x) -> event => {
311 | let event = event.unwrap();
312 |
313 | match event.response_type() {
314 | // Handle screen changes.
315 | e if display.randr().map_or(false, |rr| e == rr.first_event() + xcb::randr::SCREEN_CHANGE_NOTIFY) => {
316 | let event = unsafe { xcb::cast_event::(&event) };
317 |
318 | for window in window!(list) {
319 | if window.root() == event.root() {
320 | window.resize(event.width() as u32, event.height() as u32);
321 |
322 | if let Some(saver) = saver!(? window.id()) {
323 | saver.resize(event.width() as u32, event.height() as u32).unwrap();
324 | }
325 | }
326 | }
327 | }
328 |
329 | // Handle keyboard events.
330 | e if keyboard.owns_event(e) => {
331 | keyboard.handle(&event);
332 | }
333 |
334 | // Handle keyboard input.
335 | //
336 | // Note we only act on key presses because `Xutf8LookupString`
337 | // only generates strings from `KeyPress` events.
338 | xcb::KEY_PRESS => {
339 | sender.send(Response::Activity).unwrap();
340 |
341 | // Ignore keyboard input while checking authentication.
342 | if checking {
343 | continue;
344 | }
345 |
346 | let event = unsafe { xcb::cast_event::(&event) };
347 | if windows.values().find(|w| w.id() == event.event()).is_some() {
348 | match keyboard.symbol(event.detail().into()) {
349 | // Delete a character.
350 | Some(key::BackSpace) => {
351 | if !password.is_empty() {
352 | password.pop();
353 |
354 | for saver in saver!(list) {
355 | saver.password(Password::Delete).unwrap();
356 | }
357 | }
358 | }
359 |
360 | // Clear the password.
361 | Some(key::Escape) => {
362 | if !password.is_empty() {
363 | password.clear();
364 |
365 | for saver in saver!(list) {
366 | saver.password(Password::Reset).unwrap();
367 | }
368 | }
369 | }
370 |
371 | // Check authentication.
372 | Some(key::Return) => {
373 | for saver in saver!(list) {
374 | saver.password(Password::Check).unwrap();
375 | }
376 |
377 | sender.send(Response::Password(password)).unwrap();
378 |
379 | checking = true;
380 | password = String::new();
381 | }
382 |
383 | _ => {
384 | // Limit the maximum password length so keeping a button
385 | // pressed is not going to OOM us in the extremely long
386 | // run.
387 | if password.len() <= 255 {
388 | if let Some(string) = keyboard.string(event.detail().into()) {
389 | for ch in string.chars() {
390 | password.push(ch);
391 |
392 | for saver in saver!(list) {
393 | saver.password(Password::Insert).unwrap();
394 | }
395 | }
396 | }
397 | }
398 | }
399 | }
400 | }
401 | }
402 |
403 | xcb::KEY_RELEASE => {
404 | sender.send(Response::Activity).unwrap();
405 | }
406 |
407 | // Handle mouse button presses.
408 | xcb::BUTTON_PRESS | xcb::BUTTON_RELEASE => {
409 | sender.send(Response::Activity).unwrap();
410 |
411 | let event = unsafe { xcb::cast_event::(&event) };
412 | if let Some(window) = windows.values().find(|w| w.id() == event.event()) {
413 | if let Some(saver) = saver!(? window.id()) {
414 | saver.pointer(Pointer::Button {
415 | x: event.event_x() as i32,
416 | y: event.event_y() as i32,
417 |
418 | button: event.detail(),
419 | press: event.response_type() == xcb::BUTTON_PRESS,
420 | }).unwrap()
421 | }
422 | }
423 | }
424 |
425 | // Handle mouse motion.
426 | xcb::MOTION_NOTIFY => {
427 | sender.send(Response::Activity).unwrap();
428 |
429 | let event = unsafe { xcb::cast_event::(&event) };
430 | if let Some(window) = windows.values().find(|w| w.id() == event.event()) {
431 | if let Some(saver) = saver!(? window.id()) {
432 | saver.pointer(Pointer::Move {
433 | x: event.event_x() as i32,
434 | y: event.event_y() as i32,
435 | }).unwrap();
436 | }
437 | }
438 | }
439 |
440 | // On window changes, try to observe the window.
441 | xcb::MAP_NOTIFY | xcb::CONFIGURE_NOTIFY => {
442 | let event = unsafe { xcb::cast_event::(&event) };
443 | display.observe(event.window()).unwrap();
444 | }
445 |
446 | _ => ()
447 | }
448 | }
449 | }
450 | }
451 | });
452 |
453 | Ok(Locker {
454 | receiver: i_receiver,
455 | sender: i_sender,
456 | })
457 | }
458 |
459 | pub fn sanitize(&self) -> Result<(), SendError> {
460 | self.sender.send(Request::Sanitize)
461 | }
462 |
463 | pub fn timeout(&self, id: u64) -> Result<(), SendError> {
464 | self.sender.send(Request::Timeout { id: id })
465 | }
466 |
467 | pub fn start(&self) -> Result<(), SendError> {
468 | self.sender.send(Request::Start)
469 | }
470 |
471 | pub fn lock(&self) -> Result<(), SendError> {
472 | self.sender.send(Request::Lock)
473 | }
474 |
475 | pub fn auth(&self, value: bool) -> Result<(), SendError> {
476 | self.sender.send(Request::Auth(value))
477 | }
478 |
479 | pub fn stop(&self) -> Result<(), SendError> {
480 | self.sender.send(Request::Stop)
481 | }
482 |
483 | pub fn power(&self, value: bool) -> Result<(), SendError> {
484 | self.sender.send(Request::Power(value))
485 | }
486 |
487 | pub fn activity(&self) -> Result<(), SendError> {
488 | self.sender.send(Request::Activity)
489 | }
490 |
491 | pub fn throttle(&self, value: bool) -> Result<(), SendError> {
492 | self.sender.send(Request::Throttle(value))
493 | }
494 | }
495 |
496 | impl Deref for Locker {
497 | type Target = Receiver;
498 |
499 | fn deref(&self) -> &Receiver {
500 | &self.receiver
501 | }
502 | }
503 |
--------------------------------------------------------------------------------
/src/locker/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | mod locker;
19 | pub use self::locker::{Locker, Request, Response};
20 |
21 | mod display;
22 | pub use self::display::Display;
23 |
24 | mod window;
25 | pub use self::window::Window;
26 |
--------------------------------------------------------------------------------
/src/locker/window.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::thread;
19 | use std::sync::Arc;
20 | use std::time::Duration;
21 | use std::ops::Deref;
22 |
23 | use xcb;
24 | use log::warn;
25 |
26 | use crate::error;
27 | use super::Display;
28 | use crate::platform;
29 |
30 | pub struct Window {
31 | display: Arc,
32 | window: platform::Window,
33 | gc: u32,
34 | cursor: u32,
35 |
36 | locked: bool,
37 | keyboard: bool,
38 | pointer: bool,
39 | }
40 |
41 | #[derive(Eq, PartialEq, Copy, Clone, Debug)]
42 | pub enum Grab {
43 | Keyboard,
44 | Pointer,
45 | }
46 |
47 | impl Window {
48 | pub fn create(display: Arc, index: i32) -> error::Result {
49 | let screen = display.get_setup().roots().nth(display.screen() as usize).unwrap();
50 | let window = platform::Window::create((**display).clone(), display.screen(),
51 | screen.width_in_pixels() as u32, screen.height_in_pixels() as u32)?;
52 |
53 | let cursor = {
54 | let pixmap = display.generate_id();
55 | xcb::create_pixmap(&display, 1, pixmap, screen.root(), 1, 1);
56 |
57 | let cursor = display.generate_id();
58 | xcb::create_cursor(&display, cursor, pixmap, pixmap, 0, 0, 0, 0, 0, 0, 1, 1);
59 | xcb::free_pixmap(&display, pixmap);
60 |
61 | cursor
62 | };
63 |
64 | xcb::change_window_attributes(&display, window.id(), &[
65 | (xcb::CW_CURSOR, cursor),
66 | (xcb::CW_OVERRIDE_REDIRECT, 1)]);
67 |
68 | xcb::change_property(&display, xcb::PROP_MODE_REPLACE as u8, window.id(),
69 | xcb::intern_atom(&display, false, "SCREENRUSTER_SAVER").get_reply()?.atom(),
70 | xcb::ATOM_CARDINAL, 32, &[index]);
71 |
72 | let gc = display.generate_id();
73 | xcb::create_gc(&display, gc, window.id(), &[(xcb::GC_FOREGROUND, screen.black_pixel())]);
74 |
75 | display.flush();
76 |
77 | Ok(Window {
78 | display: display.clone(),
79 | window: window,
80 | gc: gc,
81 | cursor: cursor,
82 |
83 | locked: false,
84 | keyboard: false,
85 | pointer: false,
86 | })
87 | }
88 |
89 | /// Check if the window is locked.
90 | pub fn is_locked(&self) -> bool {
91 | self.locked
92 | }
93 |
94 | /// Check if the window grabbed the keyboard.
95 | pub fn has_keyboard(&self) -> bool {
96 | self.keyboard
97 | }
98 |
99 | /// Check if the window grabbed the pointer.
100 | pub fn has_pointer(&self) -> bool {
101 | self.pointer
102 | }
103 |
104 | /// Sanitize the window.
105 | pub fn sanitize(&mut self) {
106 | if self.locked {
107 | // Try to grab the keyboard again in case it wasn't grabbed when locking.
108 | if !self.keyboard && self.grab(Grab::Keyboard).is_ok() {
109 | self.keyboard = true;
110 | }
111 |
112 | // Try to grab the pointer again in case it wasn't grabbed when locking.
113 | if !self.pointer && self.grab(Grab::Pointer).is_ok() {
114 | self.pointer = true;
115 | }
116 |
117 | // Remap the window in case stuff like popups went above the locker.
118 | xcb::map_window(&self.display, self.id());
119 | xcb::configure_window(&self.display, self.id(), &[
120 | (xcb::CONFIG_WINDOW_STACK_MODE as u16, xcb::STACK_MODE_ABOVE)]);
121 | }
122 | }
123 |
124 | /// Grab the given input.
125 | pub fn grab(&self, grab: Grab) -> error::Result<()> {
126 | let result = match grab {
127 | Grab::Keyboard => {
128 | xcb::grab_keyboard(&self.display, false, self.id(), xcb::CURRENT_TIME,
129 | xcb::GRAB_MODE_ASYNC as u8, xcb::GRAB_MODE_ASYNC as u8
130 | ).get_reply()?.status()
131 | }
132 |
133 | Grab::Pointer => {
134 | xcb::grab_pointer(&self.display, false, self.id(),
135 | (xcb::EVENT_MASK_BUTTON_PRESS | xcb::EVENT_MASK_BUTTON_RELEASE | xcb::EVENT_MASK_POINTER_MOTION) as u16,
136 | xcb::GRAB_MODE_ASYNC as u8, xcb::GRAB_MODE_ASYNC as u8, 0, self.cursor, xcb::CURRENT_TIME
137 | ).get_reply()?.status()
138 | }
139 | };
140 |
141 | match result as u32 {
142 | xcb::GRAB_STATUS_SUCCESS =>
143 | Ok(()),
144 |
145 | xcb::GRAB_STATUS_ALREADY_GRABBED =>
146 | Err(error::Grab::Conflict.into()),
147 |
148 | xcb::GRAB_STATUS_NOT_VIEWABLE =>
149 | Err(error::Grab::Unmapped.into()),
150 |
151 | xcb::GRAB_STATUS_FROZEN =>
152 | Err(error::Grab::Frozen.into()),
153 |
154 | _ =>
155 | unreachable!()
156 | }
157 | }
158 |
159 | /// Try to grab the given input with 1ms pauses.
160 | pub fn try_grab(&self, grab: Grab, tries: usize) -> error::Result<()> {
161 | let mut result = Ok(());
162 |
163 | for _ in 0 .. tries {
164 | result = self.grab(grab);
165 |
166 | if result.is_ok() {
167 | break;
168 | }
169 |
170 | thread::sleep(Duration::from_millis(1));
171 | }
172 |
173 | result
174 | }
175 |
176 | /// Lock the window.
177 | pub fn lock(&mut self) -> error::Result<()> {
178 | if self.locked {
179 | return Ok(());
180 | }
181 |
182 | // Map the window and make sure it's raised.
183 | xcb::map_window(&self.display, self.id());
184 | xcb::configure_window(&self.display, self.id(), &[
185 | (xcb::CONFIG_WINDOW_STACK_MODE as u16, xcb::STACK_MODE_ABOVE)]);
186 |
187 | // Try to grab the keyboard and mouse.
188 | self.keyboard = self.try_grab(Grab::Keyboard, 500).is_ok();
189 | self.pointer = self.try_grab(Grab::Pointer, 500).is_ok();
190 |
191 | // Some retarded X11 applications grab the keyboard and pointer for long
192 | // periods of time for no reason, so try to change focus and grab again.
193 | if !self.keyboard || !self.pointer {
194 | warn!("could not grab keyboard or pointer, trying to change focus");
195 |
196 | xcb::set_input_focus(&self.display, xcb::INPUT_FOCUS_POINTER_ROOT as u8, self.id(), xcb::CURRENT_TIME);
197 | self.flush();
198 |
199 | // Failing to grab the keyboard is fatal since the window manager or
200 | // other applications may be stealing our thunder.
201 | if !self.keyboard {
202 | if let Err(err) = self.try_grab(Grab::Keyboard, 500) {
203 | warn!("could not grab pointer: {:?}", err);
204 | }
205 | else {
206 | self.keyboard = true;
207 | }
208 | }
209 |
210 | if !self.pointer {
211 | if let Err(err) = self.try_grab(Grab::Pointer, 500) {
212 | warn!("could not grab pointer: {:?}", err);
213 | }
214 | else {
215 | self.pointer = true;
216 | }
217 | }
218 | }
219 |
220 | // Listen for window change events.
221 | xcb::change_window_attributes(&self.display, self.root(), &[
222 | (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_SUBSTRUCTURE_NOTIFY)]);
223 |
224 | // If the display supports XRandr listen for screen change events.
225 | if self.display.randr().is_some() {
226 | xcb::randr::select_input(&self.display, self.id(),
227 | xcb::randr::NOTIFY_MASK_SCREEN_CHANGE as u16);
228 | }
229 |
230 | self.locked = true;
231 |
232 | Ok(())
233 | }
234 |
235 | /// Notify the window the power status changed.
236 | pub fn power(&mut self, value: bool) {
237 | if !value {
238 | xcb::change_window_attributes(&self.display, self.id(), &[
239 | (xcb::CW_BACK_PIXEL, self.black())]);
240 | }
241 | }
242 |
243 | /// Make the window solid black.
244 | pub fn blank(&mut self) {
245 | let (width, height) = self.dimensions();
246 |
247 | xcb::poly_fill_rectangle(&self.display, self.id(), self.gc, &[
248 | xcb::Rectangle::new(0, 0, width as u16, height as u16)]);
249 |
250 | self.flush();
251 | }
252 |
253 | /// Unlock the window, hiding and ungrabbing whatever.
254 | pub fn unlock(&mut self) -> error::Result<()> {
255 | if !self.locked {
256 | return Ok(());
257 | }
258 |
259 | xcb::ungrab_keyboard(&self.display, xcb::CURRENT_TIME);
260 | self.keyboard = false;
261 |
262 | xcb::ungrab_pointer(&self.display, xcb::CURRENT_TIME);
263 | self.pointer = false;
264 |
265 | xcb::unmap_window(&self.display, self.id());
266 | self.locked = false;
267 |
268 | self.flush();
269 |
270 | Ok(())
271 | }
272 | }
273 |
274 | impl Deref for Window {
275 | type Target = platform::Window;
276 |
277 | fn deref(&self) -> &Self::Target {
278 | &self.window
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use clap::{ArgMatches, Arg, App, SubCommand};
19 | use channel::select;
20 | use log::info;
21 |
22 | mod error;
23 |
24 | mod config;
25 | use config::Config;
26 |
27 | mod platform;
28 | mod saver;
29 |
30 | mod preview;
31 | use preview::Preview;
32 |
33 | mod locker;
34 | use locker::Locker;
35 |
36 | mod auth;
37 | use auth::Auth;
38 |
39 | mod interface;
40 | use interface::Interface;
41 |
42 | mod timer;
43 | use timer::Timer;
44 |
45 | fn main() {
46 | env_logger::init();
47 |
48 | let mut app = App::new("screenruster")
49 | .version(env!("CARGO_PKG_VERSION"))
50 | .author("meh. ")
51 | .subcommand(SubCommand::with_name("reload")
52 | .about("Reload the configuration file.")
53 | .arg(Arg::with_name("config")
54 | .short("c")
55 | .long("config")
56 | .help("The path to the configuration file to use as replacement.")
57 | .takes_value(true)))
58 | .subcommand(SubCommand::with_name("lock")
59 | .about("Lock the screen."))
60 | .subcommand(SubCommand::with_name("activate")
61 | .about("Activate the screen saver."))
62 | .subcommand(SubCommand::with_name("deactivate")
63 | .about("Deactivate the screen saver like there was user input."))
64 | .subcommand(SubCommand::with_name("inhibit")
65 | .about("Inhibit the screen saver until uninhibit is called."))
66 | .subcommand(SubCommand::with_name("uninhibit")
67 | .about("Uninhibit a previous inhibition.")
68 | .arg(Arg::with_name("COOKIE")
69 | .required(true)
70 | .index(1)
71 | .help("The previously returned cookie.")))
72 | .subcommand(SubCommand::with_name("throttle")
73 | .about("Throttle the screen saver until unthrottle is called."))
74 | .subcommand(SubCommand::with_name("unthrottle")
75 | .about("Unthrottle a previous throttle.")
76 | .arg(Arg::with_name("COOKIE")
77 | .required(true)
78 | .index(1)
79 | .help("The previously returned cookie.")))
80 | .subcommand(SubCommand::with_name("suspend")
81 | .about("Prepare the saver for suspension."))
82 | .subcommand(SubCommand::with_name("resume")
83 | .about("Prepare the saver for resuming from suspension.")
84 | .arg(Arg::with_name("COOKIE")
85 | .required(true)
86 | .index(1)
87 | .help("The previously returned cookie.")))
88 | .subcommand(SubCommand::with_name("preview")
89 | .about("Preview a saver.")
90 | .arg(Arg::with_name("config")
91 | .short("c")
92 | .long("config")
93 | .help("The path to the configuration file.")
94 | .takes_value(true))
95 | .arg(Arg::with_name("SAVER")
96 | .required(true)
97 | .index(1)
98 | .help("The saver name.")))
99 | .subcommand(SubCommand::with_name("daemon")
100 | .about("Start the daemon.")
101 | .arg(Arg::with_name("config")
102 | .short("c")
103 | .long("config")
104 | .help("The path to the configuration file.")
105 | .takes_value(true)));
106 |
107 | let matches = app.clone().get_matches();
108 |
109 | exit(match matches.subcommand() {
110 | ("reload", Some(submatches)) =>
111 | Interface::reload(submatches.value_of("config")),
112 |
113 | ("lock", Some(_)) =>
114 | Interface::lock(),
115 |
116 | ("activate", Some(_)) =>
117 | Interface::activate(),
118 |
119 | ("deactivate", Some(_)) =>
120 | Interface::deactivate(),
121 |
122 | ("inhibit", Some(_)) =>
123 | Interface::inhibit().map(|v| println!("{}", v)),
124 |
125 | ("uninhibit", Some(submatches)) =>
126 | Interface::uninhibit(submatches.value_of("COOKIE").unwrap().parse::().unwrap()),
127 |
128 | ("throttle", Some(_)) =>
129 | Interface::throttle().map(|v| println!("{}", v)),
130 |
131 | ("unthrottle", Some(submatches)) =>
132 | Interface::unthrottle(submatches.value_of("COOKIE").unwrap().parse::().unwrap()),
133 |
134 | ("suspend", Some(_)) =>
135 | Interface::suspend().map(|v| println!("{}", v)),
136 |
137 | ("resume", Some(submatches)) =>
138 | Interface::resume(submatches.value_of("COOKIE").unwrap().parse::().unwrap()),
139 |
140 | ("preview", Some(submatches)) =>
141 | preview(submatches),
142 |
143 | ("daemon", Some(submatches)) =>
144 | daemon(submatches),
145 |
146 | _ =>
147 | app.print_help().map_err(|e| e.into())
148 | });
149 | }
150 |
151 | fn exit(value: error::Result) -> T {
152 | use std::io::Write;
153 | use error::Error;
154 |
155 | macro_rules! error {
156 | ($code:expr, $message:expr) => (
157 | error!($code, "{}", $message);
158 | );
159 |
160 | ($code:expr, $message:expr, $($rest:tt)*) => ({
161 | write!(&mut ::std::io::stderr(), "ERROR: ").unwrap();
162 | writeln!(&mut ::std::io::stderr(), $message, $($rest)*).unwrap();
163 | std::process::exit($code);
164 | });
165 | }
166 |
167 | match value {
168 | Ok(value) =>
169 | value,
170 |
171 | Err(error) => match error {
172 | Error::DBus(error::DBus::AlreadyRegistered) =>
173 | error!(10, "Another screen saver is currently running."),
174 |
175 | Error::DBus(_) =>
176 | error!(11, "The daemon is not running."),
177 |
178 | ref err =>
179 | error!(255, err),
180 | }
181 | }
182 | }
183 |
184 | fn preview(matches: &ArgMatches) -> error::Result<()> {
185 | let config = Config::load(matches.value_of("config"))?;
186 | let preview = Preview::spawn(matches.value_of("SAVER").unwrap(), config)?;
187 |
188 | loop {
189 | match preview.recv().unwrap() {
190 | preview::Response::Done => {
191 | break;
192 | }
193 | }
194 | }
195 |
196 | Ok(())
197 | }
198 |
199 | fn daemon(matches: &ArgMatches) -> error::Result<()> {
200 | use std::time::{Instant, SystemTime};
201 | use std::collections::HashSet;
202 | use rand::Rng;
203 |
204 | // Timer report IDs.
205 | const GET_ACTIVE_TIME: u64 = 1;
206 | const GET_SESSION_IDLE: u64 = 2;
207 | const GET_SESSION_IDLE_TIME: u64 = 3;
208 |
209 | // How many seconds to wait before acting on an Activity after one was
210 | // already acted upon.
211 | const ACTIVATION: u64 = 1;
212 |
213 | fn insert(set: &mut HashSet) -> u32 {
214 | loop {
215 | let cookie = rand::thread_rng().gen();
216 |
217 | if set.contains(&cookie) {
218 | continue;
219 | }
220 |
221 | set.insert(cookie);
222 |
223 | return cookie;
224 | }
225 | }
226 |
227 | let config = Config::load(matches.value_of("config"))?;
228 | let timer = Timer::spawn(config.timer())?;
229 | let auth = Auth::spawn(config.auth())?;
230 | let interface = Interface::spawn(config.interface())?;
231 | let locker = Locker::spawn(config.clone())?;
232 |
233 | let mut locked = None::;
234 | let mut started = None::;
235 | let mut blanked = None::;
236 | let mut suspended = None::;
237 |
238 | let mut inhibitors = HashSet::new();
239 | let mut throttlers = HashSet::new();
240 | let mut suspenders = HashSet::new();
241 |
242 | macro_rules! act {
243 | (suspend) => (
244 | act!(suspend SystemTime::now())
245 | );
246 |
247 | (suspend $time:expr) => (
248 | if suspenders.is_empty() && suspended.is_none() {
249 | timer.suspend($time).unwrap();
250 | }
251 | );
252 |
253 | (resume) => (
254 | if suspenders.is_empty() && suspended.is_some() {
255 | if blanked.is_some() {
256 | act!(unblank);
257 | }
258 |
259 | timer.resume().unwrap();
260 | }
261 | );
262 |
263 | (blank) => (
264 | blanked = Some(Instant::now());
265 |
266 | locker.power(false).unwrap();
267 | timer.blanked().unwrap();
268 | );
269 |
270 | (unblank) => (
271 | blanked = None;
272 |
273 | locker.power(true).unwrap();
274 | timer.unblanked().unwrap();
275 | );
276 |
277 | (start) => (
278 | started = Some(Instant::now());
279 |
280 | locker.start().unwrap();
281 | interface.signal(interface::Signal::Active(true)).unwrap();
282 | timer.started().unwrap();
283 | );
284 |
285 | (lock) => (
286 | locked = Some(Instant::now());
287 |
288 | locker.lock().unwrap();
289 | timer.locked().unwrap();
290 | );
291 |
292 | (stop) => (
293 | locker.stop().unwrap();
294 | );
295 |
296 | (stopped) => (
297 | started = None;
298 | locked = None;
299 |
300 | interface.signal(interface::Signal::Active(false)).unwrap();
301 | timer.stopped().unwrap();
302 | );
303 |
304 | (auth < $value:expr) => (
305 | interface.signal(interface::Signal::AuthenticationRequest(true)).unwrap();
306 | auth.authenticate($value).unwrap();
307 | );
308 |
309 | (auth success) => (
310 | locker.auth(true).unwrap();
311 | interface.signal(interface::Signal::AuthenticationRequest(false)).unwrap();
312 | );
313 |
314 | (auth failure) => (
315 | locker.auth(false).unwrap();
316 | interface.signal(interface::Signal::AuthenticationRequest(false)).unwrap();
317 | );
318 | }
319 |
320 | loop {
321 | select! {
322 | // Locker events.
323 | recv(locker) -> event => {
324 | match event.unwrap() {
325 | // Register timeout.
326 | locker::Response::Timeout(what) => {
327 | timer.timeout(what).unwrap();
328 | }
329 |
330 | // On system activity.
331 | locker::Response::Activity => {
332 | if suspended.is_some() {
333 | continue;
334 | }
335 |
336 | // Always reset the blank timer.
337 | timer.reset(timer::Event::Blank).unwrap();
338 |
339 | if blanked.is_some() {
340 | act!(unblank);
341 | }
342 |
343 | // If the saver has started but the screen is not locked, unlock
344 | // it, otherwise just reset the timers.
345 | if let Some(at) = started {
346 | if locked.is_none() && at.elapsed().as_secs() >= ACTIVATION {
347 | act!(stop);
348 | }
349 | }
350 | else {
351 | timer.reset(timer::Event::Idle).unwrap();
352 | }
353 | }
354 |
355 | // Try authorization.
356 | locker::Response::Password(pwd) => {
357 | act!(auth < pwd);
358 | }
359 |
360 | locker::Response::Stopped => {
361 | act!(stopped);
362 | }
363 | }
364 | },
365 |
366 | // Authentication events.
367 | recv(auth) -> event => {
368 | match event.unwrap() {
369 | auth::Response::Success => {
370 | info!("authorization: success");
371 |
372 | act!(auth success);
373 | act!(stop);
374 | }
375 |
376 | auth::Response::Failure => {
377 | info!("authorization: failure");
378 |
379 | act!(auth failure);
380 | }
381 | }
382 | },
383 |
384 | // DBus events.
385 | recv(interface) -> event => {
386 | match event.unwrap() {
387 | interface::Request::Reload(source) => {
388 | config.reset();
389 | interface.response(interface::Response::Reload(
390 | config.reload(source).is_ok())).unwrap();
391 | }
392 |
393 | interface::Request::Lock => {
394 | if started.is_none() {
395 | act!(start);
396 | }
397 |
398 | if locked.is_none() {
399 | act!(lock);
400 | }
401 | }
402 |
403 | // TODO: Implement cycling.
404 | interface::Request::Cycle => (),
405 |
406 | interface::Request::SimulateUserActivity => {
407 | locker.activity().unwrap();
408 | }
409 |
410 | interface::Request::Inhibit { .. } => {
411 | interface.response(interface::Response::Inhibit(insert(&mut inhibitors))).unwrap();
412 | }
413 |
414 | interface::Request::UnInhibit(cookie) => {
415 | if inhibitors.contains(&cookie) {
416 | inhibitors.remove(&cookie);
417 | }
418 | }
419 |
420 | interface::Request::Throttle { .. } => {
421 | if throttlers.is_empty() && !config.saver().throttle() {
422 | locker.throttle(true).unwrap();
423 | }
424 |
425 | interface.response(interface::Response::Throttle(insert(&mut throttlers))).unwrap();
426 | }
427 |
428 | interface::Request::UnThrottle(cookie) => {
429 | if throttlers.contains(&cookie) {
430 | throttlers.remove(&cookie);
431 |
432 | if throttlers.is_empty() && !config.saver().throttle() {
433 | locker.throttle(false).unwrap();
434 | }
435 | }
436 | }
437 |
438 | interface::Request::SetActive(active) => {
439 | if active {
440 | if started.is_none() {
441 | act!(start);
442 | }
443 | }
444 | else {
445 | if started.is_some() && locked.is_none() {
446 | act!(stop);
447 | }
448 | }
449 | }
450 |
451 | interface::Request::GetActive => {
452 | interface.response(interface::Response::Active(started.is_some())).unwrap();
453 | }
454 |
455 | interface::Request::GetActiveTime => {
456 | timer.report(GET_ACTIVE_TIME).unwrap();
457 | }
458 |
459 | interface::Request::GetSessionIdle => {
460 | timer.report(GET_SESSION_IDLE).unwrap();
461 | }
462 |
463 | interface::Request::GetSessionIdleTime => {
464 | timer.report(GET_SESSION_IDLE_TIME).unwrap();
465 | }
466 |
467 | interface::Request::Suspend { .. } => {
468 | act!(suspend);
469 |
470 | interface.response(interface::Response::Suspend(insert(&mut suspenders))).unwrap();
471 | }
472 |
473 | interface::Request::Resume(cookie) => {
474 | if suspenders.contains(&cookie) {
475 | suspenders.remove(&cookie);
476 |
477 | act!(resume);
478 | }
479 | }
480 |
481 | interface::Request::PrepareForSleep(time) => {
482 | if let Some(time) = time {
483 | match config.locker().on_suspend() {
484 | config::OnSuspend::Ignore |
485 | config::OnSuspend::Activate |
486 | config::OnSuspend::Lock => (),
487 |
488 | config::OnSuspend::UseSystemTime => {
489 | act!(suspend time);
490 | }
491 | }
492 | }
493 | else {
494 | match config.locker().on_suspend() {
495 | config::OnSuspend::Ignore => (),
496 |
497 | config::OnSuspend::UseSystemTime => {
498 | act!(resume);
499 | }
500 |
501 | config::OnSuspend::Activate => {
502 | act!(start);
503 | }
504 |
505 | config::OnSuspend::Lock => {
506 | act!(start);
507 | act!(lock);
508 | }
509 | }
510 | }
511 | }
512 | }
513 | },
514 |
515 | // Timer events.
516 | recv(timer) -> event => {
517 | match event.unwrap() {
518 | timer::Response::Report { id: GET_ACTIVE_TIME, started, .. } => {
519 | interface.response(interface::Response::ActiveTime(started.map_or(0, |i| i.elapsed().as_secs()))).unwrap();
520 | }
521 |
522 | timer::Response::Report { id: GET_SESSION_IDLE, idle, .. } => {
523 | interface.response(interface::Response::SessionIdle(idle.elapsed().as_secs() >= 5)).unwrap();
524 | }
525 |
526 | timer::Response::Report { id: GET_SESSION_IDLE_TIME, idle, .. } => {
527 | interface.response(interface::Response::SessionIdleTime(idle.elapsed().as_secs())).unwrap();
528 | }
529 |
530 | timer::Response::Report { .. } => {
531 | unreachable!();
532 | }
533 |
534 | timer::Response::Timeout { id } => {
535 | locker.timeout(id).unwrap();
536 | }
537 |
538 | timer::Response::Suspended(time) => {
539 | suspended = Some(time);
540 | }
541 |
542 | timer::Response::Resumed => {
543 | suspended = None;
544 | }
545 |
546 | timer::Response::Heartbeat(idle) => {
547 | locker.sanitize().unwrap();
548 |
549 | if idle.elapsed().as_secs() > 5 {
550 | interface.signal(interface::Signal::SessionIdle(true)).unwrap()
551 | }
552 | else {
553 | interface.signal(interface::Signal::SessionIdle(false)).unwrap()
554 | }
555 | }
556 |
557 | timer::Response::Start => {
558 | if inhibitors.is_empty() {
559 | act!(start);
560 | }
561 | else {
562 | timer.stopped().unwrap();
563 | }
564 | }
565 |
566 | timer::Response::Lock => {
567 | act!(lock);
568 | }
569 |
570 | timer::Response::Blank => {
571 | if inhibitors.is_empty() {
572 | act!(blank);
573 | }
574 | else {
575 | timer.unblanked().unwrap();
576 | }
577 | }
578 | }
579 | }
580 | }
581 | }
582 | }
583 |
--------------------------------------------------------------------------------
/src/platform/display.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::sync::Arc;
19 | use std::ops::Deref;
20 | use std::thread;
21 | use channel;
22 |
23 | use xcb;
24 | use xcbu::ewmh;
25 |
26 | use crate::error;
27 |
28 | pub struct Display {
29 | connection: ewmh::Connection,
30 |
31 | screen: i32,
32 | name: Option,
33 | }
34 |
35 | impl Display {
36 | pub fn open(name: Option) -> error::Result> {
37 | let (connection, screen) = xcb::Connection::connect(name.as_ref().map(AsRef::as_ref))?;
38 | let connection = ewmh::Connection::connect(connection).map_err(|(e, _)| e)?;
39 |
40 | Ok(Arc::new(Display {
41 | connection, screen, name
42 | }))
43 | }
44 |
45 | pub fn screen(&self) -> i32 {
46 | self.screen
47 | }
48 |
49 | pub fn name(&self) -> Option<&str> {
50 | self.name.as_ref().map(AsRef::as_ref)
51 | }
52 |
53 | pub fn screens(&self) -> u8 {
54 | self.get_setup().roots_len()
55 | }
56 | }
57 |
58 | pub fn sink(display: &Arc) -> channel::Receiver {
59 | let (sender, receiver) = channel::bounded(1);
60 | let display = display.clone();
61 |
62 | // Drain events into a channel.
63 | thread::spawn(move || {
64 | while let Some(event) = display.wait_for_event() {
65 | sender.send(event).unwrap();
66 | }
67 | });
68 |
69 | receiver
70 | }
71 |
72 | impl Deref for Display {
73 | type Target = xcb::Connection;
74 |
75 | fn deref(&self) -> &Self::Target {
76 | &self.connection
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/platform/keyboard.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::sync::Arc;
19 | use std::env;
20 |
21 | use xcb;
22 | use xkb;
23 |
24 | use crate::error;
25 | use super::Display;
26 |
27 | /// Keyboard manager and handler.
28 | ///
29 | /// Its job is to map X key events to proper symbols/strings based on the
30 | /// layout and mappings.
31 | pub struct Keyboard {
32 | display: Arc,
33 | context: xkb::Context,
34 | device: i32,
35 | keymap: xkb::Keymap,
36 | state: xkb::State,
37 | #[allow(dead_code)]
38 | table: xkb::compose::Table,
39 | compose: xkb::compose::State,
40 | }
41 |
42 | unsafe impl Send for Keyboard { }
43 | unsafe impl Sync for Keyboard { }
44 |
45 | impl Keyboard {
46 | /// Create a keyboard for the given display.
47 | pub fn new(display: Arc, locale: Option<&str>) -> error::Result {
48 | display.get_extension_data(xcb::xkb::id())
49 | .ok_or(error::X::MissingExtension)?;
50 |
51 | // Check the XKB extension version.
52 | {
53 | let cookie = xcb::xkb::use_extension(&display,
54 | xkb::x11::MIN_MAJOR_XKB_VERSION,
55 | xkb::x11::MIN_MINOR_XKB_VERSION);
56 |
57 | if !cookie.get_reply()?.supported() {
58 | return Err(error::X::MissingExtension.into());
59 | }
60 | }
61 |
62 | // Select events.
63 | {
64 | let map =
65 | xcb::xkb::MAP_PART_KEY_TYPES |
66 | xcb::xkb::MAP_PART_KEY_SYMS |
67 | xcb::xkb::MAP_PART_MODIFIER_MAP |
68 | xcb::xkb::MAP_PART_EXPLICIT_COMPONENTS |
69 | xcb::xkb::MAP_PART_KEY_ACTIONS |
70 | xcb::xkb::MAP_PART_KEY_BEHAVIORS |
71 | xcb::xkb::MAP_PART_VIRTUAL_MODS |
72 | xcb::xkb::MAP_PART_VIRTUAL_MOD_MAP;
73 |
74 | let events =
75 | xcb::xkb::EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
76 | xcb::xkb::EVENT_TYPE_MAP_NOTIFY |
77 | xcb::xkb::EVENT_TYPE_STATE_NOTIFY;
78 |
79 | xcb::xkb::select_events_checked(&display,
80 | xcb::xkb::ID_USE_CORE_KBD as u16,
81 | events as u16, 0, events as u16,
82 | map as u16, map as u16, None).request_check()?;
83 | }
84 |
85 | let context = xkb::Context::default();
86 | let device = xkb::x11::device(&display)?;
87 | let keymap = xkb::x11::keymap(&display, device, &context, Default::default())?;
88 | let state = xkb::x11::state(&display, device, &keymap)?;
89 |
90 | let (table, compose) = {
91 | let locale = locale.map(String::from).or(env::var("LANG").ok()).unwrap_or("C".into());
92 | let table = if let Ok(table) = xkb::compose::Table::new(&context, &locale, Default::default()) {
93 | table
94 | }
95 | else {
96 | xkb::compose::Table::new(&context, "C", Default::default()).unwrap()
97 | };
98 |
99 | let state = table.state(Default::default());
100 |
101 | (table, state)
102 | };
103 |
104 | Ok(Keyboard { display, context, device, keymap, state, table, compose })
105 | }
106 |
107 | /// Get the extension data.
108 | pub fn extension(&self) -> xcb::QueryExtensionData {
109 | self.display.get_extension_data(xcb::xkb::id()).unwrap()
110 | }
111 |
112 | /// Checks if an event belongs to the keyboard.
113 | pub fn owns_event(&self, event: u8) -> bool {
114 | event >= self.extension().first_event() &&
115 | event < self.extension().first_event() + xcb::xkb::EXTENSION_DEVICE_NOTIFY
116 | }
117 |
118 | /// Handles an X event.
119 | pub fn handle(&mut self, event: &xcb::GenericEvent) {
120 | match event.response_type() - self.extension().first_event() {
121 | xcb::xkb::NEW_KEYBOARD_NOTIFY | xcb::xkb::MAP_NOTIFY => {
122 | self.keymap = xkb::x11::keymap(&self.display, self.device, &self.context, Default::default()).unwrap();
123 | self.state = xkb::x11::state(&self.display, self.device, &self.keymap).unwrap();
124 | }
125 |
126 | xcb::xkb::STATE_NOTIFY => {
127 | let event = unsafe { xcb::cast_event::(event) };
128 |
129 | self.state.update().mask(
130 | event.base_mods(),
131 | event.latched_mods(),
132 | event.locked_mods(),
133 | event.base_group(),
134 | event.latched_group(),
135 | event.locked_group());
136 | }
137 |
138 | _ => ()
139 | }
140 | }
141 |
142 | /// Translate a key code to the key symbol.
143 | pub fn symbol(&self, code: u8) -> Option {
144 | self.state.key(code).sym()
145 | }
146 |
147 | /// Translate a key code to an UTF-8 string.
148 | pub fn string(&self, code: u8) -> Option {
149 | self.state.key(code).utf8()
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/platform/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | pub mod display;
19 | pub use self::display::Display;
20 |
21 | mod window;
22 | pub use self::window::Window;
23 |
24 | mod keyboard;
25 | pub use self::keyboard::Keyboard;
26 |
--------------------------------------------------------------------------------
/src/platform/window.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::sync::Arc;
19 |
20 | use xcb;
21 |
22 | use crate::error;
23 | use super::Display;
24 |
25 | pub struct Window {
26 | display: Arc,
27 |
28 | id: u32,
29 | screen: i32,
30 | root: u32,
31 | black: u32,
32 | }
33 |
34 | unsafe impl Send for Window { }
35 | unsafe impl Sync for Window { }
36 |
37 | impl Window {
38 | pub fn create(display: Arc, index: i32, width: u32, height: u32) -> error::Result {
39 | let screen = display.get_setup().roots().nth(index as usize).unwrap();
40 | let id = display.generate_id();
41 |
42 | xcb::create_window(&display, xcb::COPY_FROM_PARENT as u8, id, screen.root(),
43 | 0, 0, width as u16, height as u16,
44 | 0, xcb::WINDOW_CLASS_INPUT_OUTPUT as u16, screen.root_visual(), &[
45 | (xcb::CW_BORDER_PIXEL, screen.black_pixel()),
46 | (xcb::CW_BACKING_PIXEL, screen.black_pixel()),
47 | (xcb::CW_BACKING_STORE, xcb::BACKING_STORE_NOT_USEFUL),
48 | (xcb::CW_EVENT_MASK,
49 | xcb::EVENT_MASK_KEY_PRESS |
50 | xcb::EVENT_MASK_KEY_RELEASE |
51 | xcb::EVENT_MASK_BUTTON_PRESS |
52 | xcb::EVENT_MASK_BUTTON_RELEASE |
53 | xcb::EVENT_MASK_POINTER_MOTION |
54 | xcb::EVENT_MASK_STRUCTURE_NOTIFY |
55 | xcb::EVENT_MASK_EXPOSURE)]);
56 |
57 | display.flush();
58 |
59 | Ok(Window {
60 | display: display.clone(),
61 |
62 | id: id,
63 | screen: index,
64 | root: screen.root(),
65 | black: screen.black_pixel(),
66 | })
67 | }
68 |
69 | /// Flush the request queue.
70 | pub fn flush(&self) {
71 | self.display.flush();
72 | }
73 |
74 | /// Get the id.
75 | pub fn id(&self) -> u32 {
76 | self.id
77 | }
78 |
79 | /// Get the screen.
80 | pub fn screen(&self) -> i32 {
81 | self.screen
82 | }
83 |
84 | /// Get the screen root.
85 | pub fn root(&self) -> u32 {
86 | self.root
87 | }
88 |
89 | /// Get the black pixel.
90 | pub fn black(&self) -> u32 {
91 | self.black
92 | }
93 |
94 | /// Resize the window.
95 | pub fn resize(&self, width: u32, height: u32) {
96 | xcb::configure_window(&self.display, self.id(), &[
97 | (xcb::CONFIG_WINDOW_WIDTH as u16, width),
98 | (xcb::CONFIG_WINDOW_HEIGHT as u16, height)]);
99 |
100 | self.flush();
101 | }
102 |
103 | /// Get the dimensions.
104 | pub fn dimensions(&self) -> (u32, u32) {
105 | if let Ok(reply) = xcb::get_geometry(&self.display, self.id()).get_reply() {
106 | (reply.width() as u32, reply.height() as u32)
107 | }
108 | else {
109 | (0, 0)
110 | }
111 | }
112 | }
113 |
114 | impl Drop for Window {
115 | fn drop(&mut self) {
116 | xcb::destroy_window(&self.display, self.id);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/preview/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | mod preview;
19 | pub use self::preview::{Preview, Request, Response};
20 |
21 | mod window;
22 | pub use self::window::Window;
23 |
--------------------------------------------------------------------------------
/src/preview/preview.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::thread;
19 | use std::ops::Deref;
20 |
21 | use channel::{self, Receiver, Sender, select};
22 | use xcb;
23 | use xkb::{self, key};
24 |
25 | use crate::error;
26 | use crate::config::Config;
27 | use crate::saver::{self, Saver};
28 | use api::{self, Password, Pointer};
29 | use super::{Window};
30 | use crate::platform::{self, Display, Keyboard};
31 |
32 | pub struct Preview {
33 | receiver: Receiver,
34 | sender: Sender,
35 | }
36 |
37 | #[derive(Clone)]
38 | pub enum Request {
39 |
40 | }
41 |
42 | #[derive(Clone)]
43 | pub enum Response {
44 | Done,
45 | }
46 |
47 | impl Preview {
48 | pub fn spawn>(name: T, config: Config) -> error::Result {
49 | let display = Display::open(None)?;
50 | let mut keyboard = Keyboard::new(display.clone(), None)?;
51 | let window = Window::create(display.clone())?;
52 | let mut saver = Saver::spawn(name.as_ref())?;
53 | let mut throttle = config.saver().throttle();
54 |
55 | saver.config(config.saver().get(name)).unwrap();
56 | saver.target(display.name(), window.screen(), window.id() as u64).unwrap();
57 |
58 | if throttle {
59 | saver.throttle(true).unwrap();
60 | }
61 |
62 | let (sender, i_receiver) = channel::unbounded();
63 | let (i_sender, receiver) = channel::unbounded();
64 |
65 | thread::spawn(move || {
66 | let x = platform::display::sink(&display);
67 | let s = saver.take().unwrap();
68 |
69 | loop {
70 | select! {
71 | // Handle control events.
72 | recv(receiver) -> event => {
73 | match event {
74 | _ => ()
75 | }
76 | },
77 |
78 | // Handle saver events.
79 | recv(s) -> event => {
80 | match event.unwrap() {
81 | saver::Response::Forward(api::Response::Initialized) => {
82 | saver.start().unwrap();
83 | }
84 |
85 | saver::Response::Forward(api::Response::Started) => {
86 | if saver.was_started() {
87 | window.show();
88 | }
89 | else {
90 | saver.kill();
91 | }
92 | }
93 |
94 | saver::Response::Forward(api::Response::Stopped) => {
95 | if !saver.was_stopped() {
96 | saver.kill();
97 | }
98 | }
99 |
100 | saver::Response::Exit(..) => {
101 | break;
102 | }
103 | }
104 | },
105 |
106 | // Handle X events.
107 | recv(x) -> event => {
108 | let event = event.unwrap();
109 |
110 | match event.response_type() {
111 | // Handle keyboard events.
112 | e if keyboard.owns_event(e) => {
113 | keyboard.handle(&event);
114 | }
115 |
116 | xcb::CONFIGURE_NOTIFY => {
117 | let event = unsafe { xcb::cast_event::(&event) };
118 | saver.resize(event.width() as u32, event.height() as u32).unwrap();
119 | }
120 |
121 | // Handle keyboard input.
122 | xcb::KEY_PRESS => {
123 | let key = unsafe { xcb::cast_event::(&event) };
124 |
125 | match keyboard.symbol(key.detail().into()) {
126 | // Toggle throttling.
127 | Some(key::t) | Some(key::T) => {
128 | throttle = !throttle;
129 | saver.throttle(throttle).unwrap();
130 | }
131 |
132 | // Stop the preview.
133 | Some(key::q) | Some(key::Q) => {
134 | saver.stop().unwrap();
135 | }
136 |
137 | // Test password insertion.
138 | Some(key::i) | Some(key::I) => {
139 | saver.password(Password::Insert).unwrap();
140 | }
141 |
142 | // Test password deletetion.
143 | Some(key::d) | Some(key::D) => {
144 | saver.password(Password::Delete).unwrap();
145 | }
146 |
147 | // Test passsword reset.
148 | Some(key::r) | Some(key::R) => {
149 | saver.password(Password::Reset).unwrap();
150 | }
151 |
152 | // Test password check.
153 | Some(key::c) | Some(key::C) => {
154 | saver.password(Password::Check).unwrap();
155 | }
156 |
157 | // Test password success.
158 | Some(key::s) | Some(key::S) => {
159 | saver.password(Password::Success).unwrap();
160 | }
161 |
162 | // Test password failure.
163 | Some(key::f) | Some(key::F) => {
164 | saver.password(Password::Failure).unwrap();
165 | }
166 |
167 | _ => ()
168 | }
169 | }
170 |
171 | // Handle mouse button presses.
172 | xcb::BUTTON_PRESS | xcb::BUTTON_RELEASE => {
173 | let event = unsafe { xcb::cast_event::(&event) };
174 |
175 | saver.pointer(Pointer::Button {
176 | x: event.event_x() as i32,
177 | y: event.event_y() as i32,
178 |
179 | button: event.detail(),
180 | press: event.response_type() == xcb::BUTTON_PRESS,
181 | }).unwrap();
182 | }
183 |
184 | // Handle mouse motion.
185 | xcb::MOTION_NOTIFY => {
186 | let event = unsafe { xcb::cast_event::(&event) };
187 |
188 | saver.pointer(Pointer::Move {
189 | x: event.event_x() as i32,
190 | y: event.event_y() as i32,
191 | }).unwrap();
192 | }
193 |
194 | _ => ()
195 | }
196 | }
197 | }
198 | }
199 |
200 | sender.send(Response::Done).unwrap();
201 | });
202 |
203 | Ok(Preview {
204 | receiver: i_receiver,
205 | sender: i_sender,
206 | })
207 | }
208 | }
209 |
210 | impl Deref for Preview {
211 | type Target = Receiver;
212 |
213 | fn deref(&self) -> &Receiver {
214 | &self.receiver
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/src/preview/window.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::sync::Arc;
19 | use std::ops::Deref;
20 |
21 | use xcb;
22 | use xcbu::icccm;
23 |
24 | use crate::error;
25 | use crate::platform::{self, Display};
26 |
27 | pub struct Window {
28 | display: Arc,
29 | window: platform::Window,
30 | }
31 |
32 | impl Window {
33 | pub fn create(display: Arc) -> error::Result {
34 | let screen = display.get_setup().roots().nth(display.screen() as usize).unwrap();
35 | let window = platform::Window::create(display.clone(), display.screen(),
36 | (screen.width_in_pixels() as f32 / 1.2) as u32,
37 | (screen.height_in_pixels() as f32 / 1.2) as u32)?;
38 |
39 | xcb::change_property(&display, xcb::PROP_MODE_REPLACE as u8, window.id(),
40 | xcb::ATOM_WM_NAME, xcb::ATOM_STRING, 8, b"ScreenRuster");
41 |
42 | icccm::set_wm_size_hints(&display, window.id(), xcb::ATOM_WM_NORMAL_HINTS, &icccm::SizeHints::empty()
43 | .aspect((screen.width_in_pixels() as i32, screen.height_in_pixels() as i32),
44 | (screen.width_in_pixels() as i32, screen.height_in_pixels() as i32))
45 | .build());
46 |
47 | display.flush();
48 |
49 | Ok(Window {
50 | display: display.clone(),
51 | window: window,
52 | })
53 | }
54 |
55 | pub fn show(&self) {
56 | xcb::map_window(&self.display, self.id());
57 |
58 | self.flush();
59 | }
60 | }
61 |
62 | impl Deref for Window {
63 | type Target = platform::Window;
64 |
65 | fn deref(&self) -> &Self::Target {
66 | &self.window
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/saver.rs:
--------------------------------------------------------------------------------
1 | // Copyleft (ↄ) meh. | http://meh.schizofreni.co
2 | //
3 | // This file is part of screenruster.
4 | //
5 | // screenruster is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // screenruster is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with screenruster. If not, see .
17 |
18 | use std::io::{self, BufRead, BufReader, Write};
19 | use std::process::{self, Child, Command, Stdio};
20 | use std::ops::Deref;
21 | use std::thread;
22 | use std::sync::{Arc, Mutex};
23 | use channel::{self, Receiver, TryRecvError, Sender, SendError};
24 |
25 | use toml;
26 | use log::{log_enabled};
27 | use api::{self, json::{self, object}};
28 | pub use api::{Safety, Password, Pointer};
29 |
30 | use crate::error;
31 |
32 | /// Interaction with an external process that implements the ScreenRuster IPC.
33 | ///
34 | /// It takes care of spawning the process and communicating with it, exposing a
35 | /// simple to use API.
36 | ///
37 | /// When the process dies it sends a message signaling the death, otherwise it
38 | /// just forwards requests and responses.
39 | pub struct Saver {
40 | process: Arc>,
41 | receiver: Option>,
42 | sender: Sender,
43 |
44 | started: bool,
45 | stopped: bool,
46 | }
47 |
48 | #[derive(Debug)]
49 | pub enum Request {
50 | Forward(api::Request),
51 | Exit,
52 | }
53 |
54 | #[derive(Debug)]
55 | pub enum Response {
56 | Forward(api::Response),
57 | Exit(Exit),
58 | }
59 |
60 | #[derive(Debug)]
61 | pub struct Exit {
62 | status: process::ExitStatus,
63 | }
64 |
65 | impl Deref for Exit {
66 | type Target = process::ExitStatus;
67 |
68 | fn deref(&self) -> &Self::Target {
69 | &self.status
70 | }
71 | }
72 |
73 | impl Saver {
74 | /// Spawn the saver with the given name.
75 | pub fn spawn>(name: S) -> error::Result {
76 | let child = Arc::new(Mutex::new(Command::new(format!("screenruster-saver-{}", name.as_ref()))
77 | .stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped())
78 | .spawn()?));
79 |
80 | let (sender, i_receiver) = channel::unbounded();
81 | let (i_sender, receiver) = channel::unbounded();
82 |
83 | // Read from the process stdout, decode the responses from JSON and send
84 | // them through the channel.
85 | {
86 | let input = child.lock().unwrap().stdout.take().unwrap();
87 | let child = child.clone();
88 | let internal = i_sender.clone();
89 |
90 | thread::spawn(move || {
91 | macro_rules! json {
92 | ($body:expr) => (
93 | if let Some(value) = $body {
94 | value
95 | }
96 | else {
97 | continue;
98 | }
99 | );
100 | }
101 |
102 | for line in BufReader::new(input).lines() {
103 | if line.is_err() {
104 | break;
105 | }
106 |
107 | if let Ok(message) = json::parse(&line.unwrap()) {
108 | sender.send(Response::Forward(match json!(message["type"].as_str()) {
109 | "initialized" => {
110 | api::Response::Initialized
111 | }
112 |
113 | "started" => {
114 | api::Response::Started
115 | }
116 |
117 | "stopped" => {
118 | api::Response::Stopped
119 | }
120 |
121 | _ =>
122 | continue
123 | })).unwrap();
124 | }
125 | }
126 |
127 | internal.send(Request::Exit).unwrap();
128 |
129 | let mut child = child.lock().unwrap();
130 | sender.send(Response::Exit(Exit {
131 | status: child.wait().unwrap(),
132 | })).unwrap()
133 | });
134 | }
135 |
136 | // Receive requests from the channel, encode them to JSON and send them to
137 | // the process stdin.
138 | {
139 | let mut output = child.lock().unwrap().stdin.take().unwrap();
140 |
141 | thread::spawn(move || {
142 | while let Ok(request) = receiver.recv() {
143 | match request {
144 | Request::Forward(request) => {
145 | output.write_all(json::stringify(match request {
146 | api::Request::Config(config) => object!{
147 | "type" => "config",
148 | "config" => config
149 | },
150 |
151 | api::Request::Safety(level) => object!{
152 | "type" => "safety",
153 | "safety" => match level {
154 | Safety::High => "high",
155 | Safety::Medium => "medium",
156 | Safety::Low => "low",
157 | }
158 | },
159 |
160 | api::Request::Target { display, screen, window } => object!{
161 | "type" => "target",
162 | "display" => display,
163 | "screen" => screen,
164 | "window" => window
165 | },
166 |
167 | api::Request::Throttle(value) => object!{
168 | "type" => "throttle",
169 | "throttle" => value
170 | },
171 |
172 | api::Request::Blank(value) => object!{
173 | "type" => "blank",
174 | "blank" => value
175 | },
176 |
177 | api::Request::Resize { width, height } => object!{
178 | "type" => "resize",
179 | "width" => width,
180 | "height" => height
181 | },
182 |
183 | api::Request::Pointer(Pointer::Move { x, y }) => object!{
184 | "type" => "pointer",
185 | "move" => object!{
186 | "x" => x,
187 | "y" => y
188 | }
189 | },
190 |
191 | api::Request::Pointer(Pointer::Button { x, y, button, press }) => object!{
192 | "type" => "pointer",
193 | "button" => object!{
194 | "x" => x,
195 | "y" => y,
196 | "button" => button,
197 | "press" => press
198 | }
199 | },
200 |
201 | api::Request::Password(password) => object!{
202 | "type" => "password",
203 | "password" => match password {
204 | Password::Insert => "insert",
205 | Password::Delete => "delete",
206 | Password::Reset => "reset",
207 | Password::Check => "check",
208 | Password::Success => "success",
209 | Password::Failure => "failure",
210 | }
211 | },
212 |
213 | api::Request::Start => object!{
214 | "type" => "start"
215 | },
216 |
217 | api::Request::Lock => object!{
218 | "type" => "lock"
219 | },
220 |
221 | api::Request::Stop => object!{
222 | "type" => "stop"
223 | },
224 | }).as_bytes()).unwrap();
225 |
226 | output.write_all(b"\n").unwrap();
227 | }
228 |
229 | Request::Exit => {
230 | break;
231 | }
232 | }
233 | }
234 | });
235 | }
236 |
237 | // Read from the process stderr and forward it to stderr.
238 | {
239 | let input = child.lock().unwrap().stderr.take().unwrap();
240 |
241 | thread::spawn(move || {
242 | for line in BufReader::new(input).lines() {
243 | if line.is_err() {
244 | break;
245 | }
246 |
247 | if log_enabled!(log::Level::Debug) {
248 | writeln!(&mut io::stderr(), "{}", line.unwrap()).unwrap();
249 | }
250 | }
251 | });
252 | }
253 |
254 | Ok(Saver {
255 | process: child,
256 | receiver: Some(i_receiver),
257 | sender: i_sender,
258 |
259 | started: false,
260 | stopped: false,
261 | })
262 | }
263 |
264 | /// Check if the saver was requested to start.
265 | pub fn was_started(&self) -> bool {
266 | self.started
267 | }
268 |
269 | /// Check if the saver was requested to stop.
270 | pub fn was_stopped(&self) -> bool {
271 | self.stopped
272 | }
273 |
274 | /// Kill the saver process.
275 | pub fn kill(&mut self) {
276 | let _ = self.process.lock().unwrap().kill();
277 | }
278 |
279 | /// Take the internal receiver.
280 | pub fn take(&mut self) -> Option> {
281 | self.receiver.take()
282 | }
283 |
284 | /// Try to receive a message from the saver.
285 | pub fn recv(&mut self) -> Option {
286 | if let Some(receiver) = self.receiver.as_ref() {
287 | match receiver.try_recv() {
288 | Ok(response) =>
289 | Some(response),
290 |
291 | Err(TryRecvError::Empty) =>
292 | None,
293 |
294 | Err(TryRecvError::Disconnected) => {
295 | None
296 | }
297 | }
298 | }
299 | else {
300 | None
301 | }
302 | }
303 |
304 | /// Send the API request.
305 | fn send(&mut self, request: api::Request) -> Result<(), SendError> {
306 | self.sender.send(Request::Forward(request))
307 | }
308 |
309 | /// Configure the saver.
310 | pub fn config(&mut self, config: toml::value::Table) -> Result<(), SendError> {
311 | fn convert(value: &toml::Value) -> json::JsonValue {
312 | match *value {
313 | toml::Value::String(ref value) =>
314 | value.clone().into(),
315 |
316 | toml::Value::Datetime(ref value) =>
317 | value.to_string().into(),
318 |
319 | toml::Value::Integer(value) =>
320 | value.into(),
321 |
322 | toml::Value::Float(value) =>
323 | value.into(),
324 |
325 | toml::Value::Boolean(value) =>
326 | value.into(),
327 |
328 | toml::Value::Array(ref value) =>
329 | json::JsonValue::Array(value.iter().map(|v| convert(v)).collect()),
330 |
331 | toml::Value::Table(ref value) =>
332 | json::JsonValue::Object(value.iter().fold(json::object::Object::new(),
333 | |mut acc, (key, value)| {
334 | acc.insert(key, convert(value));
335 | acc
336 | }))
337 | }
338 | }
339 |
340 | self.send(api::Request::Config(convert(&toml::Value::Table(config))))
341 | }
342 |
343 | /// Specify the safety level.
344 | pub fn safety(&mut self, level: Safety) -> Result<(), SendError> {
345 | self.send(api::Request::Safety(level))
346 | }
347 |
348 | /// Select the rendering target for the saver.
349 | pub fn target<'a, D: Into