├── .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>>(&mut self, display: D, screen: i32, window: u64) -> Result<(), SendError> { 350 | self.send(api::Request::Target { 351 | display: display.into().map(String::from), 352 | screen: screen, 353 | window: window, 354 | }) 355 | } 356 | 357 | /// Throttle or unthrottle the saer. 358 | pub fn throttle(&mut self, value: bool) -> Result<(), SendError> { 359 | self.send(api::Request::Throttle(value)) 360 | } 361 | 362 | /// Tell the saver the screen has been blanked or unblanked. 363 | pub fn blank(&mut self, value: bool) -> Result<(), SendError> { 364 | self.send(api::Request::Blank(value)) 365 | } 366 | 367 | /// Resize the saver. 368 | pub fn resize(&mut self, width: u32, height: u32) -> Result<(), SendError> { 369 | self.send(api::Request::Resize { 370 | width: width, 371 | height: height, 372 | }) 373 | } 374 | 375 | /// Send a pointer event. 376 | pub fn pointer(&mut self, pointer: Pointer) -> Result<(), SendError> { 377 | self.send(api::Request::Pointer(pointer)) 378 | } 379 | 380 | /// Send a password event. 381 | pub fn password(&mut self, password: Password) -> Result<(), SendError> { 382 | self.send(api::Request::Password(password)) 383 | } 384 | 385 | /// Start the saver. 386 | pub fn start(&mut self) -> Result<(), SendError> { 387 | self.started = true; 388 | self.send(api::Request::Start) 389 | } 390 | 391 | /// Start the saver. 392 | pub fn lock(&mut self) -> Result<(), SendError> { 393 | self.send(api::Request::Lock) 394 | } 395 | 396 | /// Stop the saver. 397 | pub fn stop(&mut self) -> Result<(), SendError> { 398 | self.stopped = true; 399 | self.send(api::Request::Stop) 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /src/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::collections::{HashMap, HashSet}; 19 | use std::thread; 20 | use std::ops::Deref; 21 | use std::time::{Instant, SystemTime, Duration}; 22 | use channel::{self, Receiver, Sender, SendError}; 23 | 24 | use crate::error; 25 | use crate::config; 26 | 27 | /// The timer manager. 28 | /// 29 | /// It manages timers with a 1 second granularity, reporting timer expirations 30 | /// through a channel. 31 | /// 32 | /// It also handles custom timeouts on request. 33 | pub struct Timer { 34 | receiver: Receiver, 35 | sender: Sender, 36 | } 37 | 38 | #[derive(Clone, Debug)] 39 | pub enum Request { 40 | /// Request a report on all the timers. 41 | Report { 42 | id: u64, 43 | }, 44 | 45 | /// Request a timeout. 46 | Timeout(Timeout), 47 | 48 | /// Reset the specific event. 49 | Reset(Event), 50 | 51 | /// Suspend the timers. 52 | Suspend(SystemTime), 53 | 54 | /// Resume the timers. 55 | Resume, 56 | 57 | /// The screen was blanked. 58 | Blanked, 59 | 60 | /// The screen was unblanked. 61 | Unblanked, 62 | 63 | /// The screen saver was started. 64 | Started, 65 | 66 | /// The screen was locked. 67 | Locked, 68 | 69 | /// The screen saver was stopped, restarts all timers. 70 | Stopped, 71 | } 72 | 73 | #[derive(Copy, Clone, Debug)] 74 | pub enum Timeout { 75 | /// Set a timeout. 76 | Set { 77 | id: u64, 78 | seconds: u64, 79 | }, 80 | 81 | /// Cancel a timeout. 82 | Cancel { 83 | id: u64, 84 | } 85 | } 86 | 87 | #[derive(Clone, Debug)] 88 | pub enum Event { 89 | /// Deal with idle events. 90 | Idle, 91 | 92 | /// Deal with blanking events. 93 | Blank, 94 | } 95 | 96 | #[derive(Clone, Debug)] 97 | pub enum Response { 98 | /// Report various information about the timer internal status. 99 | Report { 100 | id: u64, 101 | beat: Instant, 102 | idle: Instant, 103 | started: Option, 104 | locked: Option, 105 | blanked: Option, 106 | unblanked: Option, 107 | suspended: Option, 108 | correction: u64, 109 | corrected: bool, 110 | timeouts: HashMap, 111 | }, 112 | 113 | /// A custom timeout has expired. 114 | Timeout { 115 | id: u64 116 | }, 117 | 118 | /// The timers were suspended. 119 | Suspended(SystemTime), 120 | 121 | /// The timers were resumed. 122 | Resumed, 123 | 124 | /// Hurts my kokoro. 125 | Heartbeat(Instant), 126 | 127 | /// The system has been idle long enough. 128 | Start, 129 | 130 | /// The system has been idle long enough after starting. 131 | Lock, 132 | 133 | /// The system has been idle long enough to blank. 134 | Blank, 135 | } 136 | 137 | impl Timer { 138 | /// Spawn the timer thread with the given configuration. 139 | pub fn spawn(config: config::Timer) -> error::Result { 140 | let (sender, i_receiver) = channel::unbounded(); 141 | let (i_sender, receiver) = channel::unbounded(); 142 | 143 | thread::spawn(move || { 144 | // Instant to check last heartbeat. 145 | let mut beat = Instant::now(); 146 | 147 | // Instant to check last activity time. 148 | let mut idle = Instant::now(); 149 | 150 | // Instant to check when the screen saver starter. 151 | let mut started = None::; 152 | 153 | // Instant to check when the screen was locked. 154 | let mut locked = None::; 155 | 156 | // Instant to check when the screen was blanked. 157 | let mut blanked = None::; 158 | 159 | // Instant to check when the screen was unblanked. 160 | let mut unblanked = None::; 161 | 162 | // Instant to check when the timer was suspended. 163 | let mut suspended = None::; 164 | 165 | // Time correction for suspension. 166 | let mut correction = 0; 167 | 168 | // Whether a correction loop has already been done. 169 | let mut corrected = false; 170 | 171 | // The registered timeouts. 172 | let mut timeouts = HashMap::::new(); 173 | 174 | loop { 175 | thread::sleep(Duration::from_secs(1)); 176 | 177 | while let Ok(request) = receiver.try_recv() { 178 | match request { 179 | Request::Report { id } => { 180 | sender.send(Response::Report { 181 | id: id, 182 | beat: beat, 183 | idle: idle, 184 | started: started, 185 | locked: locked, 186 | blanked: blanked, 187 | unblanked: unblanked, 188 | suspended: suspended, 189 | correction: correction, 190 | corrected: corrected, 191 | timeouts: timeouts.clone(), 192 | }).unwrap(); 193 | } 194 | 195 | Request::Timeout(Timeout::Set { id, seconds }) => { 196 | timeouts.insert(id, (Instant::now(), seconds)); 197 | } 198 | 199 | Request::Timeout(Timeout::Cancel { id }) => { 200 | timeouts.remove(&id); 201 | } 202 | 203 | Request::Reset(Event::Idle) => { 204 | idle = Instant::now(); 205 | correction = 0; 206 | } 207 | 208 | Request::Reset(Event::Blank) | Request::Unblanked => { 209 | blanked = None; 210 | unblanked = Some(Instant::now()); 211 | } 212 | 213 | Request::Suspend(time) => { 214 | suspended = Some(time); 215 | sender.send(Response::Suspended(time)).unwrap(); 216 | } 217 | 218 | Request::Resume => { 219 | correction += suspended.take().unwrap().elapsed().unwrap_or(Duration::from_secs(0)).as_secs(); 220 | corrected = false; 221 | } 222 | 223 | Request::Blanked => { 224 | blanked = Some(Instant::now()); 225 | } 226 | 227 | Request::Started => { 228 | started = Some(Instant::now()); 229 | } 230 | 231 | Request::Locked => { 232 | locked = Some(Instant::now()); 233 | } 234 | 235 | Request::Stopped => { 236 | idle = Instant::now(); 237 | started = None; 238 | locked = None; 239 | blanked = None; 240 | correction = 0; 241 | } 242 | } 243 | } 244 | 245 | // Handle custom timeouts. 246 | { 247 | let mut expired = HashSet::new(); 248 | 249 | for (&id, &(ref started, seconds)) in &timeouts { 250 | if started.elapsed().as_secs() > seconds { 251 | expired.insert(id); 252 | } 253 | } 254 | 255 | for &id in &expired { 256 | sender.send(Response::Timeout { id }).unwrap(); 257 | timeouts.remove(&id); 258 | } 259 | } 260 | 261 | // If it's time to send a heart beat, send one and reset. 262 | if beat.elapsed().as_secs() >= config.beat() as u64 { 263 | beat = Instant::now(); 264 | sender.send(Response::Heartbeat(idle)).unwrap(); 265 | } 266 | 267 | // Do not check events if the timers are suspended. 268 | if suspended.is_some() { 269 | continue; 270 | } 271 | 272 | // If blanking is enabled and the screen is not already blanked. 273 | if let (Some(after), false) = (config.blank(), blanked.is_some()) { 274 | if unblanked.unwrap_or(idle).elapsed().as_secs() >= after as u64 { 275 | sender.send(Response::Blank).unwrap(); 276 | blanked = Some(Instant::now()); 277 | } 278 | } 279 | 280 | // If the system has been idle long enough send the message. 281 | if started.is_none() && idle.elapsed().as_secs() + correction >= config.timeout() as u64 { 282 | sender.send(Response::Start).unwrap(); 283 | started = Some(Instant::now()); 284 | } 285 | 286 | // If the screen saver has been started, the screen is not locked and locking is enabled. 287 | if let (Some(start), Some(after), false) = (started, config.lock(), locked.is_some()) { 288 | if start.elapsed().as_secs() + correction >= after as u64 { 289 | sender.send(Response::Lock).unwrap(); 290 | locked = Some(Instant::now()); 291 | } 292 | } 293 | 294 | // Only resume after one corrected loop, this avoids activities right 295 | // after resume cancelling timer events. 296 | if !corrected { 297 | sender.send(Response::Resumed).unwrap(); 298 | corrected = true; 299 | } 300 | } 301 | }); 302 | 303 | Ok(Timer { 304 | receiver: i_receiver, 305 | sender: i_sender, 306 | }) 307 | } 308 | 309 | pub fn timeout(&self, value: Timeout) -> Result<(), SendError> { 310 | self.sender.send(Request::Timeout(value)) 311 | } 312 | 313 | /// Request a report wiht the given id. 314 | pub fn report(&self, id: u64) -> Result<(), SendError> { 315 | self.sender.send(Request::Report { id: id }) 316 | } 317 | 318 | /// Reset the given timer. 319 | pub fn reset(&self, event: Event) -> Result<(), SendError> { 320 | self.sender.send(Request::Reset(event)) 321 | } 322 | 323 | /// Request the timers to suspend at the given time. 324 | pub fn suspend(&self, value: SystemTime) -> Result<(), SendError> { 325 | self.sender.send(Request::Suspend(value)) 326 | } 327 | 328 | /// Request the timers to resume. 329 | pub fn resume(&self) -> Result<(), SendError> { 330 | self.sender.send(Request::Resume) 331 | } 332 | 333 | /// Notice the screen has been blanked. 334 | pub fn blanked(&self) -> Result<(), SendError> { 335 | self.sender.send(Request::Blanked) 336 | } 337 | 338 | /// Notice the screen has been unblanked. 339 | pub fn unblanked(&self) -> Result<(), SendError> { 340 | self.sender.send(Request::Unblanked) 341 | } 342 | 343 | /// Notice the screen saver has started. 344 | pub fn started(&self) -> Result<(), SendError> { 345 | self.sender.send(Request::Started) 346 | } 347 | 348 | /// Notice the screen has been locked. 349 | pub fn locked(&self) -> Result<(), SendError> { 350 | self.sender.send(Request::Locked) 351 | } 352 | 353 | /// Notice the screen saver was stopped. 354 | pub fn stopped(&self) -> Result<(), SendError> { 355 | self.sender.send(Request::Stopped) 356 | } 357 | } 358 | 359 | impl Deref for Timer { 360 | type Target = Receiver; 361 | 362 | fn deref(&self) -> &Receiver { 363 | &self.receiver 364 | } 365 | } 366 | --------------------------------------------------------------------------------